use crate::FidlRouteIpExt;
use fidl_fuchsia_net_routes as fnet_routes;
use futures::{Future, Stream, StreamExt as _};
use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
pub fn handle_watch<I: FidlRouteIpExt>(
request: <<I::WatcherMarker as fidl::endpoints::ProtocolMarker>::RequestStream as Stream>::Item,
event_batch: Vec<I::WatchEvent>,
) {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct HandleInputs<I: FidlRouteIpExt> {
request:
<<I::WatcherMarker as fidl::endpoints::ProtocolMarker>::RequestStream as Stream>::Item,
event_batch: Vec<I::WatchEvent>,
}
I::map_ip_in(
HandleInputs { request, event_batch },
|HandleInputs { request, event_batch }| match request
.expect("failed to receive `Watch` request")
{
fnet_routes::WatcherV4Request::Watch { responder } => {
responder.send(&event_batch).expect("failed to respond to `Watch`")
}
},
|HandleInputs { request, event_batch }| match request
.expect("failed to receive `Watch` request")
{
fnet_routes::WatcherV6Request::Watch { responder } => {
responder.send(&event_batch).expect("failed to respond to `Watch`")
}
},
);
}
pub async fn fake_watcher_impl<I: FidlRouteIpExt>(
events: impl Stream<Item = Vec<I::WatchEvent>>,
server_end: fidl::endpoints::ServerEnd<I::WatcherMarker>,
) {
let (request_stream, _control_handle) = server_end.into_stream_and_control_handle();
request_stream
.zip(events)
.for_each(|(request, event_batch)| {
handle_watch::<I>(request, event_batch);
futures::future::ready(())
})
.await
}
pub async fn serve_state_request<'a, I: FidlRouteIpExt>(
request: <<I::StateMarker as fidl::endpoints::ProtocolMarker>::RequestStream as Stream>::Item,
event_stream: impl Stream<Item = Vec<I::WatchEvent>> + 'a,
) {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct GetWatcherInputs<'a, I: FidlRouteIpExt> {
request:
<<I::StateMarker as fidl::endpoints::ProtocolMarker>::RequestStream as Stream>::Item,
event_stream: Box<dyn Stream<Item = Vec<I::WatchEvent>> + 'a>,
}
I::map_ip_in::<
_,
std::pin::Pin<Box<dyn Future<Output = ()> + 'a>>,
>(
GetWatcherInputs { request, event_stream: Box::new(event_stream) },
|GetWatcherInputs { request, event_stream }| match request
.expect("failed to receive `GetWatcherV4` request")
{
fnet_routes::StateV4Request::GetWatcherV4 {
options: _,
watcher,
control_handle: _,
} => Box::pin(fake_watcher_impl::<Ipv4>(Box::into_pin(event_stream), watcher)),
fnet_routes::StateV4Request::GetRuleWatcherV4 {
options: _,
watcher: _,
control_handle: _,
} => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
},
|GetWatcherInputs { request, event_stream }| match request
.expect("failed to receive `GetWatcherV6` request")
{
fnet_routes::StateV6Request::GetWatcherV6 {
options: _,
watcher,
control_handle: _,
} => Box::pin(fake_watcher_impl::<Ipv6>(Box::into_pin(event_stream), watcher)),
fnet_routes::StateV6Request::GetRuleWatcherV6 {
options: _,
watcher: _,
control_handle: _,
} => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
},
)
.await
}
pub fn empty_watch_event_stream<'a, I: FidlRouteIpExt>(
) -> impl Stream<Item = Vec<I::WatchEvent>> + 'a {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrap<I: FidlRouteIpExt>(I::WatchEvent);
let Wrap(event) = I::map_ip(
(),
|()| Wrap(fnet_routes::EventV4::Idle(fnet_routes::Empty)),
|()| Wrap(fnet_routes::EventV6::Idle(fnet_routes::Empty)),
);
futures::stream::once(futures::future::ready(vec![event])).chain(futures::stream::pending())
}
pub mod admin {
use fidl::endpoints::ProtocolMarker;
use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
use futures::{Stream, StreamExt as _, TryStreamExt as _};
use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
use crate::admin::{FidlRouteAdminIpExt, RouteSetRequest, RouteTableRequest};
use crate::Responder;
pub fn serve_one_route_set<I: FidlRouteAdminIpExt>(
server_end: fidl::endpoints::ServerEnd<I::RouteTableMarker>,
) -> impl Stream<
Item = <
<<I as FidlRouteAdminIpExt>::RouteSetMarker as ProtocolMarker>
::RequestStream as Stream
>::Item
>{
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct In<I: FidlRouteAdminIpExt>(
<<<I as FidlRouteAdminIpExt>::RouteTableMarker as ProtocolMarker>
::RequestStream as Stream
>::Item,
);
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Out<I: FidlRouteAdminIpExt>(fidl::endpoints::ServerEnd<I::RouteSetMarker>);
let stream = server_end.into_stream();
stream
.scan(false, |responded, item| {
let responded = std::mem::replace(responded, true);
if responded {
panic!("received multiple RouteTable requests");
}
futures::future::ready(Some(item))
})
.map(|item| {
let Out(route_set_server_end) = I::map_ip(
In(item),
|In(item)| match item.expect("set provider FIDL error") {
fnet_routes_admin::RouteTableV4Request::NewRouteSet {
route_set,
control_handle: _,
} => Out(route_set),
req => unreachable!("unexpected request: {:?}", req),
},
|In(item)| match item.expect("set provider FIDL error") {
fnet_routes_admin::RouteTableV6Request::NewRouteSet {
route_set,
control_handle: _,
} => Out(route_set),
req => unreachable!("unexpected request: {:?}", req),
},
);
route_set_server_end.into_stream()
})
.fuse()
.flatten_unordered(None)
}
pub fn serve_all_route_sets_with_table_id<I: FidlRouteAdminIpExt>(
server_end: fidl::endpoints::ServerEnd<I::RouteTableMarker>,
table_id: Option<crate::TableId>,
) -> impl Stream<
Item = <
<<I as FidlRouteAdminIpExt>::RouteSetMarker as ProtocolMarker>
::RequestStream as Stream
>::Item
>{
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct In<I: FidlRouteAdminIpExt>(
<<<I as FidlRouteAdminIpExt>::RouteTableMarker as ProtocolMarker>
::RequestStream as Stream
>::Item,
);
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Out<I: FidlRouteAdminIpExt>(Option<fidl::endpoints::ServerEnd<I::RouteSetMarker>>);
let stream = server_end.into_stream();
stream
.map(move |item| {
let Out(route_set_server_end) = I::map_ip(
In(item),
|In(item)| match item.expect("set provider FIDL error") {
fnet_routes_admin::RouteTableV4Request::NewRouteSet {
route_set,
control_handle: _,
} => Out(Some(route_set)),
fnet_routes_admin::RouteTableV4Request::GetTableId { responder } => {
responder
.send(
table_id
.unwrap_or_else(|| panic!("GetTableId not supported"))
.get(),
)
.expect("error responding to GetTableId");
Out(None)
}
req => unreachable!("unexpected request: {:?}", req),
},
|In(item)| match item.expect("set provider FIDL error") {
fnet_routes_admin::RouteTableV6Request::NewRouteSet {
route_set,
control_handle: _,
} => Out(Some(route_set)),
fnet_routes_admin::RouteTableV6Request::GetTableId { responder } => {
responder
.send(
table_id
.unwrap_or_else(|| panic!("GetTableId not supported"))
.get(),
)
.expect("error responding to GetTableId");
Out(None)
}
req => unreachable!("unexpected request: {:?}", req),
},
);
match route_set_server_end {
None => futures::stream::empty().left_stream(),
Some(route_set_server_end) => route_set_server_end.into_stream().right_stream(),
}
})
.fuse()
.flatten_unordered(None)
}
pub async fn serve_noop_route_sets_with_table_id<I: FidlRouteAdminIpExt>(
server_end: fidl::endpoints::ServerEnd<I::RouteTableMarker>,
table_id: crate::TableId,
) {
let stream = server_end.into_stream();
stream
.try_for_each_concurrent(None, |item| async move {
let request = I::into_route_table_request(item);
match request {
RouteTableRequest::NewRouteSet { route_set, control_handle: _ } => {
serve_noop_route_set::<I>(route_set).await
}
RouteTableRequest::GetTableId { responder } => {
responder.send(table_id.get()).expect("responding should succeed");
}
request => panic!("unexpected request: {request:?}"),
}
Ok(())
})
.await
.expect("serving no-op route sets should succeed");
}
pub async fn serve_noop_route_sets<I: FidlRouteAdminIpExt>(
server_end: fidl::endpoints::ServerEnd<I::RouteTableMarker>,
) {
serve_noop_route_sets_with_table_id::<I>(server_end, crate::TableId::new(0)).await
}
async fn serve_noop_route_set<I: FidlRouteAdminIpExt>(
server_end: fidl::endpoints::ServerEnd<I::RouteSetMarker>,
) {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrap<I: FidlRouteAdminIpExt>(
<<<I as FidlRouteAdminIpExt>::RouteSetMarker as ProtocolMarker>
::RequestStream as Stream
>::Item,
);
let stream = server_end.into_stream();
stream
.for_each(|item| async move {
let request: RouteSetRequest<I> = I::map_ip(
Wrap(item),
|Wrap(item)| RouteSetRequest::<Ipv4>::from(item.expect("route set FIDL error")),
|Wrap(item)| RouteSetRequest::<Ipv6>::from(item.expect("route set FIDL error")),
);
match request {
RouteSetRequest::AddRoute { route, responder } => {
let _: crate::Route<I> = route.expect("AddRoute called with invalid route");
responder.send(Ok(true)).expect("respond to AddRoute");
}
RouteSetRequest::RemoveRoute { route, responder } => {
let _: crate::Route<I> =
route.expect("RemoveRoute called with invalid route");
responder.send(Ok(true)).expect("respond to RemoveRoute");
}
RouteSetRequest::AuthenticateForInterface { credential: _, responder } => {
responder.send(Ok(())).expect("respond to AuthenticateForInterface");
}
}
})
.await;
}
}
#[cfg(test)]
pub(crate) mod internal {
use super::*;
use net_declare::{fidl_ip_v4_with_prefix, fidl_ip_v6_with_prefix};
pub(crate) fn generate_event<I: FidlRouteIpExt>(seed: u32) -> I::WatchEvent {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct BuildEventOutput<I: FidlRouteIpExt>(I::WatchEvent);
let BuildEventOutput(event) = I::map_ip_out(
seed,
|seed| {
BuildEventOutput(fnet_routes::EventV4::Added(fnet_routes::InstalledRouteV4 {
route: Some(fnet_routes::RouteV4 {
destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
action: fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4 {
outbound_interface: 1,
next_hop: None,
}),
properties: fnet_routes::RoutePropertiesV4 {
specified_properties: Some(fnet_routes::SpecifiedRouteProperties {
metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(seed)),
..Default::default()
}),
..Default::default()
},
}),
effective_properties: Some(fnet_routes::EffectiveRouteProperties {
metric: Some(seed),
..Default::default()
}),
table_id: Some(0),
..Default::default()
}))
},
|seed| {
BuildEventOutput(fnet_routes::EventV6::Added(fnet_routes::InstalledRouteV6 {
route: Some(fnet_routes::RouteV6 {
destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
action: fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6 {
outbound_interface: 1,
next_hop: None,
}),
properties: fnet_routes::RoutePropertiesV6 {
specified_properties: Some(fnet_routes::SpecifiedRouteProperties {
metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(seed)),
..Default::default()
}),
..Default::default()
},
}),
effective_properties: Some(fnet_routes::EffectiveRouteProperties {
metric: Some(seed),
..Default::default()
}),
table_id: Some(0),
..Default::default()
}))
},
);
event
}
pub(crate) fn generate_events_in_range<I: FidlRouteIpExt>(
seeds: std::ops::Range<u32>,
) -> Vec<I::WatchEvent> {
seeds.into_iter().map(|seed| generate_event::<I>(seed)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::admin::FidlRouteAdminIpExt;
use crate::testutil::internal as internal_testutil;
use crate::{get_watcher, watch};
use assert_matches::assert_matches;
use futures::FutureExt;
use ip_test_macro::ip_test;
use test_case::test_case;
use zx_status;
#[ip_test(I)]
#[test_case(Vec::new(); "no events")]
#[test_case(vec![0..1]; "single_batch_single_event")]
#[test_case(vec![0..10]; "single_batch_many_events")]
#[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
#[fuchsia_async::run_singlethreaded(test)]
async fn fake_watcher_impl_against_shape<I: FidlRouteIpExt>(
test_shape: Vec<std::ops::Range<u32>>,
) {
let (event_stream_sender, event_stream_receiver) =
futures::channel::mpsc::unbounded::<Vec<I::WatchEvent>>();
for batch_shape in &test_shape {
event_stream_sender
.unbounded_send(internal_testutil::generate_events_in_range::<I>(
batch_shape.clone(),
))
.expect("failed to send event batch");
}
let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
let (mut state_request_stream, _control_handle) =
state_server_end.into_stream_and_control_handle();
let watcher_fut = state_request_stream
.next()
.then(|req| {
serve_state_request::<I>(
req.expect("State request_stream unexpectedly ended"),
event_stream_receiver,
)
})
.fuse();
futures::pin_mut!(watcher_fut);
let watcher = get_watcher::<I>(&state, Default::default()).expect("failed to get watcher");
for batch_shape in test_shape {
futures::select!(
() = watcher_fut => panic!("fake watcher implementation unexpectedly finished"),
events = watch::<I>(&watcher).fuse() => assert_eq!(
events.expect("failed to watch for events"),
internal_testutil::generate_events_in_range::<I>(batch_shape.clone())));
}
event_stream_sender.close_channel();
watcher_fut.await;
assert_matches!(
watch::<I>(&watcher).await,
Err(fidl::Error::ClientChannelClosed { status: zx_status::Status::PEER_CLOSED, .. })
);
}
#[ip_test(I)]
#[fuchsia_async::run_singlethreaded]
#[should_panic(expected = "received multiple RouteTable requests")]
async fn test_serve_one_route_set_panic<I: FidlRouteAdminIpExt>() {
let (routes_set_provider_proxy, routes_set_provider_server_end) =
fidl::endpoints::create_proxy::<I::RouteTableMarker>();
let mut provider = admin::serve_one_route_set::<I>(routes_set_provider_server_end);
let _rs1 = crate::admin::new_route_set::<I>(&routes_set_provider_proxy)
.expect("created first RouteSet");
let _rs2 = crate::admin::new_route_set::<I>(&routes_set_provider_proxy)
.expect("created second RouteSet");
let _ = provider.next().await;
}
}