blob: 5b133fdab4e23ad3731f43df0a6336f5d482528b [file]
// Copyright 2020 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::constants::{
FASTBOOT_CHECK_INTERVAL_SECS, FASTBOOT_DROP_GRACE_PERIOD_SECS,
MDNS_BROADCAST_INTERVAL_SECS, MDNS_TARGET_DROP_GRACE_PERIOD_SECS,
},
crate::events::{DaemonEvent, TargetInfo},
crate::fastboot::open_interface_with_serial,
crate::logger::{streamer::DiagnosticsStreamer, Logger},
crate::onet::HostPipeConnection,
anyhow::{anyhow, Context, Error, Result},
ascendd::Ascendd,
async_std::{
future::{timeout, TimeoutError},
sync::RwLock,
},
async_trait::async_trait,
bridge::DaemonError,
chrono::{DateTime, Utc},
ffx_daemon_core::events::{self, EventSynthesizer},
ffx_daemon_core::net::IsLocalAddr,
ffx_daemon_core::task::{SingleFlight, TaskSnapshot},
fidl::endpoints::ServiceMarker,
fidl_fuchsia_developer_bridge as bridge,
fidl_fuchsia_developer_remotecontrol::{
IdentifyHostError, RemoteControlMarker, RemoteControlProxy,
},
fidl_fuchsia_net::{IpAddress, Ipv4Address, Ipv6Address, Subnet},
fidl_fuchsia_overnet_protocol::NodeId,
fuchsia_async::Timer,
futures::future,
futures::lock::Mutex,
futures::prelude::*,
hoist::OvernetInstance,
std::cmp::Ordering,
std::collections::{BTreeSet, HashMap},
std::default::Default,
std::fmt,
std::fmt::{Debug, Display},
std::hash::{Hash, Hasher},
std::io::Write,
std::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
std::net::{Ipv4Addr, Ipv6Addr},
std::sync::{Arc, Weak},
std::time::Duration,
usb_bulk::Interface,
};
pub use crate::target_task::*;
const IDENTIFY_HOST_TIMEOUT_MILLIS: u64 = 1000;
pub trait SshFormatter {
fn ssh_fmt<W: Write>(&self, f: &mut W) -> std::io::Result<()>;
}
impl SshFormatter for TargetAddr {
fn ssh_fmt<W: Write>(&self, f: &mut W) -> std::io::Result<()> {
if self.ip.is_ipv6() {
write!(f, "[")?;
}
write!(f, "{}", self)?;
if self.ip.is_ipv6() {
write!(f, "]")?;
}
Ok(())
}
}
/// A trait for returning a consistent SSH address.
///
/// Based on the structure from which the SSH address is coming, this will
/// return in order of priority:
/// -- The first local IPv6 address with a scope id.
/// -- The last local IPv4 address.
/// -- Any other address.
pub trait SshAddrFetcher {
fn to_ssh_addr(self) -> Option<TargetAddr>;
}
impl<'a, T: Copy + IntoIterator<Item = &'a TargetAddr>> SshAddrFetcher for &'a T {
fn to_ssh_addr(self) -> Option<TargetAddr> {
let mut res: Option<TargetAddr> = None;
for addr in self.into_iter() {
let is_valid_local_addr = addr.ip().is_local_addr()
&& (addr.ip().is_ipv4() || !(addr.ip().is_link_local_addr() && addr.scope_id == 0));
if res.is_none() || is_valid_local_addr {
res.replace(addr.clone());
}
if addr.ip().is_ipv6() && is_valid_local_addr {
res.replace(addr.clone());
break;
}
}
res
}
}
#[async_trait]
pub trait ToFidlTarget {
async fn to_fidl_target(self) -> bridge::Target;
}
#[derive(Debug, Clone)]
pub struct RcsConnection {
pub proxy: RemoteControlProxy,
pub overnet_id: NodeId,
ascendd: Arc<Ascendd>,
}
impl Hash for RcsConnection {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.overnet_id.id.hash(state)
}
}
impl PartialEq for RcsConnection {
fn eq(&self, other: &Self) -> bool {
self.overnet_id == other.overnet_id
}
}
impl Eq for RcsConnection {}
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub enum TargetEvent {
RcsActivated,
Rediscovered,
/// LHS is previous state, RHS is current state.
ConnectionStateChanged(ConnectionState, ConnectionState),
}
#[derive(Debug)]
pub enum RcsConnectionError {
/// There is something wrong with the FIDL connection.
FidlConnectionError(fidl::Error),
/// There was a timeout trying to communicate with RCS.
ConnectionTimeoutError(TimeoutError),
/// There is an error from within Rcs itself.
RemoteControlError(IdentifyHostError),
/// There is an error with the output from Rcs.
TargetError(Error),
}
impl Display for RcsConnectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RcsConnectionError::FidlConnectionError(ferr) => {
write!(f, "fidl connection error: {}", ferr)
}
RcsConnectionError::ConnectionTimeoutError(_) => write!(f, "timeout error"),
RcsConnectionError::RemoteControlError(ierr) => write!(f, "internal error: {:?}", ierr),
RcsConnectionError::TargetError(error) => write!(f, "general error: {}", error),
}
}
}
impl RcsConnection {
pub async fn new(ascendd: Arc<Ascendd>, id: &mut NodeId) -> Result<Self> {
let (s, p) = fidl::Channel::create().context("failed to create zx channel")?;
let _result = RcsConnection::connect_to_service(&ascendd, id, s)?;
let proxy = RemoteControlProxy::new(
fidl::AsyncChannel::from_channel(p).context("failed to make async channel")?,
);
Ok(Self { ascendd, proxy, overnet_id: id.clone() })
}
pub fn copy_to_channel(&mut self, channel: fidl::Channel) -> Result<()> {
RcsConnection::connect_to_service(&self.ascendd, &mut self.overnet_id, channel)
}
fn connect_to_service(
ascendd: &Arc<Ascendd>,
overnet_id: &mut NodeId,
channel: fidl::Channel,
) -> Result<()> {
let svc = ascendd.connect_as_service_consumer()?;
svc.connect_to_service(overnet_id, RemoteControlMarker::NAME, channel)
.map_err(|e| anyhow!("Error connecting to Rcs: {}", e))
}
// For testing.
#[cfg(test)]
pub fn new_with_proxy(ascendd: Arc<Ascendd>, proxy: RemoteControlProxy, id: &NodeId) -> Self {
Self { proxy, overnet_id: id.clone(), ascendd }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConnectionState {
/// Default state: no connection.
Disconnected,
/// Contains the last known ping from mDNS.
Mdns(DateTime<Utc>),
/// Contains an actual connection to RCS.
Rcs(RcsConnection),
/// Target was manually added. Same as `Disconnected` but indicates that the target's name is
/// wrong as well.
Manual,
/// Contains the last known interface update with a Fastboot serial number.
Fastboot(DateTime<Utc>),
}
impl Default for ConnectionState {
fn default() -> Self {
ConnectionState::Disconnected
}
}
impl ConnectionState {
fn is_connected(&self) -> bool {
match self {
Self::Disconnected => false,
_ => true,
}
}
fn take_rcs(&mut self) -> Option<RcsConnection> {
if self.is_rcs() {
match std::mem::replace(self, ConnectionState::Disconnected) {
ConnectionState::Rcs(r) => Some(r),
_ => None,
}
} else {
None
}
}
fn is_rcs(&self) -> bool {
match self {
ConnectionState::Rcs(_) => true,
_ => false,
}
}
}
#[derive(Debug, Clone, Hash)]
pub(crate) struct TargetAddrEntry {
addr: TargetAddr,
timestamp: DateTime<Utc>,
}
impl PartialEq for TargetAddrEntry {
fn eq(&self, other: &Self) -> bool {
self.addr.eq(&other.addr)
}
}
impl Eq for TargetAddrEntry {}
impl From<(TargetAddr, DateTime<Utc>)> for TargetAddrEntry {
fn from(t: (TargetAddr, DateTime<Utc>)) -> Self {
let (addr, timestamp) = t;
Self { addr, timestamp }
}
}
impl From<TargetAddr> for TargetAddrEntry {
fn from(addr: TargetAddr) -> Self {
Self { addr, timestamp: Utc::now() }
}
}
impl Ord for TargetAddrEntry {
fn cmp(&self, other: &Self) -> Ordering {
self.addr.cmp(&other.addr)
}
}
impl PartialOrd for TargetAddrEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Default)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
pub struct TargetState {
pub connection_state: ConnectionState,
}
struct TargetInner {
nodename: Mutex<Option<String>>,
state: Mutex<TargetState>,
last_response: RwLock<DateTime<Utc>>,
addrs: RwLock<BTreeSet<TargetAddrEntry>>,
// used for Fastboot
serial: RwLock<Option<String>>,
boot_timestamp_nanos: RwLock<Option<u64>>,
diagnostics_info: Arc<DiagnosticsStreamer>,
ascendd: Arc<Ascendd>,
}
impl TargetInner {
fn new(ascendd: Arc<Ascendd>, nodename: Option<&str>) -> Self {
Self {
ascendd,
nodename: Mutex::new(nodename.map(str::to_string)),
last_response: RwLock::new(Utc::now()),
state: Mutex::new(TargetState::default()),
addrs: RwLock::new(BTreeSet::new()),
serial: RwLock::new(None),
boot_timestamp_nanos: RwLock::new(None),
diagnostics_info: Arc::new(DiagnosticsStreamer::default()),
}
}
pub fn new_with_boot_timestamp(
ascendd: Arc<Ascendd>,
nodename: &str,
boot_timestamp_nanos: u64,
) -> Self {
Self {
boot_timestamp_nanos: RwLock::new(Some(boot_timestamp_nanos)),
..Self::new(ascendd, Some(nodename))
}
}
pub fn new_with_addrs(
ascendd: Arc<Ascendd>,
nodename: Option<&str>,
addrs: BTreeSet<TargetAddr>,
) -> Self {
Self {
addrs: RwLock::new(addrs.iter().map(|e| (*e, Utc::now()).into()).collect()),
..Self::new(ascendd, nodename)
}
}
pub fn new_with_serial(ascendd: Arc<Ascendd>, nodename: &str, serial: &str) -> Self {
Self { serial: RwLock::new(Some(serial.to_string())), ..Self::new(ascendd, Some(nodename)) }
}
/// Dependency injection constructor so we can insert a fake time for
/// testing.
#[cfg(test)]
pub fn new_with_time(ascendd: Arc<Ascendd>, nodename: &str, time: DateTime<Utc>) -> Self {
Self { last_response: RwLock::new(time), ..Self::new(ascendd, Some(nodename)) }
}
pub async fn nodename_str(&self) -> String {
self.nodename.lock().await.clone().unwrap_or("<unknown>".to_owned())
}
}
#[async_trait]
impl EventSynthesizer<TargetEvent> for TargetInner {
async fn synthesize_events(&self) -> Vec<TargetEvent> {
match self.state.lock().await.connection_state {
ConnectionState::Rcs(_) => vec![TargetEvent::RcsActivated],
_ => vec![],
}
}
}
#[derive(Clone)]
pub struct WeakTarget {
pub events: events::Queue<TargetEvent>,
inner: Weak<TargetInner>,
}
impl WeakTarget {
/// attempts to upgrade to a target with a null task manager.
pub fn upgrade(&self) -> Option<Target> {
let inner = self.inner.upgrade()?;
let events = self.events.clone();
Some(Target {
inner,
events,
task_manager: Arc::new(SingleFlight::new(|_| futures::future::ready(Ok(())).boxed())),
})
}
}
#[derive(Clone)]
pub struct Target {
pub events: events::Queue<TargetEvent>,
// TODO(awdavies): This shouldn't need to be behind an Arc<>, but for some
// reason (probably something to do with the merge_insert function in the
// TargetCollection struct?) this will drop all tasks immediately if this
// isn't an Arc<>.
pub task_manager: Arc<SingleFlight<TargetTaskType, Result<(), String>>>,
inner: Arc<TargetInner>,
}
impl Target {
async fn mdns_monitor_loop(weak_target: WeakTarget, limit: Duration) -> Result<(), String> {
let limit = chrono::Duration::from_std(limit).map_err(|e| format!("{:?}", e))?;
loop {
if let Some(t) = weak_target.upgrade() {
let nodename = t.nodename_str().await;
t.update_connection_state(|s| match s {
ConnectionState::Mdns(ref time) => {
let now = Utc::now();
if now.signed_duration_since(*time) > limit {
log::debug!(
"dropping target '{}'. MDNS response older than {}",
nodename,
limit,
);
ConnectionState::Disconnected
} else {
s
}
}
_ => s,
})
.await;
Timer::new(Duration::from_secs(1)).await;
} else {
log::debug!("parent target dropped in mdns monitor loop. exiting");
break;
}
}
Ok(())
}
async fn fastboot_monitor_loop(weak_target: WeakTarget, limit: Duration) -> Result<(), String> {
let limit = chrono::Duration::from_std(limit).map_err(|e| format!("{:?}", e))?;
loop {
if let Some(t) = weak_target.upgrade() {
let nodename = t.nodename_str().await;
t.update_connection_state(|s| match s {
ConnectionState::Fastboot(ref time) => {
let now = Utc::now();
if now.signed_duration_since(*time) > limit {
log::debug!(
"dropping target '{}'. fastboot state older than {}",
nodename,
limit
);
ConnectionState::Disconnected
} else {
s
}
}
_ => s,
})
.await;
Timer::new(Duration::from_secs(1)).await;
} else {
log::debug!("parent target dropped in serial monitor loop. exiting");
break;
}
}
Ok(())
}
pub async fn is_connected(&self) -> bool {
self.inner.state.lock().await.connection_state.is_connected()
}
pub fn downgrade(&self) -> WeakTarget {
WeakTarget { events: self.events.clone(), inner: Arc::downgrade(&self.inner) }
}
pub fn ascendd(&self) -> &Arc<Ascendd> {
&self.inner.ascendd
}
fn from_inner(inner: Arc<TargetInner>) -> Self {
let events = events::Queue::new(&inner);
let weak_inner = Arc::downgrade(&inner);
let weak_target = WeakTarget { inner: weak_inner, events: events.clone() };
let task_manager = Arc::new(SingleFlight::new(move |t| match t {
TargetTaskType::HostPipe => HostPipeConnection::new(weak_target.clone()).boxed(),
TargetTaskType::MdnsMonitor => Target::mdns_monitor_loop(
weak_target.clone(),
Duration::from_secs(
MDNS_BROADCAST_INTERVAL_SECS + MDNS_TARGET_DROP_GRACE_PERIOD_SECS,
),
)
.boxed(),
TargetTaskType::ProactiveLog => Logger::new(weak_target.clone()).start().boxed(),
TargetTaskType::FastbootMonitor => Target::fastboot_monitor_loop(
weak_target.clone(),
Duration::from_secs(FASTBOOT_CHECK_INTERVAL_SECS + FASTBOOT_DROP_GRACE_PERIOD_SECS),
)
.boxed(),
}));
Self { inner, events, task_manager }
}
pub fn new(ascendd: Arc<Ascendd>, nodename: &str) -> Self {
let inner = Arc::new(TargetInner::new(ascendd, Some(nodename)));
Self::from_inner(inner)
}
pub fn new_with_boot_timestamp(
ascendd: Arc<Ascendd>,
nodename: &str,
boot_timestamp_nanos: u64,
) -> Self {
let inner =
Arc::new(TargetInner::new_with_boot_timestamp(ascendd, nodename, boot_timestamp_nanos));
Self::from_inner(inner)
}
pub fn new_with_addrs(
ascendd: Arc<Ascendd>,
nodename: Option<&str>,
addrs: BTreeSet<TargetAddr>,
) -> Self {
let inner = Arc::new(TargetInner::new_with_addrs(ascendd, nodename, addrs));
Self::from_inner(inner)
}
pub fn new_with_serial(ascendd: Arc<Ascendd>, nodename: &str, serial: &str) -> Self {
let inner = Arc::new(TargetInner::new_with_serial(ascendd, nodename, serial));
Self::from_inner(inner)
}
pub fn from_target_info(ascendd: Arc<Ascendd>, mut t: TargetInfo) -> Self {
if let Some(s) = t.serial {
Self::new_with_serial(ascendd, t.nodename.as_ref(), &s)
} else {
Self::new_with_addrs(
ascendd,
Some(t.nodename.as_ref()),
t.addresses.drain(..).collect(),
)
}
}
/// Dependency injection constructor so we can insert a fake time for
/// testing.
#[cfg(test)]
pub fn new_with_time(ascendd: Arc<Ascendd>, nodename: &str, time: DateTime<Utc>) -> Self {
let inner = Arc::new(TargetInner::new_with_time(ascendd, nodename, time));
Self::from_inner(inner)
}
async fn rcs_state(&self) -> bridge::RemoteControlState {
let loop_running = self.task_manager.task_snapshot(TargetTaskType::HostPipe).await
== TaskSnapshot::Running;
let state = self.inner.state.lock().await;
match (loop_running, &state.connection_state) {
(true, ConnectionState::Rcs(_)) => bridge::RemoteControlState::Up,
(true, _) => bridge::RemoteControlState::Down,
(_, _) => bridge::RemoteControlState::Unknown,
}
}
pub async fn nodename(&self) -> Option<String> {
self.inner.nodename.lock().await.clone()
}
pub async fn nodename_str(&self) -> String {
self.inner.nodename_str().await
}
pub async fn boot_timestamp_nanos(&self) -> Option<u64> {
self.inner.boot_timestamp_nanos.read().await.clone()
}
pub async fn update_boot_timestamp(&self, ts: Option<u64>) {
let mut inner_ts = self.inner.boot_timestamp_nanos.write().await;
*inner_ts = ts;
}
pub fn stream_info(&self) -> Arc<DiagnosticsStreamer> {
self.inner.diagnostics_info.clone()
}
pub async fn serial(&self) -> Option<String> {
self.inner.serial.read().await.clone()
}
pub async fn get_connection_state(&self) -> ConnectionState {
let state = self.inner.state.lock().await;
state.connection_state.clone()
}
/// Allows a client to atomically update the connection state based on what
/// is returned from the predicate.
///
/// For example, if the client wants to update the connection state to RCS
/// connected, but only if the target is already disconnected, it would
/// look like this:
///
/// ```rust
/// let rcs_connection = ...;
/// target.update_connection_state(move |s| {
/// match s {
/// ConnectionState::Disconnected => ConnectionState::Rcs(rcs_connection),
/// _ => s
/// }
/// }).await;
/// ```
///
/// The client must always return the state, as this is swapped with the
/// current target state in-place.
///
/// If the state changes, this will push a `ConnectionStateChanged` event
/// to the event queue.
pub async fn update_connection_state<F>(&self, func: F)
where
F: FnOnce(ConnectionState) -> ConnectionState + Sized + Send,
{
let mut state = self.inner.state.lock().await;
let former_state = state.connection_state.clone();
let update =
(func)(std::mem::replace(&mut state.connection_state, ConnectionState::Disconnected));
state.connection_state = update;
if former_state != state.connection_state {
let _ = self
.events
.push(TargetEvent::ConnectionStateChanged(
former_state,
state.connection_state.clone(),
))
.await;
}
}
pub async fn rcs(&self) -> Option<RcsConnection> {
match &self.inner.state.lock().await.connection_state {
ConnectionState::Rcs(conn) => Some(conn.clone()),
_ => None,
}
}
pub async fn usb(&self) -> Option<Interface> {
match self.inner.serial.read().await.as_ref() {
Some(s) => open_interface_with_serial(s).ok(),
None => None,
}
}
pub async fn last_response(&self) -> DateTime<Utc> {
self.inner.last_response.read().await.clone()
}
pub async fn addrs(&self) -> Vec<TargetAddr> {
let mut addrs = self.inner.addrs.read().await.iter().cloned().collect::<Vec<_>>();
addrs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
addrs.drain(..).map(|e| e.addr).collect()
}
#[cfg(test)]
pub(crate) async fn addrs_insert(&self, t: TargetAddr) {
self.inner.addrs.write().await.replace(t.into());
}
#[cfg(test)]
pub async fn new_autoconnected(ascendd: Arc<Ascendd>, n: &str) -> Self {
let s = Self::new(ascendd, n);
s.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(Utc::now())
})
.await;
s
}
#[cfg(test)]
pub(crate) async fn addrs_insert_entry(&self, t: TargetAddrEntry) {
self.inner.addrs.write().await.replace(t);
}
async fn addrs_extend<T>(&self, new_addrs: T)
where
T: IntoIterator<Item = TargetAddr>,
{
let now = Utc::now();
let mut addrs = self.inner.addrs.write().await;
// This is functionally the same as the regular extend function, instead
// replacing each item. This is to ensure that the timestamps are
// updated as they are seen.
new_addrs.into_iter().for_each(move |new_addr| {
let mut addr = new_addr;
// Subtle:
// Some sources of addresses can not be scoped, such as those which come from queries
// over Overnet.
// Link-local IPv6 addresses require scopes in order to be routable, and mdns events will
// provide us with valid scopes. As such, if an incoming address is not scoped, try to
// find an existing address entry with a scope, and carry the scope forward.
// If the incoming address has a scope, it is likely to be more recent than one that was
// originally present, for example if a directly connected USB target has restarted,
// wherein the scopeid could be incremented due to the device being given a new
// interface id allocation.
if addr.ip().is_ipv6() && addr.scope_id == 0 {
if let Some(entry) = addrs.get(&(addr, now.clone()).into()) {
addr.scope_id = entry.addr.scope_id;
}
}
addrs.replace((addr, now.clone()).into());
});
}
async fn update_last_response(&self, other: DateTime<Utc>) {
let mut last_response = self.inner.last_response.write().await;
if *last_response < other {
*last_response = other;
}
}
async fn overwrite_state(&self, mut other: TargetState) {
let mut state = self.inner.state.lock().await;
if let Some(rcs) = other.connection_state.take_rcs() {
// Nots this so that we know to push an event.
let rcs_activated = !state.connection_state.is_rcs();
state.connection_state = ConnectionState::Rcs(rcs);
if rcs_activated {
self.events.push(TargetEvent::RcsActivated).await.unwrap_or_else(|err| {
log::warn!("unable to enqueue RCS activation event: {:#}", err)
});
}
}
}
pub async fn from_rcs_connection(r: RcsConnection) -> Result<Self, RcsConnectionError> {
let identify_result =
timeout(Duration::from_millis(IDENTIFY_HOST_TIMEOUT_MILLIS), r.proxy.identify_host())
.await
.map_err(|e| RcsConnectionError::ConnectionTimeoutError(e))?;
let fidl_target = match identify_result {
Ok(res) => match res {
Ok(target) => target,
Err(e) => return Err(RcsConnectionError::RemoteControlError(e)),
},
Err(e) => return Err(RcsConnectionError::FidlConnectionError(e)),
};
let nodename = fidl_target
.nodename
.ok_or(RcsConnectionError::TargetError(anyhow!("nodename required")))?;
// TODO(awdavies): Merge target addresses once the scope_id is picked
// up properly, else there will be duplicate link-local addresses that
// aren't usable.
let target = match fidl_target.boot_timestamp_nanos {
Some(t) => Target::new_with_boot_timestamp(r.ascendd.clone(), nodename.as_ref(), t),
None => Target::new(r.ascendd.clone(), nodename.as_ref()),
};
// Forces drop of target state mutex so that target can be returned.
{
let mut target_state = target.inner.state.lock().await;
target_state.connection_state = ConnectionState::Rcs(r);
}
{
let mut target_addrs = target.inner.addrs.write().await;
let localhost_v4 = IpAddr::V4(Ipv4Addr::LOCALHOST);
let localhost_v6 = IpAddr::V6(Ipv6Addr::LOCALHOST);
for addr in fidl_target.addresses {
for subnet in addr {
// Note: This does not have a valid scope!
let ta: TargetAddr = subnet.clone().into();
// Do not add localhost addresses to the daemon from RCS, as every node has them.
if ta.ip() == localhost_v4 || ta.ip() == localhost_v6 {
continue;
}
target_addrs.replace(ta.into());
}
}
}
Ok(target)
}
pub async fn run_host_pipe(&self) {
self.task_manager.spawn_detached(TargetTaskType::HostPipe).await
}
pub async fn run_mdns_monitor(&self) {
self.task_manager.spawn_detached(TargetTaskType::MdnsMonitor).await;
}
pub async fn run_fastboot_monitor(&self) {
self.task_manager.spawn_detached(TargetTaskType::FastbootMonitor).await;
}
pub async fn run_logger(&self) {
self.task_manager.spawn_detached(TargetTaskType::ProactiveLog).await;
}
}
#[async_trait]
impl EventSynthesizer<DaemonEvent> for Target {
async fn synthesize_events(&self) -> Vec<DaemonEvent> {
if self.inner.state.lock().await.connection_state.is_connected() {
vec![DaemonEvent::NewTarget(self.nodename().await)]
} else {
vec![]
}
}
}
#[async_trait]
impl ToFidlTarget for Target {
async fn to_fidl_target(self) -> bridge::Target {
let (addrs, last_response, rcs_state) =
futures::join!(self.addrs(), self.last_response(), self.rcs_state());
bridge::Target {
nodename: self.nodename().await,
addresses: Some(addrs.iter().map(|a| a.into()).collect()),
age_ms: Some(
match Utc::now().signed_duration_since(last_response).num_milliseconds() {
dur if dur < 0 => {
log::trace!(
"negative duration encountered on target '{}': {}",
self.inner.nodename_str().await,
dur
);
0
}
dur => dur,
} as u64,
),
rcs_state: Some(rcs_state),
// TODO(awdavies): Gather more information here when possible.
target_type: Some(bridge::TargetType::Unknown),
target_state: Some(bridge::TargetState::Unknown),
..bridge::Target::EMPTY
}
}
}
impl Debug for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Target {{ {:?} }}", self.inner.nodename)
}
}
// TODO(fxbug.dev/52733): Have `TargetAddr` support serial numbers.
#[derive(Hash, Clone, Debug, Copy, Eq, PartialEq)]
pub struct TargetAddr {
ip: IpAddr,
scope_id: u32,
}
impl Ord for TargetAddr {
fn cmp(&self, other: &Self) -> Ordering {
let this_socket = SocketAddr::from(self);
let other_socket = SocketAddr::from(other);
this_socket.cmp(&other_socket)
}
}
impl PartialOrd for TargetAddr {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Into<bridge::TargetAddrInfo> for &TargetAddr {
fn into(self) -> bridge::TargetAddrInfo {
bridge::TargetAddrInfo::Ip(bridge::TargetIp {
ip: match self.ip {
IpAddr::V6(i) => IpAddress::Ipv6(Ipv6Address { addr: i.octets().into() }),
IpAddr::V4(i) => IpAddress::Ipv4(Ipv4Address { addr: i.octets().into() }),
},
scope_id: self.scope_id,
})
}
}
impl Into<bridge::TargetAddrInfo> for TargetAddr {
fn into(self) -> bridge::TargetAddrInfo {
(&self).into()
}
}
impl From<bridge::TargetAddrInfo> for TargetAddr {
fn from(t: bridge::TargetAddrInfo) -> Self {
(&t).into()
}
}
impl From<&bridge::TargetAddrInfo> for TargetAddr {
fn from(t: &bridge::TargetAddrInfo) -> Self {
let (addr, scope): (IpAddr, u32) = match t {
bridge::TargetAddrInfo::Ip(ip) => match ip.ip {
IpAddress::Ipv6(Ipv6Address { addr }) => (addr.into(), ip.scope_id),
IpAddress::Ipv4(Ipv4Address { addr }) => (addr.into(), ip.scope_id),
},
// TODO(fxbug.dev/52733): Add serial numbers.,
};
(addr, scope).into()
}
}
impl From<Subnet> for TargetAddr {
fn from(i: Subnet) -> Self {
// TODO(awdavies): Figure out if it's possible to get the scope_id from
// this address.
match i.addr {
IpAddress::Ipv4(ip4) => SocketAddr::from((ip4.addr, 0)).into(),
IpAddress::Ipv6(ip6) => SocketAddr::from((ip6.addr, 0)).into(),
}
}
}
impl From<TargetAddr> for SocketAddr {
fn from(t: TargetAddr) -> Self {
Self::from(&t)
}
}
impl From<&TargetAddr> for SocketAddr {
fn from(t: &TargetAddr) -> Self {
match t.ip {
IpAddr::V6(addr) => SocketAddr::V6(SocketAddrV6::new(addr, 0, 0, t.scope_id)),
IpAddr::V4(addr) => SocketAddr::V4(SocketAddrV4::new(addr, 0)),
}
}
}
impl From<(IpAddr, u32)> for TargetAddr {
fn from(f: (IpAddr, u32)) -> Self {
Self { ip: f.0, scope_id: f.1 }
}
}
impl From<SocketAddr> for TargetAddr {
fn from(s: SocketAddr) -> Self {
Self {
ip: s.ip(),
scope_id: match s {
SocketAddr::V6(addr) => addr.scope_id(),
_ => 0,
},
}
}
}
impl TargetAddr {
pub fn scope_id(&self) -> u32 {
self.scope_id
}
pub fn ip(&self) -> IpAddr {
self.ip.clone()
}
}
impl Display for TargetAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.ip())?;
if self.ip.is_link_local_addr() && self.scope_id() > 0 {
let mut buf = vec![0; libc::IF_NAMESIZE];
let res = unsafe {
libc::if_indextoname(self.scope_id(), buf.as_mut_ptr() as *mut libc::c_char)
};
if res.is_null() {
// TODO(awdavies): This will likely happen if the interface
// is unplugged before being removed from the target cache.
// There should be a clear error for the user indicating why
// the interface name wasn't shown as a string.
log::warn!(
"error getting interface name: {}",
nix::Error::from_errno(nix::errno::Errno::from_i32(nix::errno::errno())),
);
write!(f, "%{}", self.scope_id())?;
} else {
let string =
String::from_utf8_lossy(&buf.split(|&c| c == 0u8).next().unwrap_or(&[0u8]));
if !string.is_empty() {
write!(f, "%{}", string)?;
} else {
log::warn!("empty string for iface idx: {}", self.scope_id());
}
}
}
Ok(())
}
}
#[async_trait]
pub trait MatchTarget {
async fn match_target<TQ>(self, t: TQ) -> Option<Target>
where
TQ: Into<TargetQuery> + Send;
}
// It's unclear why this definition has to exist, but the compiler complains
// if invoking this either directly on `iter()` or if invoking on
// `iter().into_iter()` about there being unmet trait constraints. With this
// definition there are no compilation complaints.
#[async_trait]
impl<'a> MatchTarget for std::slice::Iter<'_, &'a Target> {
async fn match_target<TQ>(self, t: TQ) -> Option<Target>
where
TQ: Into<TargetQuery> + Send,
{
let t: TargetQuery = t.into();
for target in self {
if t.matches(&target).await {
return Some((*target).clone());
}
}
None
}
}
#[async_trait]
impl<'a, T: Iterator<Item = &'a Target> + Send> MatchTarget for &'a mut T {
async fn match_target<TQ>(self, t: TQ) -> Option<Target>
where
TQ: Into<TargetQuery> + Send,
{
let t: TargetQuery = t.into();
for target in self {
if t.matches(&target).await {
return Some(target.clone());
}
}
None
}
}
pub enum TargetQuery {
/// Attempts to match the nodename, falling back to serial (in that order).
NodenameOrSerial(String),
Addr(TargetAddr),
OvernetId(u64),
}
impl TargetQuery {
pub async fn matches(&self, t: &Target) -> bool {
match self {
// Simultaneously attempts to match either the nodename or the
// serial number.
Self::NodenameOrSerial(arg) => {
let (a, b) = futures::join!(
async {
t.inner
.nodename
.lock()
.await
.as_ref()
.map(|nodename| (*nodename).contains(arg))
.unwrap_or(false)
},
async {
t.inner
.serial
.read()
.await
.as_ref()
.map(|serial| (*serial).contains(arg))
.unwrap_or(false)
}
);
a || b
}
Self::Addr(addr) => {
let addrs = t.addrs().await;
// Try to do the lookup if either the scope_id is non-zero (and
// the IP address is IPv6 OR if the address is just IPv4 (as the
// scope_id is always zero in this case).
if addr.scope_id != 0 || addr.ip.is_ipv4() {
return addrs.contains(addr);
}
// Currently there's no way to parse an IP string w/ a scope_id,
// so if we're at this stage the address is IPv6 and has been
// probably parsed with a string (or it's a global IPv6 addr).
for target_addr in addrs.iter() {
if target_addr.ip == addr.ip {
return true;
}
}
false
}
Self::OvernetId(id) => match &t.inner.state.lock().await.connection_state {
ConnectionState::Rcs(rcs) => rcs.overnet_id.id == *id,
_ => false,
},
}
}
}
impl From<&str> for TargetQuery {
fn from(s: &str) -> Self {
String::from(s).into()
}
}
impl From<String> for TargetQuery {
/// If the string can be parsed as some kind of IP address, will attempt to
/// match based on that, else fall back to the nodename or serial matches.
fn from(s: String) -> Self {
match s.parse::<IpAddr>() {
Ok(a) => Self::Addr((a, 0).into()),
Err(_) => Self::NodenameOrSerial(s),
}
}
}
impl From<TargetAddr> for TargetQuery {
fn from(t: TargetAddr) -> Self {
Self::Addr(t)
}
}
impl From<u64> for TargetQuery {
fn from(id: u64) -> Self {
Self::OvernetId(id)
}
}
struct TargetCollectionInner {
named: HashMap<String, Target>,
unnamed: Vec<Target>,
}
pub struct TargetCollection {
inner: RwLock<TargetCollectionInner>,
events: RwLock<Option<events::Queue<DaemonEvent>>>,
}
#[async_trait]
impl EventSynthesizer<DaemonEvent> for TargetCollection {
async fn synthesize_events(&self) -> Vec<DaemonEvent> {
let collection = self.inner.read().await;
// TODO(awdavies): This won't be accurate once a target is able to create
// more than one event at a time.
let mut res = Vec::with_capacity(collection.named.len());
for target in collection.named.values() {
res.extend(target.synthesize_events().await);
}
for target in collection.unnamed.iter() {
res.extend(target.synthesize_events().await);
}
res
}
}
impl TargetCollection {
pub fn new() -> Self {
Self {
inner: RwLock::new(TargetCollectionInner {
named: HashMap::new(),
unnamed: Vec::new(),
}),
events: RwLock::new(None),
}
}
pub async fn set_event_queue(&self, q: events::Queue<DaemonEvent>) {
// This should be the only place a write lock is ever held.
self.events
.write()
.then(move |mut e| {
*e = Some(q);
future::ready(())
})
.await;
}
pub async fn targets(&self) -> Vec<Target> {
let inner = self.inner.read().await;
inner.named.values().chain(inner.unnamed.iter()).cloned().collect()
}
pub async fn remove_target(&self, target_id: String) -> bool {
let mut inner = self.inner.write().await;
if inner.named.remove(&target_id).is_none() {
let mut found = None;
'unnamed: for (index, target) in inner.unnamed.iter().enumerate() {
for addr in target.addrs().await.into_iter() {
if format!("{}", addr.ip) == target_id {
found = Some(index);
break 'unnamed;
}
}
}
if let Some(found) = found {
inner.unnamed.remove(found);
true
} else {
let mut found = None;
'named: for (index, target) in inner.named.iter() {
for addr in target.addrs().await.into_iter() {
if format!("{}", addr.ip) == target_id {
found = Some(index.clone());
break 'named;
}
}
}
if let Some(found) = found {
inner.named.remove(&found);
true
} else {
false
}
}
} else {
true
}
}
pub async fn merge_insert(&self, t: Target) -> Target {
let mut inner = self.inner.write().await;
let mut unnamed_match = None;
'outer: for (i, to_update) in inner.unnamed.iter().enumerate() {
for addr in to_update.addrs().await.into_iter() {
for other in t.addrs().await.into_iter() {
if addr.ip == other.ip {
unnamed_match = Some(i);
break 'outer;
}
}
}
}
let to_update = if let Some(unnamed_match) = unnamed_match {
if let Some(name) = t.inner.nodename.lock().await.clone() {
let mergeable = inner.unnamed.remove(unnamed_match);
*mergeable.inner.nodename.lock().await = Some(name.clone());
inner.named.insert(name, mergeable);
None
} else {
Some(&inner.unnamed[unnamed_match])
}
} else {
None
};
let nodename = t.inner.nodename.lock().await;
// TODO(awdavies): better merging (using more indices for matching).
match to_update.or_else(|| nodename.as_ref().and_then(|name| inner.named.get(name))) {
Some(to_update) => {
log::trace!(
"attempting to merge info into target: {}",
t.inner.nodename_str().await
);
futures::join!(
to_update.update_last_response(t.last_response().await),
to_update.addrs_extend(t.addrs().await),
to_update.update_boot_timestamp(t.boot_timestamp_nanos().await),
to_update.overwrite_state(TargetState {
connection_state: std::mem::replace(
&mut t.inner.state.lock().await.connection_state,
ConnectionState::Disconnected
),
}),
);
to_update.events.push(TargetEvent::Rediscovered).await.unwrap_or_else(|err| {
log::warn!("unable to enqueue rediscovered event: {:#}", err)
});
to_update.clone()
}
None => {
std::mem::drop(nodename);
if let Some(name) = t.nodename().await.as_ref() {
inner.named.insert(name.to_owned(), t.clone());
log::info!("New target: {}", name);
if let Some(e) = self.events.read().await.as_ref() {
e.push(DaemonEvent::NewTarget(t.nodename().await)).await.unwrap_or_else(
|e| log::warn!("unable to push new target event: {}", e),
);
}
} else {
for to_return in inner.named.values() {
for addr in to_return.addrs().await.into_iter() {
for other in t.addrs().await.into_iter() {
if addr.ip == other.ip {
to_return.update_last_response(t.last_response().await).await;
return to_return.clone();
}
}
}
}
inner.unnamed.push(t.clone());
}
t
}
}
}
/// Attempts to get a target based off of the default. Returns an error if
/// there is no target available (this doesn't await a new target).
///
/// Ignores any targets that do not have the state of "connected" at the
/// time the command is invoked, so can cause some raciness.
pub async fn get_default(&self, n: Option<String>) -> Result<Target, DaemonError> {
// The "get the mapped targets for filtering connected ones" step has
// to be separate from the actual `filter_map()` statement on account of
// the compiler claiming a temporary value is borrowed whilst still in
// use. It is unclear why this needs to be done in two statements, but
// without it the compiler will complain.
let targets = &self.inner.read().await.named;
let targets = futures::future::join_all(
targets
.iter()
.map(|(nodename, t)| async move {
(
nodename,
t.clone(),
t.inner.state.lock().await.connection_state.is_connected(),
)
})
.collect::<Vec<_>>(),
)
.await;
let targets = targets
.iter()
.filter_map(|(_, t, connected)| if *connected { Some(t) } else { None })
.collect::<Vec<_>>();
match (targets.len(), n) {
(0, None) => Err(DaemonError::TargetCacheEmpty),
(1, None) => {
let res = targets
.iter()
.next()
.ok_or(DaemonError::TargetCacheEmpty)
.map(|t| (*t).clone())?;
log::debug!(
"No default target selected, returning only target - {:?}",
res.nodename().await,
);
Ok(res)
}
(_, None) => {
// n > 1 case (0 and 1 are covered, and this is an unsigned integer).
Err(DaemonError::TargetAmbiguous)
}
(_, Some(nodename)) => {
targets.iter().match_target(nodename).await.ok_or(DaemonError::TargetNotFound)
}
}
}
pub async fn get_connected<TQ>(&self, t: TQ) -> Option<Target>
where
TQ: Into<TargetQuery>,
{
let t: TargetQuery = t.into();
let inner = self.inner.read().await;
for target in inner.named.values().chain(inner.unnamed.iter()) {
if target.inner.state.lock().await.connection_state.is_connected() {
if t.matches(target).await {
return Some(target.clone());
}
}
}
None
}
pub async fn get<TQ>(&self, t: TQ) -> Option<Target>
where
TQ: Into<TargetQuery>,
{
let t: TargetQuery = t.into();
let inner = self.inner.read().await;
inner.named.values().chain(inner.unnamed.iter()).match_target(t).await
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::onet::create_ascendd,
chrono::offset::TimeZone,
fidl, fidl_fuchsia_developer_remotecontrol as rcs,
futures::{channel::mpsc, executor::block_on},
std::net::{Ipv4Addr, Ipv6Addr},
};
async fn clone_target(t: &Target) -> Target {
let inner = Arc::new(TargetInner::clone(&t.inner));
Target::from_inner(inner)
}
impl Clone for TargetInner {
fn clone(&self) -> Self {
Self {
nodename: Mutex::new(block_on(self.nodename.lock()).clone()),
last_response: RwLock::new(block_on(self.last_response.read()).clone()),
state: Mutex::new(block_on(self.state.lock()).clone()),
addrs: RwLock::new(block_on(self.addrs.read()).clone()),
serial: RwLock::new(block_on(self.serial.read()).clone()),
boot_timestamp_nanos: RwLock::new(
block_on(self.boot_timestamp_nanos.read()).clone(),
),
diagnostics_info: self.diagnostics_info.clone(),
ascendd: self.ascendd.clone(),
}
}
}
fn fake_now() -> DateTime<Utc> {
Utc.ymd(2014, 10, 31).and_hms(9, 10, 12)
}
fn fake_elapsed() -> DateTime<Utc> {
Utc.ymd(2014, 11, 2).and_hms(13, 2, 1)
}
impl PartialEq for Target {
fn eq(&self, o: &Target) -> bool {
block_on(self.nodename()) == block_on(o.nodename())
&& *block_on(self.inner.last_response.read())
== *block_on(o.inner.last_response.read())
&& block_on(self.addrs()) == block_on(o.addrs())
&& *block_on(self.inner.state.lock()) == *block_on(o.inner.state.lock())
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_insert_new_not_connected() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let tc = TargetCollection::new();
let nodename = String::from("what");
let t = Target::new_with_time(ascendd, &nodename, fake_now());
tc.merge_insert(clone_target(&t).await).await;
let other_target = &tc.get(nodename.clone()).await.unwrap();
assert_eq!(other_target, &t);
match tc.get_connected(nodename.clone()).await {
Some(_) => panic!("string lookup should return None"),
_ => (),
}
let now = Utc::now();
other_target
.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(now)
})
.await;
t.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(now)
})
.await;
assert_eq!(&tc.get_connected(nodename.clone()).await.unwrap(), &t);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_insert_new() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let tc = TargetCollection::new();
let nodename = String::from("what");
let t = Target::new_with_time(ascendd, &nodename, fake_now());
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(&tc.get(nodename.clone()).await.unwrap(), &t);
match tc.get("oihaoih").await {
Some(_) => panic!("string lookup should return None"),
_ => (),
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_merge() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let tc = TargetCollection::new();
let nodename = String::from("bananas");
let t1 = Target::new_with_time(ascendd.clone(), &nodename, fake_now());
let t2 = Target::new_with_time(ascendd.clone(), &nodename, fake_elapsed());
let a1 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
let a2 = IpAddr::V6(Ipv6Addr::new(
0xfe80, 0xcafe, 0xf00d, 0xf000, 0xb412, 0xb455, 0x1337, 0xfeed,
));
t1.addrs_insert((a1.clone(), 1).into()).await;
t2.addrs_insert((a2.clone(), 1).into()).await;
tc.merge_insert(clone_target(&t2).await).await;
tc.merge_insert(clone_target(&t1).await).await;
let merged_target = tc.get(nodename.clone()).await.unwrap();
assert_ne!(&merged_target, &t1);
assert_ne!(&merged_target, &t2);
assert_eq!(merged_target.addrs().await.len(), 2);
assert_eq!(*merged_target.inner.last_response.read().await, fake_elapsed());
assert!(merged_target.addrs().await.contains(&(a1, 1).into()));
assert!(merged_target.addrs().await.contains(&(a2, 1).into()));
// Insert another instance of the a2 address, but with a missing scope_id, and ensure that the scope is preserved from the
// pre-existing value in t1 a2.
let t3 = Target::new_with_time(ascendd.clone(), &nodename, fake_now());
t3.addrs_insert((a2.clone(), 0).into()).await;
tc.merge_insert(clone_target(&t3).await).await;
let merged_target = tc.get(nodename.clone()).await.unwrap();
assert_eq!(merged_target.addrs().await.iter().filter(|addr| addr.scope_id == 1).count(), 2);
// Insert another instance of the a2 address, but with a new scope_id, and ensure that the new scope is used.
let t3 = Target::new_with_time(ascendd.clone(), &nodename, fake_now());
t3.addrs_insert((a2.clone(), 3).into()).await;
tc.merge_insert(clone_target(&t3).await).await;
let merged_target = tc.get(nodename.clone()).await.unwrap();
assert_eq!(merged_target.addrs().await.iter().filter(|addr| addr.scope_id == 3).count(), 1);
}
fn setup_fake_remote_control_service(
send_internal_error: bool,
nodename_response: String,
) -> RemoteControlProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<RemoteControlMarker>().unwrap();
fuchsia_async::Task::spawn(async move {
while let Ok(Some(req)) = stream.try_next().await {
match req {
rcs::RemoteControlRequest::IdentifyHost { responder } => {
if send_internal_error {
let _ = responder
.send(&mut Err(rcs::IdentifyHostError::ListInterfacesFailed))
.context("sending testing error response")
.unwrap();
} else {
let result: Vec<Subnet> = vec![Subnet {
addr: IpAddress::Ipv4(Ipv4Address { addr: [192, 168, 0, 1] }),
prefix_len: 24,
}];
let nodename = if nodename_response.len() == 0 {
None
} else {
Some(nodename_response.clone())
};
responder
.send(&mut Ok(rcs::IdentifyHostResponse {
nodename,
addresses: Some(result),
..rcs::IdentifyHostResponse::EMPTY
}))
.context("sending testing response")
.unwrap();
}
}
_ => assert!(false),
}
}
})
.detach();
proxy
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_from_rcs_connection_internal_err() {
// TODO(awdavies): Do some form of PartialEq implementation for
// the RcsConnectionError enum to avoid the nested matches.
let ascendd = Arc::new(create_ascendd().await.unwrap());
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(true, "foo".to_owned()),
&NodeId { id: 123 },
);
match Target::from_rcs_connection(conn).await {
Ok(_) => assert!(false),
Err(e) => match e {
RcsConnectionError::RemoteControlError(rce) => match rce {
rcs::IdentifyHostError::ListInterfacesFailed => (),
_ => assert!(false),
},
_ => assert!(false),
},
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_from_rcs_connection_nodename_none() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(false, "".to_owned()),
&NodeId { id: 123456 },
);
match Target::from_rcs_connection(conn).await {
Ok(_) => assert!(false),
Err(e) => match e {
RcsConnectionError::TargetError(_) => (),
_ => assert!(false),
},
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_from_rcs_connection_no_err() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(false, "foo".to_owned()),
&NodeId { id: 1234 },
);
match Target::from_rcs_connection(conn).await {
Ok(t) => {
assert_eq!(t.nodename().await.unwrap(), "foo".to_string());
assert_eq!(t.rcs().await.unwrap().overnet_id.id, 1234u64);
assert_eq!(t.addrs().await.len(), 1);
}
Err(_) => assert!(false),
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_query_matches_nodename() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let query = TargetQuery::from("foo");
let target = Arc::new(Target::new(ascendd, "foo"));
assert!(query.matches(&target).await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_by_overnet_id() {
const ID: u64 = 12345;
let ascendd = Arc::new(create_ascendd().await.unwrap());
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(false, "foo".to_owned()),
&NodeId { id: ID },
);
let t = Target::from_rcs_connection(conn).await.unwrap();
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get(ID).await.unwrap(), t);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_by_addr() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let addr: TargetAddr = (IpAddr::from([192, 168, 0, 1]), 0).into();
let t = Target::new(ascendd.clone(), "foo");
t.addrs_insert(addr.clone()).await;
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get(addr).await.unwrap(), t);
assert_eq!(tc.get("192.168.0.1").await.unwrap(), t);
assert!(tc.get("fe80::dead:beef:beef:beef").await.is_none());
let addr: TargetAddr =
(IpAddr::from([0xfe80, 0x0, 0x0, 0x0, 0xdead, 0xbeef, 0xbeef, 0xbeef]), 3).into();
let t = Target::new(ascendd.clone(), "fooberdoober");
t.addrs_insert(addr.clone()).await;
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get("fe80::dead:beef:beef:beef").await.unwrap(), t);
assert_eq!(tc.get(addr.clone()).await.unwrap(), t);
assert_eq!(tc.get("fooberdoober").await.unwrap(), t);
}
// Most of this is now handled in `task.rs`
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_disconnect_multiple_invocations() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Arc::new(Target::new(ascendd, "flabbadoobiedoo"));
{
let addr: TargetAddr = (IpAddr::from([192, 168, 0, 1]), 0).into();
t.addrs_insert(addr).await;
}
// Assures multiple "simultaneous" invocations to start the target
// doesn't put it into a bad state that would hang.
let _: ((), (), ()) =
futures::join!(t.run_host_pipe(), t.run_host_pipe(), t.run_host_pipe());
}
struct RcsStateTest {
loop_started: bool,
rcs_is_some: bool,
expected: bridge::RemoteControlState,
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_rcs_states() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
for test in vec![
RcsStateTest {
loop_started: true,
rcs_is_some: false,
expected: bridge::RemoteControlState::Down,
},
RcsStateTest {
loop_started: true,
rcs_is_some: true,
expected: bridge::RemoteControlState::Up,
},
RcsStateTest {
loop_started: false,
rcs_is_some: true,
expected: bridge::RemoteControlState::Unknown,
},
RcsStateTest {
loop_started: false,
rcs_is_some: false,
expected: bridge::RemoteControlState::Unknown,
},
] {
let t = Target::new(ascendd.clone(), "schlabbadoo");
let a2 = IpAddr::V6(Ipv6Addr::new(
0xfe80, 0xcafe, 0xf00d, 0xf000, 0xb412, 0xb455, 0x1337, 0xfeed,
));
t.addrs_insert((a2, 2).into()).await;
if test.loop_started {
t.run_host_pipe().await;
}
{
*t.inner.state.lock().await = TargetState {
connection_state: if test.rcs_is_some {
ConnectionState::Rcs(RcsConnection::new_with_proxy(
ascendd.clone(),
setup_fake_remote_control_service(true, "foobiedoo".to_owned()),
&NodeId { id: 123 },
))
} else {
ConnectionState::Disconnected
},
};
}
assert_eq!(t.rcs_state().await, test.expected);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_to_fidl_target() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "cragdune-the-impaler");
let a1 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
let a2 = IpAddr::V6(Ipv6Addr::new(
0xfe80, 0xcafe, 0xf00d, 0xf000, 0xb412, 0xb455, 0x1337, 0xfeed,
));
t.addrs_insert((a1, 1).into()).await;
t.addrs_insert((a2, 1).into()).await;
let t_conv = t.clone().to_fidl_target().await;
assert_eq!(t.nodename().await.unwrap(), t_conv.nodename.unwrap().to_string());
let addrs = t.addrs().await;
let conv_addrs = t_conv.addresses.unwrap();
assert_eq!(addrs.len(), conv_addrs.len());
// Will crash if any addresses are missing.
for address in conv_addrs {
let address = TargetAddr::from(address);
assert!(addrs.iter().any(|&a| a == address));
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_event_synthesis() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd.clone(), "clopperdoop");
let vec = t.synthesize_events().await;
assert_eq!(vec.len(), 0);
t.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(Utc::now())
})
.await;
let vec = t.synthesize_events().await;
assert_eq!(vec.len(), 1);
assert_eq!(
vec.iter().next().expect("events empty"),
&DaemonEvent::NewTarget(Some("clopperdoop".to_string()))
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_event_synthesis_all_connected() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new_autoconnected(ascendd.clone(), "clam-chowder-is-tasty").await;
let t2 = Target::new_autoconnected(ascendd.clone(), "this-is-a-crunchy-falafel").await;
let t3 = Target::new_autoconnected(ascendd.clone(), "i-should-probably-eat-lunch").await;
let t4 = Target::new_autoconnected(ascendd.clone(), "i-should-probably-eat-lunch").await;
let tc = TargetCollection::new();
tc.merge_insert(t).await;
tc.merge_insert(t2).await;
tc.merge_insert(t3).await;
tc.merge_insert(t4).await;
let events = tc.synthesize_events().await;
assert_eq!(events.len(), 3);
assert!(events
.iter()
.any(|e| e == &DaemonEvent::NewTarget(Some("clam-chowder-is-tasty".to_string()))));
assert!(events
.iter()
.any(|e| e == &DaemonEvent::NewTarget(Some("this-is-a-crunchy-falafel".to_string()))));
assert!(
events
.iter()
.any(|e| e
== &DaemonEvent::NewTarget(Some("i-should-probably-eat-lunch".to_string())))
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_event_synthesis_none_connected() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd.clone(), "clam-chowder-is-tasty");
let t2 = Target::new(ascendd.clone(), "this-is-a-crunchy-falafel");
let t3 = Target::new(ascendd.clone(), "i-should-probably-eat-lunch");
let t4 = Target::new(ascendd.clone(), "i-should-probably-eat-lunch");
let tc = TargetCollection::new();
tc.merge_insert(t).await;
tc.merge_insert(t2).await;
tc.merge_insert(t3).await;
tc.merge_insert(t4).await;
let events = tc.synthesize_events().await;
assert_eq!(events.len(), 0);
}
struct EventPusher {
got: mpsc::UnboundedSender<String>,
}
impl EventPusher {
fn new() -> (Self, mpsc::UnboundedReceiver<String>) {
let (got, rx) = mpsc::unbounded::<String>();
(Self { got }, rx)
}
}
#[async_trait]
impl events::EventHandler<DaemonEvent> for EventPusher {
async fn on_event(&self, event: DaemonEvent) -> Result<bool> {
if let DaemonEvent::NewTarget(Some(s)) = event {
self.got.unbounded_send(s).unwrap();
Ok(false)
} else {
panic!("this should never receive any other kind of event");
}
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_collection_events() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd.clone(), "clam-chowder-is-tasty");
let t2 = Target::new(ascendd.clone(), "this-is-a-crunchy-falafel");
let t3 = Target::new(ascendd.clone(), "i-should-probably-eat-lunch");
let tc = Arc::new(TargetCollection::new());
let queue = events::Queue::new(&tc);
let (handler, rx) = EventPusher::new();
queue.add_handler(handler).await;
tc.set_event_queue(queue).await;
tc.merge_insert(t).await;
tc.merge_insert(t2).await;
tc.merge_insert(t3).await;
let results = rx.take(3).collect::<Vec<_>>().await;
assert!(results.iter().any(|e| e == &"clam-chowder-is-tasty".to_string()));
assert!(results.iter().any(|e| e == &"this-is-a-crunchy-falafel".to_string()));
assert!(results.iter().any(|e| e == &"i-should-probably-eat-lunch".to_string()));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_event_synthesis_wait() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(false, "foo".to_owned()),
&NodeId { id: 1234 },
);
let t = match Target::from_rcs_connection(conn).await {
Ok(t) => {
assert_eq!(t.nodename().await.unwrap(), "foo".to_string());
assert_eq!(t.rcs().await.unwrap().overnet_id.id, 1234u64);
assert_eq!(t.addrs().await.len(), 1);
t
}
Err(_) => unimplemented!("this branch should never happen"),
};
// This will hang forever if no synthesis happens.
t.events.wait_for(None, |e| e == TargetEvent::RcsActivated).await.unwrap();
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_event_fire() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd.clone(), "balaowihf");
let conn = RcsConnection::new_with_proxy(
ascendd,
setup_fake_remote_control_service(false, "balaowihf".to_owned()),
&NodeId { id: 1234 },
);
let fut = t.events.wait_for(None, |e| e == TargetEvent::RcsActivated);
let mut new_state = TargetState::default();
new_state.connection_state = ConnectionState::Rcs(conn);
// This is a bit of a race, so it's possible that state will be
// updated before the wait_for invocation is registered with the
// event queue, but either way this should succeed.
let (res, ()) = futures::join!(fut, t.overwrite_state(new_state));
res.unwrap();
}
#[test]
fn test_to_ssh_addr() {
let sockets = vec![
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)),
SocketAddr::V6(SocketAddrV6::new("f111::3".parse().unwrap(), 0, 0, 0)),
SocketAddr::V6(SocketAddrV6::new("fe80::1".parse().unwrap(), 0, 0, 0)),
SocketAddr::V6(SocketAddrV6::new("fe80::2".parse().unwrap(), 0, 0, 1)),
SocketAddr::V6(SocketAddrV6::new("fe80::3".parse().unwrap(), 0, 0, 0)),
];
let addrs = sockets.iter().map(|s| TargetAddr::from(*s)).collect::<Vec<_>>();
assert_eq!((&addrs).to_ssh_addr(), Some(addrs[3]));
let sockets = vec![
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)),
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(129, 0, 0, 1), 0)),
];
let addrs = sockets.iter().map(|s| TargetAddr::from(*s)).collect::<Vec<_>>();
assert_eq!((&addrs).to_ssh_addr(), Some(addrs[0]));
let addrs = Vec::<TargetAddr>::new();
assert_eq!((&addrs).to_ssh_addr(), None);
}
#[test]
fn test_ssh_formatting() {
struct SshFormatTest {
addr: TargetAddr,
expect: &'static str,
}
let tests_pre = vec![
(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)), "127.0.0.1"),
(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(129, 0, 0, 1), 0)), "129.0.0.1"),
(SocketAddr::V6(SocketAddrV6::new("f111::3".parse().unwrap(), 0, 0, 0)), "[f111::3]"),
(SocketAddr::V6(SocketAddrV6::new("fe80::1".parse().unwrap(), 0, 0, 0)), "[fe80::1]"),
(
SocketAddr::V6(SocketAddrV6::new("fe80::2".parse().unwrap(), 0, 0, 198)),
"[fe80::2%198]",
),
];
let tests = tests_pre
.iter()
.map(|t| SshFormatTest { addr: TargetAddr::from(t.0), expect: t.1 })
.collect::<Vec<_>>();
for test in tests.iter() {
let mut res = Vec::<u8>::new();
test.addr.ssh_fmt(&mut res).unwrap();
assert_eq!(std::str::from_utf8(&res[..]).unwrap(), test.expect);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let default = "clam-chowder-is-tasty";
let t = Target::new_autoconnected(ascendd.clone(), default).await;
let t2 = Target::new_autoconnected(ascendd.clone(), "this-is-a-crunchy-falafel").await;
let tc = TargetCollection::new();
assert!(tc.get_default(None).await.is_err());
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get_default(Some(default.to_string())).await.unwrap(), t);
assert_eq!(tc.get_default(None).await.unwrap(), t);
tc.merge_insert(t2).await;
assert_eq!(tc.get_default(Some(default.to_string())).await.unwrap(), t);
assert!(tc.get_default(None).await.is_err());
assert!(tc.get_default(Some("not_in_here".to_owned())).await.is_err());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default_matches_contains() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let default = "clam-chowder-is-tasty";
let t = Target::new_autoconnected(ascendd.clone(), default).await;
let t2 = Target::new_autoconnected(ascendd.clone(), "this-is-a-crunchy-falafel").await;
let tc = TargetCollection::new();
assert!(tc.get_default(None).await.is_err());
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get_default(Some(default.to_string())).await.unwrap(), t);
assert_eq!(tc.get_default(None).await.unwrap(), t);
tc.merge_insert(t2).await;
assert_eq!(tc.get_default(Some(default.to_string())).await.unwrap(), t);
assert!(tc.get_default(None).await.is_err());
assert!(tc.get_default(Some("not_in_here".to_owned())).await.is_err());
assert_eq!(tc.get_default(Some("clam".to_string())).await.unwrap(), t);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_update_connection_state() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "have-you-seen-my-cat");
let fake_time = Utc.yo(2017, 12).and_hms(1, 2, 3);
let fake_time_clone = fake_time.clone();
t.update_connection_state(move |s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(fake_time_clone)
})
.await;
assert_eq!(ConnectionState::Mdns(fake_time), t.inner.state.lock().await.connection_state);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_mdns_set_disconnected() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "yo-yo-ma-plays-that-cello-ya-hear");
let now = Utc::now();
t.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Mdns(now)
})
.await;
let events = t.events.clone();
let _task = fuchsia_async::Task::local(async move {
Target::mdns_monitor_loop(t.downgrade(), Duration::from_secs(2))
.await
.expect("mdns monitor loop failed")
});
events
.wait_for(None, move |e| {
e == TargetEvent::ConnectionStateChanged(
ConnectionState::Mdns(now),
ConnectionState::Disconnected,
)
})
.await
.unwrap();
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_fastboot_set_disconnected() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "platypodes-are-venomous");
let now = Utc::now();
t.update_connection_state(|s| {
assert_eq!(s, ConnectionState::Disconnected);
ConnectionState::Fastboot(now)
})
.await;
let events = t.events.clone();
let _task = fuchsia_async::Task::local(async move {
Target::fastboot_monitor_loop(t.downgrade(), Duration::from_secs(2))
.await
.expect("mdns monitor loop failed")
});
events
.wait_for(None, move |e| {
e == TargetEvent::ConnectionStateChanged(
ConnectionState::Fastboot(now),
ConnectionState::Disconnected,
)
})
.await
.unwrap();
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_addresses_order_preserved() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "this-is-a-target-i-guess");
let addrs_pre = vec![
SocketAddr::V6(SocketAddrV6::new("fe80::1".parse().unwrap(), 0, 0, 0)),
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)),
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(129, 0, 0, 1), 0)),
SocketAddr::V6(SocketAddrV6::new("f111::3".parse().unwrap(), 0, 0, 0)),
SocketAddr::V6(SocketAddrV6::new("fe80::1".parse().unwrap(), 0, 0, 0)),
SocketAddr::V6(SocketAddrV6::new("fe80::2".parse().unwrap(), 0, 0, 2)),
];
let mut addrs_post = addrs_pre
.iter()
.cloned()
.enumerate()
.map(|(i, e)| {
(TargetAddr::from(e), Utc.ymd(2014 + (i as i32), 10, 31).and_hms(9, 10, 12)).into()
})
.collect::<Vec<TargetAddrEntry>>();
for a in addrs_post.iter().cloned() {
t.addrs_insert_entry(a).await;
}
// Removes expected duplicate address. Should be marked as a duplicate
// and also removed from the very beginning as a more-recent version
// is added later.
addrs_post.remove(0);
// The order should be: last one inserted should show up first.
addrs_post.reverse();
assert_eq!(addrs_post.drain(..).map(|e| e.addr).collect::<Vec<_>>(), t.addrs().await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_addresses_order() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "hi-hi-hi");
let expected = SocketAddr::V6(SocketAddrV6::new(
"fe80::4559:49b2:462d:f46b".parse().unwrap(),
0,
0,
8,
));
let addrs_pre = vec![
expected.clone(),
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 70, 68), 0)),
];
let addrs_post = addrs_pre
.iter()
.cloned()
.enumerate()
.map(|(i, e)| {
(TargetAddr::from(e), Utc.ymd(2014 + (i as i32), 10, 31).and_hms(9, 10, 12)).into()
})
.collect::<Vec<TargetAddrEntry>>();
for a in addrs_post.iter().cloned() {
t.addrs_insert_entry(a).await;
}
assert_eq!((&t.addrs().await).to_ssh_addr().unwrap(), TargetAddr::from(expected));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_addresses_prefer_local_vs_v6() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new(ascendd, "hi-hi-hi");
let expected = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 70, 68), 0));
let addrs_pre = vec![
expected.clone(),
SocketAddr::V6(SocketAddrV6::new(
"9999::4559:49b2:462d:f46b".parse().unwrap(),
0,
0,
0,
)),
];
let addrs_post = addrs_pre
.iter()
.cloned()
.enumerate()
.map(|(i, e)| {
(TargetAddr::from(e), Utc.ymd(2014 + (i as i32), 10, 31).and_hms(9, 10, 12)).into()
})
.collect::<Vec<TargetAddrEntry>>();
for a in addrs_post.iter().cloned() {
t.addrs_insert_entry(a).await;
}
assert_eq!((&t.addrs().await).to_ssh_addr().unwrap(), TargetAddr::from(expected));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_merge_no_name() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let ip = "f111::3".parse().unwrap();
let mut addr_set = BTreeSet::new();
addr_set.replace(TargetAddr { ip, scope_id: 0xbadf00d });
let t1 = Target::new_with_addrs(ascendd.clone(), None, addr_set);
let t2 = Target::new(ascendd, "this-is-a-crunchy-falafel");
let tc = TargetCollection::new();
t2.inner.addrs.write().await.replace(TargetAddr { ip, scope_id: 0 }.into());
tc.merge_insert(t1).await;
tc.merge_insert(t2).await;
let mut targets = tc.targets().await.into_iter();
let target = targets.next().expect("Merging resulted in no targets.");
assert!(targets.next().is_none());
assert_eq!(target.nodename_str().await, "this-is-a-crunchy-falafel");
let mut addrs = target.addrs().await.into_iter();
let addr = addrs.next().expect("Merged target has no address.");
assert!(addrs.next().is_none());
assert_eq!(addr.ip, ip);
assert_eq!(addr.scope_id, 0xbadf00d);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default_empty() {
let tc = TargetCollection::new();
assert_eq!(Err(DaemonError::TargetCacheEmpty), tc.get_default(None).await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default_successful() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let default = "clam-chowder-is-tasty";
let t = Target::new_autoconnected(ascendd.clone(), default).await;
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(tc.get_default(Some(default.to_string())).await.unwrap(), t);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default_ambiguous() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let default = "clam-chowder-is-tasty";
let t = Target::new_autoconnected(ascendd.clone(), default).await;
let t2 = Target::new_autoconnected(ascendd.clone(), "this-is-a-crunchy-falafel").await;
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
tc.merge_insert(t2).await;
assert_eq!(Err(DaemonError::TargetAmbiguous), tc.get_default(None).await);
assert_eq!(
Err(DaemonError::TargetNotFound),
tc.get_default(Some("not_in_here".to_owned())).await
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_get_default_not_found() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let default = "clam-chowder-is-tasty";
let t = Target::new_autoconnected(ascendd.clone(), default).await;
let t2 = Target::new_autoconnected(ascendd.clone(), "this-is-a-crunchy-falafel").await;
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
tc.merge_insert(t2).await;
assert_eq!(
Err(DaemonError::TargetNotFound),
tc.get_default(Some("not_in_here".to_owned())).await
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_remove_unnamed_by_addr() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let ip1 = "f111::3".parse().unwrap();
let ip2 = "f111::4".parse().unwrap();
let mut addr_set = BTreeSet::new();
addr_set.replace(TargetAddr { ip: ip1, scope_id: 0xbadf00d });
let t1 = Target::new_with_addrs(ascendd.clone(), None, addr_set);
let t2 = Target::new(ascendd, "this-is-a-crunchy-falafel");
let tc = TargetCollection::new();
t2.inner.addrs.write().await.replace(TargetAddr { ip: ip2, scope_id: 0 }.into());
tc.merge_insert(t1).await;
tc.merge_insert(t2).await;
let mut targets = tc.targets().await.into_iter();
let target1 = targets.next().expect("Merging resulted in no targets.");
let target2 = targets.next().expect("Merging resulted in only one target.");
assert!(targets.next().is_none());
assert_eq!(target1.nodename_str().await, "this-is-a-crunchy-falafel");
assert_eq!(target2.nodename().await, None);
assert!(tc.remove_target("f111::3".to_owned()).await);
let mut targets = tc.targets().await.into_iter();
let target = targets.next().expect("Merging resulted in no targets.");
assert!(targets.next().is_none());
assert_eq!(target.nodename_str().await, "this-is-a-crunchy-falafel");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_remove_named_by_addr() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let ip1 = "f111::3".parse().unwrap();
let ip2 = "f111::4".parse().unwrap();
let mut addr_set = BTreeSet::new();
addr_set.replace(TargetAddr { ip: ip1, scope_id: 0xbadf00d });
let t1 = Target::new_with_addrs(ascendd.clone(), None, addr_set);
let t2 = Target::new(ascendd, "this-is-a-crunchy-falafel");
let tc = TargetCollection::new();
t2.inner.addrs.write().await.replace(TargetAddr { ip: ip2, scope_id: 0 }.into());
tc.merge_insert(t1).await;
tc.merge_insert(t2).await;
let mut targets = tc.targets().await.into_iter();
let target1 = targets.next().expect("Merging resulted in no targets.");
let target2 = targets.next().expect("Merging resulted in only one target.");
assert!(targets.next().is_none());
assert_eq!(target1.nodename_str().await, "this-is-a-crunchy-falafel");
assert_eq!(target2.nodename().await, None);
assert!(tc.remove_target("f111::4".to_owned()).await);
let mut targets = tc.targets().await.into_iter();
let target = targets.next().expect("Merging resulted in no targets.");
assert!(targets.next().is_none());
assert_eq!(target.nodename().await, None);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_remove_by_name() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let ip1 = "f111::3".parse().unwrap();
let ip2 = "f111::4".parse().unwrap();
let mut addr_set = BTreeSet::new();
addr_set.replace(TargetAddr { ip: ip1, scope_id: 0xbadf00d });
let t1 = Target::new_with_addrs(ascendd.clone(), None, addr_set);
let t2 = Target::new(ascendd, "this-is-a-crunchy-falafel");
let tc = TargetCollection::new();
t2.inner.addrs.write().await.replace(TargetAddr { ip: ip2, scope_id: 0 }.into());
tc.merge_insert(t1).await;
tc.merge_insert(t2).await;
let mut targets = tc.targets().await.into_iter();
let target1 = targets.next().expect("Merging resulted in no targets.");
let target2 = targets.next().expect("Merging resulted in only one target.");
assert!(targets.next().is_none());
assert_eq!(target1.nodename_str().await, "this-is-a-crunchy-falafel");
assert_eq!(target2.nodename().await, None);
assert!(tc.remove_target("this-is-a-crunchy-falafel".to_owned()).await);
let mut targets = tc.targets().await.into_iter();
let target = targets.next().expect("Merging resulted in no targets.");
assert!(targets.next().is_none());
assert_eq!(target.nodename().await, None);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_target_match_serial() {
let ascendd = Arc::new(create_ascendd().await.unwrap());
let t = Target::new_with_serial(
ascendd.clone(),
"turritopsis-dohrnii-is-an-immortal-jellyfish",
"florp",
);
let tc = TargetCollection::new();
tc.merge_insert(clone_target(&t).await).await;
assert_eq!(
t.nodename().await.unwrap(),
tc.get("flor").await.unwrap().nodename().await.unwrap()
);
}
}