use fidl::endpoints::ProtocolMarker as _;
use fidl::{HandleBased, Rights};
use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
use thiserror::Error;
use {
fidl_fuchsia_net_interfaces as fnet_interfaces,
fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
};
#[derive(Error, Debug)]
pub enum AddressStateProviderError {
#[error("address removed: {0:?}")]
AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
#[error("fidl error")]
Fidl(#[from] fidl::Error),
#[error("AddressStateProvider channel closed")]
ChannelClosed,
}
impl From<TerminalError<fnet_interfaces_admin::AddressRemovalReason>>
for AddressStateProviderError
{
fn from(e: TerminalError<fnet_interfaces_admin::AddressRemovalReason>) -> Self {
match e {
TerminalError::Fidl(e) => AddressStateProviderError::Fidl(e),
TerminalError::Terminal(r) => AddressStateProviderError::AddressRemoved(r),
}
}
}
pub async fn wait_for_address_added_event(
event_stream: &mut fnet_interfaces_admin::AddressStateProviderEventStream,
) -> Result<(), AddressStateProviderError> {
let event = event_stream
.next()
.await
.ok_or(AddressStateProviderError::ChannelClosed)?
.map_err(AddressStateProviderError::Fidl)?;
match event {
fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(()),
fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
Err(AddressStateProviderError::AddressRemoved(error))
}
}
}
pub fn assignment_state_stream(
address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
) -> impl Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
{
let event_fut = address_state_provider
.take_event_stream()
.filter_map(|event| {
futures::future::ready(match event {
Ok(event) => match event {
fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => None,
fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
error,
} => Some(AddressStateProviderError::AddressRemoved(error)),
},
Err(e) => Some(AddressStateProviderError::Fidl(e)),
})
})
.into_future()
.map(|(event, _stream)| event.unwrap_or(AddressStateProviderError::ChannelClosed));
futures::stream::try_unfold(
(address_state_provider, event_fut),
|(address_state_provider, event_fut)| {
futures::future::select(
address_state_provider.watch_address_assignment_state(),
event_fut,
)
.then(|s| match s {
futures::future::Either::Left((state_result, event_fut)) => match state_result {
Ok(state) => {
futures::future::ok(Some((state, (address_state_provider, event_fut))))
.left_future()
}
Err(e) if e.is_closed() => event_fut.map(Result::Err).right_future(),
Err(e) => {
futures::future::err(AddressStateProviderError::Fidl(e)).left_future()
}
},
futures::future::Either::Right((error, _state_fut)) => {
futures::future::err(error).left_future()
}
})
},
)
}
pub async fn wait_assignment_state<S>(
stream: S,
want: fnet_interfaces::AddressAssignmentState,
) -> Result<(), AddressStateProviderError>
where
S: Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
+ Unpin,
{
stream
.try_filter_map(|state| futures::future::ok((state == want).then(|| ())))
.try_next()
.await
.and_then(|opt| opt.ok_or_else(|| AddressStateProviderError::ChannelClosed))
}
type ControlEventStreamFutureToReason =
fn(
(
Option<Result<fnet_interfaces_admin::ControlEvent, fidl::Error>>,
fnet_interfaces_admin::ControlEventStream,
),
) -> Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>;
pub fn proof_from_grant(
grant: &fnet_interfaces_admin::GrantForInterfaceAuthorization,
) -> fnet_interfaces_admin::ProofOfInterfaceAuthorization {
let fnet_interfaces_admin::GrantForInterfaceAuthorization { interface_id, token } = grant;
fnet_interfaces_admin::ProofOfInterfaceAuthorization {
interface_id: *interface_id,
token: token.duplicate_handle(Rights::TRANSFER).unwrap(),
}
}
#[derive(Clone)]
pub struct Control {
proxy: fnet_interfaces_admin::ControlProxy,
terminal_event_fut: futures::future::Shared<
futures::future::Map<
futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
ControlEventStreamFutureToReason,
>,
>,
}
async fn or_terminal_event<QR, QF, TR>(
query_fut: QF,
terminal_event_fut: TR,
) -> Result<QR, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>
where
QR: Unpin,
QF: Unpin + Future<Output = Result<QR, fidl::Error>>,
TR: Unpin
+ Future<Output = Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>>,
{
match futures::future::select(query_fut, terminal_event_fut).await {
futures::future::Either::Left((query_result, terminal_event_fut)) => match query_result {
Ok(ok) => Ok(ok),
Err(e) if e.is_closed() => match terminal_event_fut.await {
Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
},
Err(e) => Err(TerminalError::Fidl(e)),
},
futures::future::Either::Right((event, query_fut)) => {
if let Some(query_result) = query_fut.now_or_never() {
match query_result {
Ok(ok) => Ok(ok),
Err(e) if e.is_closed() => match event {
Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
},
Err(e) => Err(TerminalError::Fidl(e)),
}
} else {
match event.map_err(|e| TerminalError::Fidl(e))? {
Some(removal_reason) => Err(TerminalError::Terminal(removal_reason)),
None => Err(TerminalError::Fidl(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
})),
}
}
}
}
}
impl Control {
pub fn add_address(
&self,
address: &fidl_fuchsia_net::Subnet,
parameters: &fnet_interfaces_admin::AddressParameters,
address_state_provider: fidl::endpoints::ServerEnd<
fnet_interfaces_admin::AddressStateProviderMarker,
>,
) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
self.or_terminal_event_no_return(self.proxy.add_address(
address,
parameters,
address_state_provider,
))
}
pub async fn get_id(
&self,
) -> Result<u64, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
self.or_terminal_event(self.proxy.get_id()).await
}
pub async fn remove_address(
&self,
address: &fidl_fuchsia_net::Subnet,
) -> Result<
fnet_interfaces_admin::ControlRemoveAddressResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.remove_address(address)).await
}
pub async fn set_configuration(
&self,
config: &fnet_interfaces_admin::Configuration,
) -> Result<
fnet_interfaces_admin::ControlSetConfigurationResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.set_configuration(config)).await
}
pub async fn get_configuration(
&self,
) -> Result<
fnet_interfaces_admin::ControlGetConfigurationResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.get_configuration()).await
}
pub async fn get_authorization_for_interface(
&self,
) -> Result<
fnet_interfaces_admin::GrantForInterfaceAuthorization,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.get_authorization_for_interface()).await
}
pub async fn enable(
&self,
) -> Result<
fnet_interfaces_admin::ControlEnableResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.enable()).await
}
pub async fn remove(
&self,
) -> Result<
fnet_interfaces_admin::ControlRemoveResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.remove()).await
}
pub async fn disable(
&self,
) -> Result<
fnet_interfaces_admin::ControlDisableResult,
TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
> {
self.or_terminal_event(self.proxy.disable()).await
}
pub fn detach(
&self,
) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
self.or_terminal_event_no_return(self.proxy.detach())
}
pub fn new(proxy: fnet_interfaces_admin::ControlProxy) -> Self {
let terminal_event_fut = proxy
.take_event_stream()
.into_future()
.map::<_, ControlEventStreamFutureToReason>(|(event, _stream)| {
event
.map(|r| {
r.map(
|fnet_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
reason
},
)
})
.transpose()
})
.shared();
Self { proxy, terminal_event_fut }
}
pub async fn wait_termination(
self,
) -> TerminalError<fnet_interfaces_admin::InterfaceRemovedReason> {
let Self { proxy: _, terminal_event_fut } = self;
match terminal_event_fut.await {
Ok(Some(event)) => TerminalError::Terminal(event),
Ok(None) => TerminalError::Fidl(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}),
Err(e) => TerminalError::Fidl(e),
}
}
pub fn create_endpoints(
) -> Result<(Self, fidl::endpoints::ServerEnd<fnet_interfaces_admin::ControlMarker>), fidl::Error>
{
let (proxy, server_end) = fidl::endpoints::create_proxy();
Ok((Self::new(proxy), server_end))
}
async fn or_terminal_event<R: Unpin, F: Unpin + Future<Output = Result<R, fidl::Error>>>(
&self,
fut: F,
) -> Result<R, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
or_terminal_event(fut, self.terminal_event_fut.clone()).await
}
fn or_terminal_event_no_return(
&self,
r: Result<(), fidl::Error>,
) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
r.map_err(|err| {
if !err.is_closed() {
return TerminalError::Fidl(err);
}
match self.terminal_event_fut.clone().now_or_never() {
Some(Ok(Some(terminal_event))) => TerminalError::Terminal(terminal_event),
Some(Err(e)) => {
let _: fidl::Error = e;
TerminalError::Fidl(err)
}
None | Some(Ok(None)) => TerminalError::Fidl(err),
}
})
}
}
impl std::fmt::Debug for Control {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { proxy, terminal_event_fut: _ } = self;
fmt.debug_struct("Control").field("proxy", proxy).finish()
}
}
#[derive(Debug)]
pub enum TerminalError<E> {
Terminal(E),
Fidl(fidl::Error),
}
impl<E> std::fmt::Display for TerminalError<E>
where
E: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TerminalError::Terminal(e) => write!(f, "terminal event: {:?}", e),
TerminalError::Fidl(e) => write!(f, "fidl error: {}", e),
}
}
}
impl<E: std::fmt::Debug> std::error::Error for TerminalError<E> {}
#[cfg(test)]
mod test {
use std::task::Poll;
use super::{
assignment_state_stream, or_terminal_event, proof_from_grant, AddressStateProviderError,
TerminalError,
};
use assert_matches::assert_matches;
use fidl::prelude::*;
use fidl::Rights;
use fnet_interfaces_admin::InterfaceRemovedReason;
use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
use test_case::test_case;
use {
fidl_fuchsia_net_interfaces as fnet_interfaces,
fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
};
#[fuchsia_async::run_singlethreaded(test)]
async fn test_assignment_state_stream() {
let (address_state_provider, server_end) =
fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
let state_stream = assignment_state_stream(address_state_provider);
futures::pin_mut!(state_stream);
const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
fnet_interfaces_admin::AddressRemovalReason::Invalid;
{
let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
fnet_interfaces::AddressAssignmentState::Assigned;
let state_fut = state_stream.try_next().map(|r| {
assert_eq!(
r.expect("state stream error").expect("state stream ended"),
ASSIGNMENT_STATE_ASSIGNED
)
});
let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
let () = responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
}
req => panic!("unexpected method called: {:?}", req),
});
let ((), ()) = futures::join!(state_fut, handle_fut);
let () = control_handle
.send_on_address_removed(REMOVAL_REASON_INVALID)
.expect("failed to send fake INVALID address removal reason event");
}
assert_matches::assert_matches!(
state_stream.try_collect::<Vec<_>>().await,
Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_assignment_state_stream_single_error() {
let (address_state_provider, server_end) =
fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
let state_stream = assignment_state_stream(address_state_provider);
let () = server_end
.close_with_epitaph(fidl::Status::INTERNAL)
.expect("failed to send INTERNAL epitaph");
assert_matches::assert_matches!(
state_stream
.collect::<Vec<_>>()
.now_or_never()
.expect("state stream not immediately ready")
.as_slice(),
[Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
status: fidl::Status::INTERNAL,
protocol_name: _,
#[cfg(not(target_os = "fuchsia"))]
reason: None
}))]
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn assignment_state_stream_state_before_event() {
let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
fnet_interfaces_admin::AddressStateProviderMarker,
>();
const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
fnet_interfaces::AddressAssignmentState::Assigned;
const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
fnet_interfaces_admin::AddressRemovalReason::Invalid;
let ((), ()) = futures::future::join(
async move {
let () = request_stream
.try_next()
.await
.expect("request stream error")
.expect("request stream ended")
.into_watch_address_assignment_state()
.expect("unexpected request")
.send(ASSIGNMENT_STATE_ASSIGNED)
.expect("failed to send stubbed assignment state");
let () = request_stream
.control_handle()
.send_on_address_removed(REMOVAL_REASON_INVALID)
.expect("failed to send fake INVALID address removal reason event");
},
async move {
let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
assert_matches::assert_matches!(
got.as_slice(),
&[
Ok(got_state),
Err(AddressStateProviderError::AddressRemoved(got_reason)),
] => {
assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
assert_eq!(got_reason, REMOVAL_REASON_INVALID);
}
);
},
)
.await;
}
#[fuchsia_async::run_singlethreaded(test)]
async fn control_terminal_event() {
let (control, mut request_stream) =
fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
let control = super::Control::new(control);
const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
const ID: u64 = 15;
let ((), ()) = futures::future::join(
async move {
assert_matches::assert_matches!(control.get_id().await, Ok(ID));
assert_matches::assert_matches!(
control.get_id().await,
Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
);
},
async move {
let responder = request_stream
.try_next()
.await
.expect("operating request stream")
.expect("stream ended unexpectedly")
.into_get_id()
.expect("unexpected request");
let () = responder.send(ID).expect("failed to send response");
let () = request_stream
.control_handle()
.send_on_interface_removed(EXPECTED_EVENT)
.expect("sending terminal event");
},
)
.await;
}
#[fuchsia_async::run_singlethreaded(test)]
async fn control_missing_terminal_event() {
let (control, mut request_stream) =
fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
let control = super::Control::new(control);
let ((), ()) = futures::future::join(
async move {
assert_matches::assert_matches!(
control.get_id().await,
Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None
}))
);
},
async move {
match request_stream
.try_next()
.await
.expect("operating request stream")
.expect("stream ended unexpectedly")
{
fnet_interfaces_admin::ControlRequest::GetId { responder } => {
std::mem::drop(responder);
}
request => panic!("unexpected request {:?}", request),
}
},
)
.await;
}
#[fuchsia_async::run_singlethreaded(test)]
async fn control_pipelined_error() {
let (control, request_stream) =
fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
let control = super::Control::new(control);
const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
let () = request_stream
.control_handle()
.send_on_interface_removed(CLOSE_REASON)
.expect("send terminal event");
std::mem::drop(request_stream);
assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
assert_matches::assert_matches!(
control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
zx::Status::INTERNAL.into()
))),
Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
fidl::TransportError::Status(zx::Status::INTERNAL)
)))
);
#[cfg(target_os = "fuchsia")]
assert_matches::assert_matches!(
control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
})),
Err(super::TerminalError::Terminal(CLOSE_REASON))
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn control_wait_termination() {
let (control, request_stream) =
fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
let control = super::Control::new(control);
const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
let () = request_stream
.control_handle()
.send_on_interface_removed(CLOSE_REASON)
.expect("send terminal event");
std::mem::drop(request_stream);
assert_matches::assert_matches!(
control.wait_termination().await,
super::TerminalError::Terminal(CLOSE_REASON)
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn control_respond_and_drop() {
const ID: u64 = 15;
let (control, mut request_stream) =
fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
let control = super::Control::new(control);
let ((), ()) = futures::future::join(
async move {
assert_matches::assert_matches!(control.get_id().await, Ok(ID));
},
async move {
let responder = request_stream
.try_next()
.await
.expect("operating request stream")
.expect("stream ended unexpectedly")
.into_get_id()
.expect("unexpected request");
let () = responder.send(ID).expect("failed to send response");
},
)
.await;
}
#[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
#[test_case(
Err(fidl::Error::InvalidHeader),
Ok(Some(InterfaceRemovedReason::User)),
Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
"returns query error when not closed"
)]
#[test_case(
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}),
Ok(Some(InterfaceRemovedReason::User)),
Err(TerminalError::Terminal(InterfaceRemovedReason::User));
"returns terminal error when channel closed"
)]
#[test_case(
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}),
Ok(None),
Err(TerminalError::Fidl(
fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}
));
"returns query error when no terminal error"
)]
#[test_case(
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}),
Err(fidl::Error::InvalidHeader),
Err(TerminalError::Fidl(
fidl::Error::ClientChannelClosed {
status: zx::Status::PEER_CLOSED,
protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
#[cfg(not(target_os = "fuchsia"))]
reason: None,
}
));
"returns query error when terminal event returns a fidl error"
)]
#[fuchsia_async::run_singlethreaded(test)]
async fn control_polling_race(
left_future_result: Result<(), fidl::Error>,
right_future_result: Result<
Option<fnet_interfaces_admin::InterfaceRemovedReason>,
fidl::Error,
>,
expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
) {
let mut polled = false;
let first_future = std::future::poll_fn(|_cx| {
if polled {
Poll::Ready(left_future_result.clone())
} else {
polled = true;
Poll::Pending
}
})
.fuse();
let second_future =
std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
let res = or_terminal_event(first_future, second_future).await;
match (res, expected) {
(Ok(()), Ok(())) => (),
(Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
if res == expected => {}
(Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
(res, expected) => panic!("expected {:?} got {:?}", expected, res),
}
}
#[test]
fn convert_proof_to_grant() {
let event = fidl::Event::create();
let grant = fnet_interfaces_admin::GrantForInterfaceAuthorization {
interface_id: Default::default(),
token: event,
};
let fnet_interfaces_admin::ProofOfInterfaceAuthorization { interface_id, token } =
proof_from_grant(&grant);
assert_eq!(interface_id, Default::default());
assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
}
}