fidl_fuchsia_net_filter_ext/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Extensions for the fuchsia.net.filter FIDL library.
6//!
7//! Note that this library as written is not meant for inclusion in the SDK. It
8//! is only meant to be used in conjunction with a netstack that is compiled
9//! against the same API level of the `fuchsia.net.filter` FIDL library. This
10//! library opts in to compile-time and runtime breakage when the FIDL library
11//! is evolved in order to enforce that it is updated along with the FIDL
12//! library itself.
13
14#[cfg(target_os = "fuchsia")]
15pub mod sync;
16
17use std::collections::HashMap;
18use std::fmt::Debug;
19use std::num::NonZeroU16;
20use std::ops::RangeInclusive;
21
22use async_utils::fold::FoldWhile;
23use fidl::marker::SourceBreaking;
24use futures::{Stream, StreamExt as _, TryStreamExt as _};
25use thiserror::Error;
26use {
27    fidl_fuchsia_ebpf as febpf, fidl_fuchsia_net as fnet, fidl_fuchsia_net_filter as fnet_filter,
28    fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
29    fidl_fuchsia_net_matchers_ext as fnet_matchers_ext, fidl_fuchsia_net_root as fnet_root,
30};
31
32/// Conversion errors from `fnet_filter` FIDL types to the
33/// equivalents defined in this module.
34#[derive(Debug, Error, PartialEq)]
35pub enum FidlConversionError {
36    #[error("union is of an unknown variant: {0}")]
37    UnknownUnionVariant(&'static str),
38    #[error("namespace ID not provided")]
39    MissingNamespaceId,
40    #[error("namespace domain not provided")]
41    MissingNamespaceDomain,
42    #[error("routine ID not provided")]
43    MissingRoutineId,
44    #[error("routine type not provided")]
45    MissingRoutineType,
46    #[error("IP installation hook not provided")]
47    MissingIpInstallationHook,
48    #[error("NAT installation hook not provided")]
49    MissingNatInstallationHook,
50    #[error("interface matcher specified an invalid ID of 0")]
51    ZeroInterfaceId,
52    #[error("invalid address range (start must be <= end)")]
53    InvalidAddressRange,
54    #[error("address range start and end addresses are not the same IP family")]
55    AddressRangeFamilyMismatch,
56    #[error("prefix length of subnet is longer than number of bits in IP address")]
57    SubnetPrefixTooLong,
58    #[error("host bits are set in subnet network")]
59    SubnetHostBitsSet,
60    #[error("invalid port matcher range (start must be <= end)")]
61    InvalidPortMatcherRange,
62    #[error("transparent proxy action specified an invalid local port of 0")]
63    UnspecifiedTransparentProxyPort,
64    #[error("NAT action specified an invalid rewrite port of 0")]
65    UnspecifiedNatPort,
66    #[error("invalid port range (start must be <= end)")]
67    InvalidPortRange,
68    #[error("non-error result variant could not be converted to an error")]
69    NotAnError,
70}
71
72impl From<fnet_matchers_ext::PortError> for FidlConversionError {
73    fn from(value: fnet_matchers_ext::PortError) -> Self {
74        match value {
75            fnet_matchers_ext::PortError::InvalidPortRange => {
76                FidlConversionError::InvalidPortMatcherRange
77            }
78        }
79    }
80}
81
82impl From<fnet_matchers_ext::InterfaceError> for FidlConversionError {
83    fn from(value: fnet_matchers_ext::InterfaceError) -> Self {
84        match value {
85            fnet_matchers_ext::InterfaceError::ZeroId => FidlConversionError::ZeroInterfaceId,
86            fnet_matchers_ext::InterfaceError::UnknownUnionVariant => {
87                FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
88            }
89            fidl_fuchsia_net_matchers_ext::InterfaceError::UnknownPortClass(
90                unknown_port_class_error,
91            ) => match unknown_port_class_error {
92                fnet_interfaces_ext::UnknownPortClassError::NetInterfaces(_) => {
93                    FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
94                }
95                fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(_) => {
96                    FidlConversionError::UnknownUnionVariant(
97                        type_names::HARDWARE_NETWORK_PORT_CLASS,
98                    )
99                }
100            },
101        }
102    }
103}
104
105impl From<fnet_matchers_ext::AddressError> for FidlConversionError {
106    fn from(value: fnet_matchers_ext::AddressError) -> Self {
107        match value {
108            fnet_matchers_ext::AddressError::AddressMatcherType(address_matcher_type_error) => {
109                address_matcher_type_error.into()
110            }
111        }
112    }
113}
114
115impl From<fnet_matchers_ext::AddressMatcherTypeError> for FidlConversionError {
116    fn from(value: fnet_matchers_ext::AddressMatcherTypeError) -> Self {
117        match value {
118            fnet_matchers_ext::AddressMatcherTypeError::Subnet(subnet_error) => subnet_error.into(),
119            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(address_range_error) => {
120                address_range_error.into()
121            }
122            fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant => {
123                FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
124            }
125        }
126    }
127}
128
129impl From<fnet_matchers_ext::AddressRangeError> for FidlConversionError {
130    fn from(value: fnet_matchers_ext::AddressRangeError) -> Self {
131        match value {
132            fnet_matchers_ext::AddressRangeError::Invalid => {
133                FidlConversionError::InvalidAddressRange
134            }
135            fnet_matchers_ext::AddressRangeError::FamilyMismatch => {
136                FidlConversionError::AddressRangeFamilyMismatch
137            }
138        }
139    }
140}
141
142impl From<fnet_matchers_ext::SubnetError> for FidlConversionError {
143    fn from(value: fnet_matchers_ext::SubnetError) -> Self {
144        match value {
145            fnet_matchers_ext::SubnetError::PrefixTooLong => {
146                FidlConversionError::SubnetPrefixTooLong
147            }
148            fnet_matchers_ext::SubnetError::HostBitsSet => FidlConversionError::SubnetHostBitsSet,
149        }
150    }
151}
152
153impl From<fnet_matchers_ext::TransportProtocolError> for FidlConversionError {
154    fn from(value: fnet_matchers_ext::TransportProtocolError) -> Self {
155        match value {
156            fnet_matchers_ext::TransportProtocolError::Port(port_matcher_error) => {
157                port_matcher_error.into()
158            }
159            fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant => {
160                FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
161            }
162        }
163    }
164}
165
166// TODO(https://fxbug.dev/317058051): remove this when the Rust FIDL bindings
167// expose constants for these.
168mod type_names {
169    pub(super) const RESOURCE_ID: &str = "fuchsia.net.filter/ResourceId";
170    pub(super) const DOMAIN: &str = "fuchsia.net.filter/Domain";
171    pub(super) const IP_INSTALLATION_HOOK: &str = "fuchsia.net.filter/IpInstallationHook";
172    pub(super) const NAT_INSTALLATION_HOOK: &str = "fuchsia.net.filter/NatInstallationHook";
173    pub(super) const ROUTINE_TYPE: &str = "fuchsia.net.filter/RoutineType";
174    pub(super) const INTERFACE_MATCHER: &str = "fuchsia.net.matchers/Interface";
175    pub(super) const ADDRESS_MATCHER_TYPE: &str = "fuchsia.net.filter/AddressMatcherType";
176    pub(super) const TRANSPORT_PROTOCOL: &str = "fuchsia.net.matchers/TransportProtocol";
177    pub(super) const ACTION: &str = "fuchsia.net.filter/Action";
178    pub(super) const MARK_ACTION: &str = "fuchsia.net.filter/MarkAction";
179    pub(super) const TRANSPARENT_PROXY: &str = "fuchsia.net.filter/TransparentProxy";
180    pub(super) const RESOURCE: &str = "fuchsia.net.filter/Resource";
181    pub(super) const EVENT: &str = "fuchsia.net.filter/Event";
182    pub(super) const CHANGE: &str = "fuchsia.net.filter/Change";
183    pub(super) const CHANGE_VALIDATION_ERROR: &str = "fuchsia.net.filter/ChangeValidationError";
184    pub(super) const CHANGE_VALIDATION_RESULT: &str = "fuchsia.net.filter/ChangeValidationResult";
185    pub(super) const COMMIT_ERROR: &str = "fuchsia.net.filter/CommitError";
186    pub(super) const COMMIT_RESULT: &str = "fuchsia.net.filter/CommitResult";
187    pub(super) const NET_INTERFACES_PORT_CLASS: &str = "fuchsia.net.interfaces/PortClass";
188    pub(super) const HARDWARE_NETWORK_PORT_CLASS: &str = "fuchsia.hardware.network/PortClass";
189}
190
191/// Extension type for [`fnet_filter::NamespaceId`].
192#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
193pub struct NamespaceId(pub String);
194
195/// Extension type for [`fnet_filter::RoutineId`].
196#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197pub struct RoutineId {
198    pub namespace: NamespaceId,
199    pub name: String,
200}
201
202impl From<fnet_filter::RoutineId> for RoutineId {
203    fn from(id: fnet_filter::RoutineId) -> Self {
204        let fnet_filter::RoutineId { namespace, name } = id;
205        Self { namespace: NamespaceId(namespace), name }
206    }
207}
208
209impl From<RoutineId> for fnet_filter::RoutineId {
210    fn from(id: RoutineId) -> Self {
211        let RoutineId { namespace, name } = id;
212        let NamespaceId(namespace) = namespace;
213        Self { namespace, name }
214    }
215}
216
217/// Extension type for [`fnet_filter::RuleId`].
218#[derive(Debug, Clone, PartialEq, Eq, Hash)]
219pub struct RuleId {
220    pub routine: RoutineId,
221    pub index: u32,
222}
223
224impl From<fnet_filter::RuleId> for RuleId {
225    fn from(id: fnet_filter::RuleId) -> Self {
226        let fnet_filter::RuleId { routine, index } = id;
227        Self { routine: routine.into(), index }
228    }
229}
230
231impl From<RuleId> for fnet_filter::RuleId {
232    fn from(id: RuleId) -> Self {
233        let RuleId { routine, index } = id;
234        Self { routine: routine.into(), index }
235    }
236}
237
238/// Extension type for [`fnet_filter::ResourceId`].
239#[derive(Debug, Clone, PartialEq, Eq, Hash)]
240pub enum ResourceId {
241    Namespace(NamespaceId),
242    Routine(RoutineId),
243    Rule(RuleId),
244}
245
246impl TryFrom<fnet_filter::ResourceId> for ResourceId {
247    type Error = FidlConversionError;
248
249    fn try_from(id: fnet_filter::ResourceId) -> Result<Self, Self::Error> {
250        match id {
251            fnet_filter::ResourceId::Namespace(id) => Ok(Self::Namespace(NamespaceId(id))),
252            fnet_filter::ResourceId::Routine(id) => Ok(Self::Routine(id.into())),
253            fnet_filter::ResourceId::Rule(id) => Ok(Self::Rule(id.into())),
254            fnet_filter::ResourceId::__SourceBreaking { .. } => {
255                Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
256            }
257        }
258    }
259}
260
261impl From<ResourceId> for fnet_filter::ResourceId {
262    fn from(id: ResourceId) -> Self {
263        match id {
264            ResourceId::Namespace(NamespaceId(id)) => fnet_filter::ResourceId::Namespace(id),
265            ResourceId::Routine(id) => fnet_filter::ResourceId::Routine(id.into()),
266            ResourceId::Rule(id) => fnet_filter::ResourceId::Rule(id.into()),
267        }
268    }
269}
270
271/// Extension type for [`fnet_filter::Domain`].
272#[derive(Debug, Clone, PartialEq)]
273pub enum Domain {
274    Ipv4,
275    Ipv6,
276    AllIp,
277}
278
279impl From<Domain> for fnet_filter::Domain {
280    fn from(domain: Domain) -> Self {
281        match domain {
282            Domain::Ipv4 => fnet_filter::Domain::Ipv4,
283            Domain::Ipv6 => fnet_filter::Domain::Ipv6,
284            Domain::AllIp => fnet_filter::Domain::AllIp,
285        }
286    }
287}
288
289impl TryFrom<fnet_filter::Domain> for Domain {
290    type Error = FidlConversionError;
291
292    fn try_from(domain: fnet_filter::Domain) -> Result<Self, Self::Error> {
293        match domain {
294            fnet_filter::Domain::Ipv4 => Ok(Self::Ipv4),
295            fnet_filter::Domain::Ipv6 => Ok(Self::Ipv6),
296            fnet_filter::Domain::AllIp => Ok(Self::AllIp),
297            fnet_filter::Domain::__SourceBreaking { .. } => {
298                Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
299            }
300        }
301    }
302}
303
304/// Extension type for [`fnet_filter::Namespace`].
305#[derive(Debug, Clone, PartialEq)]
306pub struct Namespace {
307    pub id: NamespaceId,
308    pub domain: Domain,
309}
310
311impl From<Namespace> for fnet_filter::Namespace {
312    fn from(namespace: Namespace) -> Self {
313        let Namespace { id, domain } = namespace;
314        let NamespaceId(id) = id;
315        Self { id: Some(id), domain: Some(domain.into()), __source_breaking: SourceBreaking }
316    }
317}
318
319impl TryFrom<fnet_filter::Namespace> for Namespace {
320    type Error = FidlConversionError;
321
322    fn try_from(namespace: fnet_filter::Namespace) -> Result<Self, Self::Error> {
323        let fnet_filter::Namespace { id, domain, __source_breaking } = namespace;
324        let id = NamespaceId(id.ok_or(FidlConversionError::MissingNamespaceId)?);
325        let domain = domain.ok_or(FidlConversionError::MissingNamespaceDomain)?.try_into()?;
326        Ok(Self { id, domain })
327    }
328}
329
330/// Extension type for [`fnet_filter::IpInstallationHook`].
331#[derive(Debug, Clone, Copy, PartialEq)]
332pub enum IpHook {
333    Ingress,
334    LocalIngress,
335    Forwarding,
336    LocalEgress,
337    Egress,
338}
339
340impl From<IpHook> for fnet_filter::IpInstallationHook {
341    fn from(hook: IpHook) -> Self {
342        match hook {
343            IpHook::Ingress => Self::Ingress,
344            IpHook::LocalIngress => Self::LocalIngress,
345            IpHook::Forwarding => Self::Forwarding,
346            IpHook::LocalEgress => Self::LocalEgress,
347            IpHook::Egress => Self::Egress,
348        }
349    }
350}
351
352impl TryFrom<fnet_filter::IpInstallationHook> for IpHook {
353    type Error = FidlConversionError;
354
355    fn try_from(hook: fnet_filter::IpInstallationHook) -> Result<Self, Self::Error> {
356        match hook {
357            fnet_filter::IpInstallationHook::Ingress => Ok(Self::Ingress),
358            fnet_filter::IpInstallationHook::LocalIngress => Ok(Self::LocalIngress),
359            fnet_filter::IpInstallationHook::Forwarding => Ok(Self::Forwarding),
360            fnet_filter::IpInstallationHook::LocalEgress => Ok(Self::LocalEgress),
361            fnet_filter::IpInstallationHook::Egress => Ok(Self::Egress),
362            fnet_filter::IpInstallationHook::__SourceBreaking { .. } => {
363                Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
364            }
365        }
366    }
367}
368
369/// Extension type for [`fnet_filter::NatInstallationHook`].
370#[derive(Debug, Clone, Copy, PartialEq)]
371pub enum NatHook {
372    Ingress,
373    LocalIngress,
374    LocalEgress,
375    Egress,
376}
377
378impl From<NatHook> for fnet_filter::NatInstallationHook {
379    fn from(hook: NatHook) -> Self {
380        match hook {
381            NatHook::Ingress => Self::Ingress,
382            NatHook::LocalIngress => Self::LocalIngress,
383            NatHook::LocalEgress => Self::LocalEgress,
384            NatHook::Egress => Self::Egress,
385        }
386    }
387}
388
389impl TryFrom<fnet_filter::NatInstallationHook> for NatHook {
390    type Error = FidlConversionError;
391
392    fn try_from(hook: fnet_filter::NatInstallationHook) -> Result<Self, Self::Error> {
393        match hook {
394            fnet_filter::NatInstallationHook::Ingress => Ok(Self::Ingress),
395            fnet_filter::NatInstallationHook::LocalIngress => Ok(Self::LocalIngress),
396            fnet_filter::NatInstallationHook::LocalEgress => Ok(Self::LocalEgress),
397            fnet_filter::NatInstallationHook::Egress => Ok(Self::Egress),
398            fnet_filter::NatInstallationHook::__SourceBreaking { .. } => {
399                Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
400            }
401        }
402    }
403}
404
405/// Extension type for [`fnet_filter::InstalledIpRoutine`].
406#[derive(Debug, Clone, PartialEq)]
407pub struct InstalledIpRoutine {
408    pub hook: IpHook,
409    pub priority: i32,
410}
411
412impl From<InstalledIpRoutine> for fnet_filter::InstalledIpRoutine {
413    fn from(routine: InstalledIpRoutine) -> Self {
414        let InstalledIpRoutine { hook, priority } = routine;
415        Self {
416            hook: Some(hook.into()),
417            priority: Some(priority),
418            __source_breaking: SourceBreaking,
419        }
420    }
421}
422
423impl TryFrom<fnet_filter::InstalledIpRoutine> for InstalledIpRoutine {
424    type Error = FidlConversionError;
425
426    fn try_from(routine: fnet_filter::InstalledIpRoutine) -> Result<Self, Self::Error> {
427        let fnet_filter::InstalledIpRoutine { hook, priority, __source_breaking } = routine;
428        let hook = hook.ok_or(FidlConversionError::MissingIpInstallationHook)?;
429        let priority = priority.unwrap_or(fnet_filter::DEFAULT_ROUTINE_PRIORITY);
430        Ok(Self { hook: hook.try_into()?, priority })
431    }
432}
433
434/// Extension type for [`fnet_filter::InstalledNatRoutine`].
435#[derive(Debug, Clone, PartialEq)]
436pub struct InstalledNatRoutine {
437    pub hook: NatHook,
438    pub priority: i32,
439}
440
441impl From<InstalledNatRoutine> for fnet_filter::InstalledNatRoutine {
442    fn from(routine: InstalledNatRoutine) -> Self {
443        let InstalledNatRoutine { hook, priority } = routine;
444        Self {
445            hook: Some(hook.into()),
446            priority: Some(priority),
447            __source_breaking: SourceBreaking,
448        }
449    }
450}
451
452impl TryFrom<fnet_filter::InstalledNatRoutine> for InstalledNatRoutine {
453    type Error = FidlConversionError;
454
455    fn try_from(routine: fnet_filter::InstalledNatRoutine) -> Result<Self, Self::Error> {
456        let fnet_filter::InstalledNatRoutine { hook, priority, __source_breaking } = routine;
457        let hook = hook.ok_or(FidlConversionError::MissingNatInstallationHook)?;
458        let priority = priority.unwrap_or(fnet_filter::DEFAULT_ROUTINE_PRIORITY);
459        Ok(Self { hook: hook.try_into()?, priority })
460    }
461}
462
463/// Extension type for [`fnet_filter::RoutineType`].
464#[derive(Debug, Clone, PartialEq)]
465pub enum RoutineType {
466    Ip(Option<InstalledIpRoutine>),
467    Nat(Option<InstalledNatRoutine>),
468}
469
470impl RoutineType {
471    pub fn is_installed(&self) -> bool {
472        // The `InstalledIpRoutine` or `InstalledNatRoutine` configuration is
473        // optional, and when omitted, signifies an uninstalled routine.
474        match self {
475            Self::Ip(Some(_)) | Self::Nat(Some(_)) => true,
476            Self::Ip(None) | Self::Nat(None) => false,
477        }
478    }
479}
480
481impl From<RoutineType> for fnet_filter::RoutineType {
482    fn from(routine: RoutineType) -> Self {
483        match routine {
484            RoutineType::Ip(installation) => Self::Ip(fnet_filter::IpRoutine {
485                installation: installation.map(Into::into),
486                __source_breaking: SourceBreaking,
487            }),
488            RoutineType::Nat(installation) => Self::Nat(fnet_filter::NatRoutine {
489                installation: installation.map(Into::into),
490                __source_breaking: SourceBreaking,
491            }),
492        }
493    }
494}
495
496impl TryFrom<fnet_filter::RoutineType> for RoutineType {
497    type Error = FidlConversionError;
498
499    fn try_from(type_: fnet_filter::RoutineType) -> Result<Self, Self::Error> {
500        match type_ {
501            fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
502                installation,
503                __source_breaking,
504            }) => Ok(RoutineType::Ip(installation.map(TryInto::try_into).transpose()?)),
505            fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine {
506                installation,
507                __source_breaking,
508            }) => Ok(RoutineType::Nat(installation.map(TryInto::try_into).transpose()?)),
509            fnet_filter::RoutineType::__SourceBreaking { .. } => {
510                Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
511            }
512        }
513    }
514}
515
516/// Extension type for [`fnet_filter::Routine`].
517#[derive(Debug, Clone, PartialEq)]
518pub struct Routine {
519    pub id: RoutineId,
520    pub routine_type: RoutineType,
521}
522
523impl From<Routine> for fnet_filter::Routine {
524    fn from(routine: Routine) -> Self {
525        let Routine { id, routine_type: type_ } = routine;
526        Self { id: Some(id.into()), type_: Some(type_.into()), __source_breaking: SourceBreaking }
527    }
528}
529
530impl TryFrom<fnet_filter::Routine> for Routine {
531    type Error = FidlConversionError;
532
533    fn try_from(routine: fnet_filter::Routine) -> Result<Self, Self::Error> {
534        let fnet_filter::Routine { id, type_, __source_breaking } = routine;
535        let id = id.ok_or(FidlConversionError::MissingRoutineId)?;
536        let type_ = type_.ok_or(FidlConversionError::MissingRoutineType)?;
537        Ok(Self { id: id.into(), routine_type: type_.try_into()? })
538    }
539}
540
541/// Extension type for [`fnet_filter::Matchers`].
542#[derive(Default, Clone, PartialEq)]
543pub struct Matchers {
544    pub in_interface: Option<fnet_matchers_ext::Interface>,
545    pub out_interface: Option<fnet_matchers_ext::Interface>,
546    pub src_addr: Option<fnet_matchers_ext::Address>,
547    pub dst_addr: Option<fnet_matchers_ext::Address>,
548    pub transport_protocol: Option<fnet_matchers_ext::TransportProtocol>,
549    pub ebpf_program: Option<febpf::ProgramId>,
550}
551
552impl From<Matchers> for fnet_filter::Matchers {
553    fn from(matchers: Matchers) -> Self {
554        let Matchers {
555            in_interface,
556            out_interface,
557            src_addr,
558            dst_addr,
559            transport_protocol,
560            ebpf_program,
561        } = matchers;
562        Self {
563            in_interface: in_interface.map(Into::into),
564            out_interface: out_interface.map(Into::into),
565            src_addr: src_addr.map(Into::into),
566            dst_addr: dst_addr.map(Into::into),
567            transport_protocol: transport_protocol.map(Into::into),
568            ebpf_program: ebpf_program.map(Into::into),
569            __source_breaking: SourceBreaking,
570        }
571    }
572}
573
574impl TryFrom<fnet_filter::Matchers> for Matchers {
575    type Error = FidlConversionError;
576
577    fn try_from(matchers: fnet_filter::Matchers) -> Result<Self, Self::Error> {
578        let fnet_filter::Matchers {
579            in_interface,
580            out_interface,
581            src_addr,
582            dst_addr,
583            transport_protocol,
584            ebpf_program,
585            __source_breaking,
586        } = matchers;
587        Ok(Self {
588            in_interface: in_interface.map(TryInto::try_into).transpose()?,
589            out_interface: out_interface.map(TryInto::try_into).transpose()?,
590            src_addr: src_addr.map(TryInto::try_into).transpose()?,
591            dst_addr: dst_addr.map(TryInto::try_into).transpose()?,
592            transport_protocol: transport_protocol.map(TryInto::try_into).transpose()?,
593            ebpf_program: ebpf_program.map(Into::into),
594        })
595    }
596}
597
598impl Debug for Matchers {
599    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600        let mut debug_struct = f.debug_struct("Matchers");
601
602        let Matchers {
603            in_interface,
604            out_interface,
605            src_addr,
606            dst_addr,
607            transport_protocol,
608            ebpf_program,
609        } = &self;
610
611        // Omit empty fields.
612        if let Some(matcher) = in_interface {
613            let _ = debug_struct.field("in_interface", matcher);
614        }
615
616        if let Some(matcher) = out_interface {
617            let _ = debug_struct.field("out_interface", matcher);
618        }
619
620        if let Some(matcher) = src_addr {
621            let _ = debug_struct.field("src_addr", matcher);
622        }
623
624        if let Some(matcher) = dst_addr {
625            let _ = debug_struct.field("dst_addr", matcher);
626        }
627
628        if let Some(matcher) = transport_protocol {
629            let _ = debug_struct.field("transport_protocol", matcher);
630        }
631
632        if let Some(matcher) = ebpf_program {
633            let _ = debug_struct.field("ebpf_program", matcher);
634        }
635
636        debug_struct.finish()
637    }
638}
639
640/// Extension type for [`fnet_filter::Action`].
641#[derive(Debug, Clone, PartialEq)]
642pub enum Action {
643    Accept,
644    Drop,
645    Jump(String),
646    Return,
647    TransparentProxy(TransparentProxy),
648    Redirect { dst_port: Option<PortRange> },
649    Masquerade { src_port: Option<PortRange> },
650    Mark { domain: fnet::MarkDomain, action: MarkAction },
651    None,
652}
653
654#[derive(Debug, Clone, PartialEq)]
655pub enum MarkAction {
656    SetMark { clearing_mask: fnet::Mark, mark: fnet::Mark },
657}
658
659/// Extension type for [`fnet_filter::TransparentProxy_`].
660#[derive(Debug, Clone, PartialEq)]
661pub enum TransparentProxy {
662    LocalAddr(fnet::IpAddress),
663    LocalPort(NonZeroU16),
664    LocalAddrAndPort(fnet::IpAddress, NonZeroU16),
665}
666
667#[derive(Debug, Clone, PartialEq)]
668pub struct PortRange(pub RangeInclusive<NonZeroU16>);
669
670impl From<PortRange> for fnet_filter::PortRange {
671    fn from(range: PortRange) -> Self {
672        let PortRange(range) = range;
673        Self { start: range.start().get(), end: range.end().get() }
674    }
675}
676
677impl TryFrom<fnet_filter::PortRange> for PortRange {
678    type Error = FidlConversionError;
679
680    fn try_from(range: fnet_filter::PortRange) -> Result<Self, Self::Error> {
681        let fnet_filter::PortRange { start, end } = range;
682        if start > end {
683            Err(FidlConversionError::InvalidPortRange)
684        } else {
685            let start = NonZeroU16::new(start).ok_or(FidlConversionError::UnspecifiedNatPort)?;
686            let end = NonZeroU16::new(end).ok_or(FidlConversionError::UnspecifiedNatPort)?;
687            Ok(Self(start..=end))
688        }
689    }
690}
691
692impl From<Action> for fnet_filter::Action {
693    fn from(action: Action) -> Self {
694        match action {
695            Action::Accept => Self::Accept(fnet_filter::Empty {}),
696            Action::Drop => Self::Drop(fnet_filter::Empty {}),
697            Action::Jump(target) => Self::Jump(target),
698            Action::Return => Self::Return_(fnet_filter::Empty {}),
699            Action::TransparentProxy(proxy) => Self::TransparentProxy(match proxy {
700                TransparentProxy::LocalAddr(addr) => {
701                    fnet_filter::TransparentProxy_::LocalAddr(addr)
702                }
703                TransparentProxy::LocalPort(port) => {
704                    fnet_filter::TransparentProxy_::LocalPort(port.get())
705                }
706                TransparentProxy::LocalAddrAndPort(addr, port) => {
707                    fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
708                        addr,
709                        port: port.get(),
710                    })
711                }
712            }),
713            Action::Redirect { dst_port } => Self::Redirect(fnet_filter::Redirect {
714                dst_port: dst_port.map(Into::into),
715                __source_breaking: SourceBreaking,
716            }),
717            Action::Masquerade { src_port } => Self::Masquerade(fnet_filter::Masquerade {
718                src_port: src_port.map(Into::into),
719                __source_breaking: SourceBreaking,
720            }),
721            Action::Mark { domain, action } => {
722                Self::Mark(fnet_filter::Mark { domain, action: action.into() })
723            }
724            Action::None => Self::None(fnet_filter::Empty {}),
725        }
726    }
727}
728
729impl TryFrom<fnet_filter::Action> for Action {
730    type Error = FidlConversionError;
731
732    fn try_from(action: fnet_filter::Action) -> Result<Self, Self::Error> {
733        match action {
734            fnet_filter::Action::Accept(fnet_filter::Empty {}) => Ok(Self::Accept),
735            fnet_filter::Action::Drop(fnet_filter::Empty {}) => Ok(Self::Drop),
736            fnet_filter::Action::Jump(target) => Ok(Self::Jump(target)),
737            fnet_filter::Action::Return_(fnet_filter::Empty {}) => Ok(Self::Return),
738            fnet_filter::Action::TransparentProxy(proxy) => {
739                Ok(Self::TransparentProxy(match proxy {
740                    fnet_filter::TransparentProxy_::LocalAddr(addr) => {
741                        TransparentProxy::LocalAddr(addr)
742                    }
743                    fnet_filter::TransparentProxy_::LocalPort(port) => {
744                        let port = NonZeroU16::new(port)
745                            .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
746                        TransparentProxy::LocalPort(port)
747                    }
748                    fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
749                        addr,
750                        port,
751                    }) => {
752                        let port = NonZeroU16::new(port)
753                            .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
754                        TransparentProxy::LocalAddrAndPort(addr, port)
755                    }
756                    fnet_filter::TransparentProxy_::__SourceBreaking { .. } => {
757                        return Err(FidlConversionError::UnknownUnionVariant(
758                            type_names::TRANSPARENT_PROXY,
759                        ));
760                    }
761                }))
762            }
763            fnet_filter::Action::Redirect(fnet_filter::Redirect {
764                dst_port,
765                __source_breaking,
766            }) => Ok(Self::Redirect { dst_port: dst_port.map(TryInto::try_into).transpose()? }),
767            fnet_filter::Action::Masquerade(fnet_filter::Masquerade {
768                src_port,
769                __source_breaking,
770            }) => Ok(Self::Masquerade { src_port: src_port.map(TryInto::try_into).transpose()? }),
771            fnet_filter::Action::Mark(fnet_filter::Mark { domain, action }) => {
772                Ok(Self::Mark { domain, action: action.try_into()? })
773            }
774            fnet_filter::Action::__SourceBreaking { .. } => {
775                Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
776            }
777            fnet_filter::Action::None(fnet_filter::Empty {}) => Ok(Self::None),
778        }
779    }
780}
781
782impl From<MarkAction> for fnet_filter::MarkAction {
783    fn from(action: MarkAction) -> Self {
784        match action {
785            MarkAction::SetMark { clearing_mask, mark } => {
786                Self::SetMark(fnet_filter::SetMark { clearing_mask, mark })
787            }
788        }
789    }
790}
791
792impl TryFrom<fnet_filter::MarkAction> for MarkAction {
793    type Error = FidlConversionError;
794    fn try_from(action: fnet_filter::MarkAction) -> Result<Self, Self::Error> {
795        match action {
796            fnet_filter::MarkAction::SetMark(fnet_filter::SetMark { clearing_mask, mark }) => {
797                Ok(Self::SetMark { clearing_mask, mark })
798            }
799            fnet_filter::MarkAction::__SourceBreaking { .. } => {
800                Err(FidlConversionError::UnknownUnionVariant(type_names::MARK_ACTION))
801            }
802        }
803    }
804}
805
806/// Extension type for [`fnet_filter::Rule`].
807#[derive(Debug, Clone, PartialEq)]
808pub struct Rule {
809    pub id: RuleId,
810    pub matchers: Matchers,
811    pub action: Action,
812}
813
814impl From<Rule> for fnet_filter::Rule {
815    fn from(rule: Rule) -> Self {
816        let Rule { id, matchers, action } = rule;
817        Self { id: id.into(), matchers: matchers.into(), action: action.into() }
818    }
819}
820
821impl TryFrom<fnet_filter::Rule> for Rule {
822    type Error = FidlConversionError;
823
824    fn try_from(rule: fnet_filter::Rule) -> Result<Self, Self::Error> {
825        let fnet_filter::Rule { id, matchers, action } = rule;
826        Ok(Self { id: id.into(), matchers: matchers.try_into()?, action: action.try_into()? })
827    }
828}
829
830/// Extension type for [`fnet_filter::Resource`].
831#[derive(Debug, Clone, PartialEq)]
832pub enum Resource {
833    Namespace(Namespace),
834    Routine(Routine),
835    Rule(Rule),
836}
837
838impl Resource {
839    pub fn id(&self) -> ResourceId {
840        match self {
841            Self::Namespace(Namespace { id, domain: _ }) => ResourceId::Namespace(id.clone()),
842            Self::Routine(Routine { id, routine_type: _ }) => ResourceId::Routine(id.clone()),
843            Self::Rule(Rule { id, matchers: _, action: _ }) => ResourceId::Rule(id.clone()),
844        }
845    }
846}
847
848impl From<Resource> for fnet_filter::Resource {
849    fn from(resource: Resource) -> Self {
850        match resource {
851            Resource::Namespace(namespace) => Self::Namespace(namespace.into()),
852            Resource::Routine(routine) => Self::Routine(routine.into()),
853            Resource::Rule(rule) => Self::Rule(rule.into()),
854        }
855    }
856}
857
858impl TryFrom<fnet_filter::Resource> for Resource {
859    type Error = FidlConversionError;
860
861    fn try_from(resource: fnet_filter::Resource) -> Result<Self, Self::Error> {
862        match resource {
863            fnet_filter::Resource::Namespace(namespace) => {
864                Ok(Self::Namespace(namespace.try_into()?))
865            }
866            fnet_filter::Resource::Routine(routine) => Ok(Self::Routine(routine.try_into()?)),
867            fnet_filter::Resource::Rule(rule) => Ok(Self::Rule(rule.try_into()?)),
868            fnet_filter::Resource::__SourceBreaking { .. } => {
869                Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
870            }
871        }
872    }
873}
874
875/// Extension type for [`fnet_filter::ControllerId`].
876#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
877pub struct ControllerId(pub String);
878
879/// Extension type for [`fnet_filter::Event`].
880#[derive(Debug, Clone, PartialEq)]
881pub enum Event {
882    Existing(ControllerId, Resource),
883    Idle,
884    Added(ControllerId, Resource),
885    Removed(ControllerId, ResourceId),
886    EndOfUpdate,
887}
888
889impl From<Event> for fnet_filter::Event {
890    fn from(event: Event) -> Self {
891        match event {
892            Event::Existing(controller, resource) => {
893                let ControllerId(id) = controller;
894                Self::Existing(fnet_filter::ExistingResource {
895                    controller: id,
896                    resource: resource.into(),
897                })
898            }
899            Event::Idle => Self::Idle(fnet_filter::Empty {}),
900            Event::Added(controller, resource) => {
901                let ControllerId(id) = controller;
902                Self::Added(fnet_filter::AddedResource {
903                    controller: id,
904                    resource: resource.into(),
905                })
906            }
907            Event::Removed(controller, resource) => {
908                let ControllerId(id) = controller;
909                Self::Removed(fnet_filter::RemovedResource {
910                    controller: id,
911                    resource: resource.into(),
912                })
913            }
914            Event::EndOfUpdate => Self::EndOfUpdate(fnet_filter::Empty {}),
915        }
916    }
917}
918
919impl TryFrom<fnet_filter::Event> for Event {
920    type Error = FidlConversionError;
921
922    fn try_from(event: fnet_filter::Event) -> Result<Self, Self::Error> {
923        match event {
924            fnet_filter::Event::Existing(fnet_filter::ExistingResource {
925                controller,
926                resource,
927            }) => Ok(Self::Existing(ControllerId(controller), resource.try_into()?)),
928            fnet_filter::Event::Idle(fnet_filter::Empty {}) => Ok(Self::Idle),
929            fnet_filter::Event::Added(fnet_filter::AddedResource { controller, resource }) => {
930                Ok(Self::Added(ControllerId(controller), resource.try_into()?))
931            }
932            fnet_filter::Event::Removed(fnet_filter::RemovedResource { controller, resource }) => {
933                Ok(Self::Removed(ControllerId(controller), resource.try_into()?))
934            }
935            fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}) => Ok(Self::EndOfUpdate),
936            fnet_filter::Event::__SourceBreaking { .. } => {
937                Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
938            }
939        }
940    }
941}
942
943/// Filter watcher creation errors.
944#[derive(Debug, Error)]
945pub enum WatcherCreationError {
946    #[error("failed to create filter watcher proxy: {0}")]
947    CreateProxy(fidl::Error),
948    #[error("failed to get filter watcher: {0}")]
949    GetWatcher(fidl::Error),
950}
951
952/// Filter watcher `Watch` errors.
953#[derive(Debug, Error)]
954pub enum WatchError {
955    /// The call to `Watch` returned a FIDL error.
956    #[error("the call to `Watch()` failed: {0}")]
957    Fidl(fidl::Error),
958    /// The event returned by `Watch` encountered a conversion error.
959    #[error("failed to convert event returned by `Watch()`: {0}")]
960    Conversion(FidlConversionError),
961    /// The server returned an empty batch of events.
962    #[error("the call to `Watch()` returned an empty batch of events")]
963    EmptyEventBatch,
964}
965
966/// Connects to the watcher protocol and converts the Hanging-Get style API into
967/// an Event stream.
968///
969/// Each call to `Watch` returns a batch of events, which are flattened into a
970/// single stream. If an error is encountered while calling `Watch` or while
971/// converting the event, the stream is immediately terminated.
972pub fn event_stream_from_state(
973    state: fnet_filter::StateProxy,
974) -> Result<impl Stream<Item = Result<Event, WatchError>>, WatcherCreationError> {
975    let (watcher, server_end) = fidl::endpoints::create_proxy::<fnet_filter::WatcherMarker>();
976    state
977        .get_watcher(&fnet_filter::WatcherOptions::default(), server_end)
978        .map_err(WatcherCreationError::GetWatcher)?;
979
980    let stream = futures::stream::try_unfold(watcher, |watcher| async {
981        let events = watcher.watch().await.map_err(WatchError::Fidl)?;
982        if events.is_empty() {
983            return Err(WatchError::EmptyEventBatch);
984        }
985
986        let event_stream = futures::stream::iter(events).map(Ok).and_then(|event| {
987            futures::future::ready(event.try_into().map_err(WatchError::Conversion))
988        });
989        Ok(Some((event_stream, watcher)))
990    })
991    .try_flatten();
992
993    Ok(stream)
994}
995
996/// Errors returned by [`get_existing_resources`].
997#[derive(Debug, Error)]
998pub enum GetExistingResourcesError {
999    /// There was an error in the event stream.
1000    #[error("there was an error in the event stream: {0}")]
1001    ErrorInStream(WatchError),
1002    /// There was an unexpected event in the event stream. Only `existing` or
1003    /// `idle` events are expected.
1004    #[error("there was an unexpected event in the event stream: {0:?}")]
1005    UnexpectedEvent(Event),
1006    /// A duplicate existing resource was reported in the event stream.
1007    #[error("a duplicate existing resource was reported")]
1008    DuplicateResource(Resource),
1009    /// The event stream unexpectedly ended.
1010    #[error("the event stream unexpectedly ended")]
1011    StreamEnded,
1012}
1013
1014/// A trait for types holding filtering state that can be updated by change
1015/// events.
1016pub trait Update {
1017    /// Add the resource to the specified controller's state.
1018    ///
1019    /// Optionally returns a resource that has already been added to the
1020    /// controller with the same [`ResourceId`].
1021    fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource>;
1022
1023    /// Remove the resource from the specified controller's state.
1024    ///
1025    /// Returns the removed resource, if present.
1026    fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource>;
1027}
1028
1029impl Update for HashMap<ControllerId, HashMap<ResourceId, Resource>> {
1030    fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource> {
1031        self.entry(controller).or_default().insert(resource.id(), resource)
1032    }
1033
1034    fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource> {
1035        self.get_mut(&controller)?.remove(resource)
1036    }
1037}
1038
1039/// Collects all `existing` events from the stream, stopping once the `idle`
1040/// event is observed.
1041pub async fn get_existing_resources<C: Update + Default>(
1042    stream: impl Stream<Item = Result<Event, WatchError>>,
1043) -> Result<C, GetExistingResourcesError> {
1044    async_utils::fold::fold_while(
1045        stream,
1046        Ok(C::default()),
1047        |resources: Result<C, GetExistingResourcesError>, event| {
1048            let mut resources =
1049                resources.expect("`resources` must be `Ok`, because we stop folding on err");
1050            futures::future::ready(match event {
1051                Err(e) => FoldWhile::Done(Err(GetExistingResourcesError::ErrorInStream(e))),
1052                Ok(e) => match e {
1053                    Event::Existing(controller, resource) => {
1054                        if let Some(resource) = resources.add(controller, resource) {
1055                            FoldWhile::Done(Err(GetExistingResourcesError::DuplicateResource(
1056                                resource,
1057                            )))
1058                        } else {
1059                            FoldWhile::Continue(Ok(resources))
1060                        }
1061                    }
1062                    Event::Idle => FoldWhile::Done(Ok(resources)),
1063                    e @ (Event::Added(_, _) | Event::Removed(_, _) | Event::EndOfUpdate) => {
1064                        FoldWhile::Done(Err(GetExistingResourcesError::UnexpectedEvent(e)))
1065                    }
1066                },
1067            })
1068        },
1069    )
1070    .await
1071    .short_circuited()
1072    .map_err(|_resources| GetExistingResourcesError::StreamEnded)?
1073}
1074
1075/// Errors returned by [`wait_for_condition`].
1076#[derive(Debug, Error)]
1077pub enum WaitForConditionError {
1078    /// There was an error in the event stream.
1079    #[error("there was an error in the event stream: {0}")]
1080    ErrorInStream(WatchError),
1081    /// There was an `Added` event for an already existing resource.
1082    #[error("observed an added event for an already existing resource: {0:?}")]
1083    AddedAlreadyExisting(Resource),
1084    /// There was a `Removed` event for a non-existent resource.
1085    #[error("observed a removed event for a non-existent resource: {0:?}")]
1086    RemovedNonExistent(ResourceId),
1087    /// The event stream unexpectedly ended.
1088    #[error("the event stream unexpectedly ended")]
1089    StreamEnded,
1090}
1091
1092/// Wait for a condition on filtering state to be satisfied.
1093///
1094/// With the given `initial_state`, take events from `event_stream` and update
1095/// the state, calling `predicate` whenever the state changes. When predicates
1096/// returns `True` yield `Ok(())`.
1097pub async fn wait_for_condition<
1098    C: Update,
1099    S: Stream<Item = Result<Event, WatchError>>,
1100    F: Fn(&C) -> bool,
1101>(
1102    event_stream: S,
1103    initial_state: &mut C,
1104    predicate: F,
1105) -> Result<(), WaitForConditionError> {
1106    async_utils::fold::try_fold_while(
1107        event_stream.map_err(WaitForConditionError::ErrorInStream),
1108        initial_state,
1109        |resources: &mut C, event| {
1110            futures::future::ready(match event {
1111                Event::Existing(controller, resource) | Event::Added(controller, resource) => {
1112                    if let Some(resource) = resources.add(controller, resource) {
1113                        Err(WaitForConditionError::AddedAlreadyExisting(resource))
1114                    } else {
1115                        Ok(FoldWhile::Continue(resources))
1116                    }
1117                }
1118                Event::Removed(controller, resource) => resources
1119                    .remove(controller, &resource)
1120                    .map(|_| FoldWhile::Continue(resources))
1121                    .ok_or(WaitForConditionError::RemovedNonExistent(resource)),
1122                // Wait until a transactional update has been completed to call
1123                // the predicate so it's not run against partially-updated
1124                // state.
1125                Event::Idle | Event::EndOfUpdate => {
1126                    if predicate(&resources) {
1127                        Ok(FoldWhile::Done(()))
1128                    } else {
1129                        Ok(FoldWhile::Continue(resources))
1130                    }
1131                }
1132            })
1133        },
1134    )
1135    .await?
1136    .short_circuited()
1137    .map_err(|_resources: &mut C| WaitForConditionError::StreamEnded)
1138}
1139
1140/// Namespace controller creation errors.
1141#[derive(Debug, Error)]
1142pub enum ControllerCreationError {
1143    #[error("failed to create namespace controller proxy: {0}")]
1144    CreateProxy(fidl::Error),
1145    #[error("failed to open namespace controller: {0}")]
1146    OpenController(fidl::Error),
1147    #[error("server did not emit OnIdAssigned event")]
1148    NoIdAssigned,
1149    #[error("failed to observe ID assignment event: {0}")]
1150    IdAssignment(fidl::Error),
1151}
1152
1153/// Errors for individual changes pushed.
1154///
1155/// Extension type for the error variants of [`fnet_filter::ChangeValidationError`].
1156#[derive(Debug, Error, PartialEq)]
1157pub enum ChangeValidationError {
1158    #[error("change contains a resource that is missing a required field")]
1159    MissingRequiredField,
1160    #[error("rule specifies an invalid interface matcher")]
1161    InvalidInterfaceMatcher,
1162    #[error("rule specifies an invalid address matcher")]
1163    InvalidAddressMatcher,
1164    #[error("rule specifies an invalid port matcher")]
1165    InvalidPortMatcher,
1166    #[error("rule specifies an invalid transparent proxy action")]
1167    InvalidTransparentProxyAction,
1168    #[error("rule specifies an invalid NAT action")]
1169    InvalidNatAction,
1170    #[error("rule specifies an invalid port range")]
1171    InvalidPortRange,
1172}
1173
1174impl TryFrom<fnet_filter::ChangeValidationError> for ChangeValidationError {
1175    type Error = FidlConversionError;
1176
1177    fn try_from(error: fnet_filter::ChangeValidationError) -> Result<Self, Self::Error> {
1178        match error {
1179            fnet_filter::ChangeValidationError::MissingRequiredField => {
1180                Ok(Self::MissingRequiredField)
1181            }
1182            fnet_filter::ChangeValidationError::InvalidInterfaceMatcher => {
1183                Ok(Self::InvalidInterfaceMatcher)
1184            }
1185            fnet_filter::ChangeValidationError::InvalidAddressMatcher => {
1186                Ok(Self::InvalidAddressMatcher)
1187            }
1188            fnet_filter::ChangeValidationError::InvalidPortMatcher => Ok(Self::InvalidPortMatcher),
1189            fnet_filter::ChangeValidationError::InvalidTransparentProxyAction => {
1190                Ok(Self::InvalidTransparentProxyAction)
1191            }
1192            fnet_filter::ChangeValidationError::InvalidNatAction => Ok(Self::InvalidNatAction),
1193            fnet_filter::ChangeValidationError::InvalidPortRange => Ok(Self::InvalidPortRange),
1194            fnet_filter::ChangeValidationError::Ok
1195            | fnet_filter::ChangeValidationError::NotReached => {
1196                Err(FidlConversionError::NotAnError)
1197            }
1198            fnet_filter::ChangeValidationError::__SourceBreaking { unknown_ordinal: _ } => {
1199                Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_ERROR))
1200            }
1201        }
1202    }
1203}
1204
1205#[derive(Debug, Error)]
1206pub enum RegisterEbpfProgramError {
1207    #[error("failed to call FIDL method: {0}")]
1208    CallMethod(fidl::Error),
1209
1210    #[error("failed to link the program")]
1211    LinkFailed,
1212
1213    #[error("failed to initialize a map")]
1214    MapFailed,
1215
1216    #[error("the program is already registered")]
1217    AlreadyRegistered,
1218
1219    #[error("the request is missing a required field")]
1220    MissingRequiredField,
1221}
1222
1223impl From<fnet_filter::RegisterEbpfProgramError> for RegisterEbpfProgramError {
1224    fn from(error: fnet_filter::RegisterEbpfProgramError) -> Self {
1225        match error {
1226            fnet_filter::RegisterEbpfProgramError::LinkFailed => Self::LinkFailed,
1227            fnet_filter::RegisterEbpfProgramError::MapFailed => Self::MapFailed,
1228            fnet_filter::RegisterEbpfProgramError::AlreadyRegistered => Self::AlreadyRegistered,
1229            fnet_filter::RegisterEbpfProgramError::MissingRequiredField => {
1230                Self::MissingRequiredField
1231            }
1232        }
1233    }
1234}
1235
1236/// Errors for the NamespaceController.PushChanges method.
1237#[derive(Debug, Error)]
1238pub enum PushChangesError {
1239    #[error("failed to call FIDL method: {0}")]
1240    CallMethod(fidl::Error),
1241    #[error("too many changes were pushed to the server")]
1242    TooManyChanges,
1243    #[error("invalid change(s) pushed: {0:?}")]
1244    ErrorOnChange(Vec<(Change, ChangeValidationError)>),
1245    #[error("unknown FIDL type: {0}")]
1246    FidlConversion(#[from] FidlConversionError),
1247}
1248
1249/// Errors for individual changes committed.
1250///
1251/// Extension type for the error variants of [`fnet_filter::CommitError`].
1252#[derive(Debug, Error, PartialEq)]
1253pub enum ChangeCommitError {
1254    #[error("the change referred to an unknown namespace")]
1255    NamespaceNotFound,
1256    #[error("the change referred to an unknown routine")]
1257    RoutineNotFound,
1258    #[error("the change referred to an unknown rule")]
1259    RuleNotFound,
1260    #[error("the specified resource already exists")]
1261    AlreadyExists,
1262    #[error("the change includes a rule that jumps to an installed routine")]
1263    TargetRoutineIsInstalled,
1264}
1265
1266impl TryFrom<fnet_filter::CommitError> for ChangeCommitError {
1267    type Error = FidlConversionError;
1268
1269    fn try_from(error: fnet_filter::CommitError) -> Result<Self, Self::Error> {
1270        match error {
1271            fnet_filter::CommitError::NamespaceNotFound => Ok(Self::NamespaceNotFound),
1272            fnet_filter::CommitError::RoutineNotFound => Ok(Self::RoutineNotFound),
1273            fnet_filter::CommitError::RuleNotFound => Ok(Self::RuleNotFound),
1274            fnet_filter::CommitError::AlreadyExists => Ok(Self::AlreadyExists),
1275            fnet_filter::CommitError::TargetRoutineIsInstalled => {
1276                Ok(Self::TargetRoutineIsInstalled)
1277            }
1278            fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1279                Err(FidlConversionError::NotAnError)
1280            }
1281            fnet_filter::CommitError::__SourceBreaking { unknown_ordinal: _ } => {
1282                Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR))
1283            }
1284        }
1285    }
1286}
1287
1288/// Errors for the NamespaceController.Commit method.
1289#[derive(Debug, Error)]
1290pub enum CommitError {
1291    #[error("failed to call FIDL method: {0}")]
1292    CallMethod(fidl::Error),
1293    #[error("rule has a matcher that is unavailable in its context: {0:?}")]
1294    RuleWithInvalidMatcher(RuleId),
1295    #[error("rule has an action that is invalid for its routine: {0:?}")]
1296    RuleWithInvalidAction(RuleId),
1297    #[error("rule has a TransparentProxy action but not a valid transport protocol matcher: {0:?}")]
1298    TransparentProxyWithInvalidMatcher(RuleId),
1299    #[error(
1300        "rule has a Redirect action that specifies a destination port but not a valid transport \
1301        protocol matcher: {0:?}"
1302    )]
1303    RedirectWithInvalidMatcher(RuleId),
1304    #[error(
1305        "rule has a Masquerade action that specifies a source port but not a valid transport \
1306        protocol matcher: {0:?}"
1307    )]
1308    MasqueradeWithInvalidMatcher(RuleId),
1309    #[error("routine forms a cycle {0:?}")]
1310    CyclicalRoutineGraph(RoutineId),
1311    #[error("invalid change was pushed: {0:?}")]
1312    ErrorOnChange(Vec<(Change, ChangeCommitError)>),
1313    #[error("unknown FIDL type: {0}")]
1314    FidlConversion(#[from] FidlConversionError),
1315}
1316
1317/// Extension type for [`fnet_filter::Change`].
1318#[derive(Debug, Clone, PartialEq)]
1319pub enum Change {
1320    Create(Resource),
1321    Remove(ResourceId),
1322}
1323
1324impl From<Change> for fnet_filter::Change {
1325    fn from(change: Change) -> Self {
1326        match change {
1327            Change::Create(resource) => Self::Create(resource.into()),
1328            Change::Remove(resource) => Self::Remove(resource.into()),
1329        }
1330    }
1331}
1332
1333impl TryFrom<fnet_filter::Change> for Change {
1334    type Error = FidlConversionError;
1335
1336    fn try_from(change: fnet_filter::Change) -> Result<Self, Self::Error> {
1337        match change {
1338            fnet_filter::Change::Create(resource) => Ok(Self::Create(resource.try_into()?)),
1339            fnet_filter::Change::Remove(resource) => Ok(Self::Remove(resource.try_into()?)),
1340            fnet_filter::Change::__SourceBreaking { .. } => {
1341                Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1342            }
1343        }
1344    }
1345}
1346
1347/// A controller for filtering state.
1348pub struct Controller {
1349    controller: fnet_filter::NamespaceControllerProxy,
1350    // The client provides an ID when creating a new controller, but the server
1351    // may need to assign a different ID to avoid conflicts; either way, the
1352    // server informs the client of the final `ControllerId` on creation.
1353    id: ControllerId,
1354    // Changes that have been pushed to the server but not yet committed. This
1355    // allows the `Controller` to report more informative errors by correlating
1356    // error codes with particular changes.
1357    pending_changes: Vec<Change>,
1358}
1359
1360impl Controller {
1361    pub async fn new_root(
1362        root: &fnet_root::FilterProxy,
1363        ControllerId(id): &ControllerId,
1364    ) -> Result<Self, ControllerCreationError> {
1365        let (controller, server_end) = fidl::endpoints::create_proxy();
1366        root.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1367
1368        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1369            .take_event_stream()
1370            .next()
1371            .await
1372            .ok_or(ControllerCreationError::NoIdAssigned)?
1373            .map_err(ControllerCreationError::IdAssignment)?;
1374        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1375    }
1376
1377    /// Creates a new `Controller`.
1378    ///
1379    /// Note that the provided `ControllerId` may need to be modified server-
1380    /// side to avoid collisions; to obtain the final ID assigned to the
1381    /// `Controller`, use the `id` method.
1382    pub async fn new(
1383        control: &fnet_filter::ControlProxy,
1384        ControllerId(id): &ControllerId,
1385    ) -> Result<Self, ControllerCreationError> {
1386        let (controller, server_end) = fidl::endpoints::create_proxy();
1387        control.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1388
1389        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1390            .take_event_stream()
1391            .next()
1392            .await
1393            .ok_or(ControllerCreationError::NoIdAssigned)?
1394            .map_err(ControllerCreationError::IdAssignment)?;
1395        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1396    }
1397
1398    pub fn id(&self) -> &ControllerId {
1399        &self.id
1400    }
1401
1402    pub async fn register_ebpf_program(
1403        &mut self,
1404        handle: febpf::ProgramHandle,
1405        program: febpf::VerifiedProgram,
1406    ) -> Result<(), RegisterEbpfProgramError> {
1407        self.controller
1408            .register_ebpf_program(handle, program)
1409            .await
1410            .map_err(RegisterEbpfProgramError::CallMethod)?
1411            .map_err(RegisterEbpfProgramError::from)
1412    }
1413
1414    pub async fn push_changes(&mut self, changes: Vec<Change>) -> Result<(), PushChangesError> {
1415        let fidl_changes = changes.iter().cloned().map(Into::into).collect::<Vec<_>>();
1416        let result = self
1417            .controller
1418            .push_changes(&fidl_changes)
1419            .await
1420            .map_err(PushChangesError::CallMethod)?;
1421        handle_change_validation_result(result, &changes)?;
1422        // Maintain a client-side copy of the pending changes we've pushed to
1423        // the server in order to provide better error messages if a commit
1424        // fails.
1425        self.pending_changes.extend(changes);
1426        Ok(())
1427    }
1428
1429    async fn commit_with_options(
1430        &mut self,
1431        options: fnet_filter::CommitOptions,
1432    ) -> Result<(), CommitError> {
1433        let committed_changes = std::mem::take(&mut self.pending_changes);
1434        let result = self.controller.commit(options).await.map_err(CommitError::CallMethod)?;
1435        handle_commit_result(result, committed_changes)
1436    }
1437
1438    pub async fn commit(&mut self) -> Result<(), CommitError> {
1439        self.commit_with_options(fnet_filter::CommitOptions::default()).await
1440    }
1441
1442    pub async fn commit_idempotent(&mut self) -> Result<(), CommitError> {
1443        self.commit_with_options(fnet_filter::CommitOptions {
1444            idempotent: Some(true),
1445            __source_breaking: SourceBreaking,
1446        })
1447        .await
1448    }
1449}
1450
1451pub(crate) fn handle_change_validation_result(
1452    change_validation_result: fnet_filter::ChangeValidationResult,
1453    changes: &Vec<Change>,
1454) -> Result<(), PushChangesError> {
1455    match change_validation_result {
1456        fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}) => Ok(()),
1457        fnet_filter::ChangeValidationResult::TooManyChanges(fnet_filter::Empty {}) => {
1458            Err(PushChangesError::TooManyChanges)
1459        }
1460        fnet_filter::ChangeValidationResult::ErrorOnChange(results) => {
1461            let errors: Result<_, PushChangesError> =
1462                changes.iter().zip(results).try_fold(Vec::new(), |mut errors, (change, result)| {
1463                    match result {
1464                        fnet_filter::ChangeValidationError::Ok
1465                        | fnet_filter::ChangeValidationError::NotReached => Ok(errors),
1466                        error @ (fnet_filter::ChangeValidationError::MissingRequiredField
1467                        | fnet_filter::ChangeValidationError::InvalidInterfaceMatcher
1468                        | fnet_filter::ChangeValidationError::InvalidAddressMatcher
1469                        | fnet_filter::ChangeValidationError::InvalidPortMatcher
1470                        | fnet_filter::ChangeValidationError::InvalidTransparentProxyAction
1471                        | fnet_filter::ChangeValidationError::InvalidNatAction
1472                        | fnet_filter::ChangeValidationError::InvalidPortRange) => {
1473                            let error = error
1474                                .try_into()
1475                                .expect("`Ok` and `NotReached` are handled in another arm");
1476                            errors.push((change.clone(), error));
1477                            Ok(errors)
1478                        }
1479                        fnet_filter::ChangeValidationError::__SourceBreaking { .. } => {
1480                            Err(FidlConversionError::UnknownUnionVariant(
1481                                type_names::CHANGE_VALIDATION_ERROR,
1482                            )
1483                            .into())
1484                        }
1485                    }
1486                });
1487            Err(PushChangesError::ErrorOnChange(errors?))
1488        }
1489        fnet_filter::ChangeValidationResult::__SourceBreaking { .. } => {
1490            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_RESULT)
1491                .into())
1492        }
1493    }
1494}
1495
1496pub(crate) fn handle_commit_result(
1497    commit_result: fnet_filter::CommitResult,
1498    committed_changes: Vec<Change>,
1499) -> Result<(), CommitError> {
1500    match commit_result {
1501        fnet_filter::CommitResult::Ok(fnet_filter::Empty {}) => Ok(()),
1502        fnet_filter::CommitResult::RuleWithInvalidMatcher(rule_id) => {
1503            Err(CommitError::RuleWithInvalidMatcher(rule_id.into()))
1504        }
1505        fnet_filter::CommitResult::RuleWithInvalidAction(rule_id) => {
1506            Err(CommitError::RuleWithInvalidAction(rule_id.into()))
1507        }
1508        fnet_filter::CommitResult::TransparentProxyWithInvalidMatcher(rule_id) => {
1509            Err(CommitError::TransparentProxyWithInvalidMatcher(rule_id.into()))
1510        }
1511        fnet_filter::CommitResult::RedirectWithInvalidMatcher(rule_id) => {
1512            Err(CommitError::RedirectWithInvalidMatcher(rule_id.into()))
1513        }
1514        fnet_filter::CommitResult::MasqueradeWithInvalidMatcher(rule_id) => {
1515            Err(CommitError::MasqueradeWithInvalidMatcher(rule_id.into()))
1516        }
1517        fnet_filter::CommitResult::CyclicalRoutineGraph(routine_id) => {
1518            Err(CommitError::CyclicalRoutineGraph(routine_id.into()))
1519        }
1520        fnet_filter::CommitResult::ErrorOnChange(results) => {
1521            let errors: Result<_, CommitError> = committed_changes
1522                .into_iter()
1523                .zip(results)
1524                .try_fold(Vec::new(), |mut errors, (change, result)| match result {
1525                    fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1526                        Ok(errors)
1527                    }
1528                    error @ (fnet_filter::CommitError::NamespaceNotFound
1529                    | fnet_filter::CommitError::RoutineNotFound
1530                    | fnet_filter::CommitError::RuleNotFound
1531                    | fnet_filter::CommitError::AlreadyExists
1532                    | fnet_filter::CommitError::TargetRoutineIsInstalled) => {
1533                        let error = error
1534                            .try_into()
1535                            .expect("`Ok` and `NotReached` are handled in another arm");
1536                        errors.push((change, error));
1537                        Ok(errors)
1538                    }
1539                    fnet_filter::CommitError::__SourceBreaking { .. } => {
1540                        Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR)
1541                            .into())
1542                    }
1543                });
1544            Err(CommitError::ErrorOnChange(errors?))
1545        }
1546        fnet_filter::CommitResult::__SourceBreaking { .. } => {
1547            Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_RESULT).into())
1548        }
1549    }
1550}
1551
1552#[cfg(test)]
1553mod tests {
1554
1555    use assert_matches::assert_matches;
1556    use fidl_fuchsia_net_matchers as fnet_matchers;
1557    use futures::channel::mpsc;
1558    use futures::task::Poll;
1559    use futures::{FutureExt as _, SinkExt as _};
1560    use test_case::test_case;
1561
1562    use {
1563        fidl_fuchsia_hardware_network as fhardware_network,
1564        fidl_fuchsia_net_interfaces as fnet_interfaces,
1565    };
1566
1567    use super::*;
1568
1569    #[test_case(
1570        fnet_filter::ResourceId::Namespace(String::from("namespace")),
1571        ResourceId::Namespace(NamespaceId(String::from("namespace")));
1572        "NamespaceId"
1573    )]
1574    #[test_case(fnet_filter::Domain::Ipv4, Domain::Ipv4; "Domain")]
1575    #[test_case(
1576        fnet_filter::Namespace {
1577            id: Some(String::from("namespace")),
1578            domain: Some(fnet_filter::Domain::Ipv4),
1579            ..Default::default()
1580        },
1581        Namespace { id: NamespaceId(String::from("namespace")), domain: Domain::Ipv4 };
1582        "Namespace"
1583    )]
1584    #[test_case(fnet_filter::IpInstallationHook::Egress, IpHook::Egress; "IpHook")]
1585    #[test_case(fnet_filter::NatInstallationHook::Egress, NatHook::Egress; "NatHook")]
1586    #[test_case(
1587        fnet_filter::InstalledIpRoutine {
1588            hook: Some(fnet_filter::IpInstallationHook::Egress),
1589            priority: Some(1),
1590            ..Default::default()
1591        },
1592        InstalledIpRoutine {
1593            hook: IpHook::Egress,
1594            priority: 1,
1595        };
1596        "InstalledIpRoutine"
1597    )]
1598    #[test_case(
1599        fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
1600            installation: Some(fnet_filter::InstalledIpRoutine {
1601                hook: Some(fnet_filter::IpInstallationHook::LocalEgress),
1602                priority: Some(1),
1603                ..Default::default()
1604            }),
1605            ..Default::default()
1606        }),
1607        RoutineType::Ip(Some(InstalledIpRoutine { hook: IpHook::LocalEgress, priority: 1 }));
1608        "RoutineType"
1609    )]
1610    #[test_case(
1611        fnet_filter::Routine {
1612            id: Some(fnet_filter::RoutineId {
1613                namespace: String::from("namespace"),
1614                name: String::from("routine"),
1615            }),
1616            type_: Some(fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine::default())),
1617            ..Default::default()
1618        },
1619        Routine {
1620            id: RoutineId {
1621                namespace: NamespaceId(String::from("namespace")),
1622                name: String::from("routine"),
1623            },
1624            routine_type: RoutineType::Nat(None),
1625        };
1626        "Routine"
1627    )]
1628    #[test_case(
1629        fnet_filter::Matchers {
1630            in_interface: Some(fnet_matchers::Interface::Name(String::from("wlan"))),
1631            transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
1632                src_port: None,
1633                dst_port: Some(fnet_matchers::Port { start: 22, end: 22, invert: false }),
1634                ..Default::default()
1635            })),
1636            ..Default::default()
1637        },
1638        Matchers {
1639            in_interface: Some(fnet_matchers_ext::Interface::Name(String::from("wlan"))),
1640            transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
1641                src_port: None,
1642                dst_port: Some(fnet_matchers_ext::Port::new(22, 22, false).unwrap()),
1643            }),
1644            ..Default::default()
1645        };
1646        "Matchers"
1647    )]
1648    #[test_case(
1649        fnet_filter::Action::Accept(fnet_filter::Empty {}),
1650        Action::Accept;
1651        "Action"
1652    )]
1653    #[test_case(
1654        fnet_filter::Rule {
1655            id: fnet_filter::RuleId {
1656                routine: fnet_filter::RoutineId {
1657                    namespace: String::from("namespace"),
1658                    name: String::from("routine"),
1659                },
1660                index: 1,
1661            },
1662            matchers: fnet_filter::Matchers {
1663                transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Icmp(
1664                    fnet_matchers::IcmpPacket::default()
1665                )),
1666                ..Default::default()
1667            },
1668            action: fnet_filter::Action::Drop(fnet_filter::Empty {}),
1669        },
1670        Rule {
1671            id: RuleId {
1672                routine: RoutineId {
1673                    namespace: NamespaceId(String::from("namespace")),
1674                    name: String::from("routine"),
1675                },
1676                index: 1,
1677            },
1678            matchers: Matchers {
1679                transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
1680                ..Default::default()
1681            },
1682            action: Action::Drop,
1683        };
1684        "Rule"
1685    )]
1686    #[test_case(
1687        fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1688            id: Some(String::from("namespace")),
1689            domain: Some(fnet_filter::Domain::Ipv4),
1690            ..Default::default()
1691        }),
1692        Resource::Namespace(Namespace {
1693            id: NamespaceId(String::from("namespace")),
1694            domain: Domain::Ipv4
1695        });
1696        "Resource"
1697    )]
1698    #[test_case(
1699        fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}),
1700        Event::EndOfUpdate;
1701        "Event"
1702    )]
1703    #[test_case(
1704        fnet_filter::Change::Remove(fnet_filter::ResourceId::Namespace(String::from("namespace"))),
1705        Change::Remove(ResourceId::Namespace(NamespaceId(String::from("namespace"))));
1706        "Change"
1707    )]
1708    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1709    where
1710        E: TryFrom<F> + Clone + Debug + PartialEq,
1711        <E as TryFrom<F>>::Error: Debug + PartialEq,
1712        F: From<E> + Clone + Debug + PartialEq,
1713    {
1714        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1715        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1716    }
1717
1718    #[test]
1719    fn resource_id_try_from_unknown_variant() {
1720        assert_eq!(
1721            ResourceId::try_from(fnet_filter::ResourceId::__SourceBreaking { unknown_ordinal: 0 }),
1722            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
1723        );
1724    }
1725
1726    #[test]
1727    fn domain_try_from_unknown_variant() {
1728        assert_eq!(
1729            Domain::try_from(fnet_filter::Domain::__SourceBreaking { unknown_ordinal: 0 }),
1730            Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
1731        );
1732    }
1733
1734    #[test]
1735    fn namespace_try_from_missing_properties() {
1736        assert_eq!(
1737            Namespace::try_from(fnet_filter::Namespace {
1738                id: None,
1739                domain: Some(fnet_filter::Domain::Ipv4),
1740                ..Default::default()
1741            }),
1742            Err(FidlConversionError::MissingNamespaceId)
1743        );
1744        assert_eq!(
1745            Namespace::try_from(fnet_filter::Namespace {
1746                id: Some(String::from("namespace")),
1747                domain: None,
1748                ..Default::default()
1749            }),
1750            Err(FidlConversionError::MissingNamespaceDomain)
1751        );
1752    }
1753
1754    #[test]
1755    fn ip_installation_hook_try_from_unknown_variant() {
1756        assert_eq!(
1757            IpHook::try_from(fnet_filter::IpInstallationHook::__SourceBreaking {
1758                unknown_ordinal: 0
1759            }),
1760            Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
1761        );
1762    }
1763
1764    #[test]
1765    fn nat_installation_hook_try_from_unknown_variant() {
1766        assert_eq!(
1767            NatHook::try_from(fnet_filter::NatInstallationHook::__SourceBreaking {
1768                unknown_ordinal: 0
1769            }),
1770            Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
1771        );
1772    }
1773
1774    #[test]
1775    fn installed_ip_routine_try_from_missing_hook() {
1776        assert_eq!(
1777            InstalledIpRoutine::try_from(fnet_filter::InstalledIpRoutine {
1778                hook: None,
1779                ..Default::default()
1780            }),
1781            Err(FidlConversionError::MissingIpInstallationHook)
1782        );
1783    }
1784
1785    #[test]
1786    fn installed_nat_routine_try_from_missing_hook() {
1787        assert_eq!(
1788            InstalledNatRoutine::try_from(fnet_filter::InstalledNatRoutine {
1789                hook: None,
1790                ..Default::default()
1791            }),
1792            Err(FidlConversionError::MissingNatInstallationHook)
1793        );
1794    }
1795
1796    #[test]
1797    fn routine_type_try_from_unknown_variant() {
1798        assert_eq!(
1799            RoutineType::try_from(fnet_filter::RoutineType::__SourceBreaking {
1800                unknown_ordinal: 0
1801            }),
1802            Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
1803        );
1804    }
1805
1806    #[test]
1807    fn routine_try_from_missing_properties() {
1808        assert_eq!(
1809            Routine::try_from(fnet_filter::Routine { id: None, ..Default::default() }),
1810            Err(FidlConversionError::MissingRoutineId)
1811        );
1812        assert_eq!(
1813            Routine::try_from(fnet_filter::Routine {
1814                id: Some(fnet_filter::RoutineId {
1815                    namespace: String::from("namespace"),
1816                    name: String::from("routine"),
1817                }),
1818                type_: None,
1819                ..Default::default()
1820            }),
1821            Err(FidlConversionError::MissingRoutineType)
1822        );
1823    }
1824
1825    #[test_case(
1826        fnet_matchers_ext::PortError::InvalidPortRange =>
1827        FidlConversionError::InvalidPortMatcherRange
1828    )]
1829    #[test_case(
1830        fnet_matchers_ext::InterfaceError::ZeroId =>
1831        FidlConversionError::ZeroInterfaceId
1832    )]
1833    #[test_case(
1834        fnet_matchers_ext::InterfaceError::UnknownUnionVariant =>
1835        FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
1836    )]
1837    #[test_case(
1838        {
1839            let invalid_port_class = fnet_interfaces::PortClass::__SourceBreaking {
1840                unknown_ordinal: 0
1841            };
1842            let error = fnet_interfaces_ext::PortClass::try_from(
1843                invalid_port_class
1844            ).unwrap_err();
1845            fnet_matchers_ext::InterfaceError::UnknownPortClass(error)
1846        } =>
1847        FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
1848    )]
1849    #[test_case(
1850        {
1851            let invalid_port_class = fhardware_network::PortClass::__SourceBreaking {
1852                unknown_ordinal: 0
1853            };
1854            let error = fnet_interfaces_ext::PortClass::try_from(
1855                invalid_port_class
1856            ).unwrap_err();
1857            fnet_matchers_ext::InterfaceError::UnknownPortClass(
1858                fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(error))
1859        } =>
1860        FidlConversionError::UnknownUnionVariant(type_names::HARDWARE_NETWORK_PORT_CLASS)
1861    )]
1862    #[test_case(
1863        fnet_matchers_ext::SubnetError::PrefixTooLong =>
1864        FidlConversionError::SubnetPrefixTooLong
1865    )]
1866    #[test_case(
1867        fnet_matchers_ext::SubnetError::HostBitsSet =>
1868        FidlConversionError::SubnetHostBitsSet
1869    )]
1870    #[test_case(
1871        fnet_matchers_ext::AddressRangeError::Invalid =>
1872        FidlConversionError::InvalidAddressRange
1873    )]
1874    #[test_case(
1875        fnet_matchers_ext::AddressRangeError::FamilyMismatch =>
1876        FidlConversionError::AddressRangeFamilyMismatch
1877    )]
1878    #[test_case(
1879        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1880            fnet_matchers_ext::SubnetError::PrefixTooLong) =>
1881        FidlConversionError::SubnetPrefixTooLong
1882    )]
1883    #[test_case(
1884        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1885            fnet_matchers_ext::SubnetError::HostBitsSet) =>
1886        FidlConversionError::SubnetHostBitsSet
1887    )]
1888    #[test_case(
1889        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1890            fnet_matchers_ext::AddressRangeError::Invalid) =>
1891        FidlConversionError::InvalidAddressRange
1892    )]
1893    #[test_case(
1894        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1895            fnet_matchers_ext::AddressRangeError::FamilyMismatch) =>
1896        FidlConversionError::AddressRangeFamilyMismatch
1897    )]
1898    #[test_case(
1899        fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant =>
1900        FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1901    )]
1902    #[test_case(
1903        fnet_matchers_ext::AddressError::AddressMatcherType(
1904            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1905                fnet_matchers_ext::SubnetError::PrefixTooLong)) =>
1906        FidlConversionError::SubnetPrefixTooLong
1907    )]
1908    #[test_case(
1909        fnet_matchers_ext::AddressError::AddressMatcherType(
1910            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1911                fnet_matchers_ext::SubnetError::HostBitsSet)) =>
1912        FidlConversionError::SubnetHostBitsSet
1913    )]
1914    #[test_case(
1915        fnet_matchers_ext::AddressError::AddressMatcherType(
1916            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1917                fnet_matchers_ext::AddressRangeError::Invalid)) =>
1918        FidlConversionError::InvalidAddressRange
1919    )]
1920    #[test_case(
1921        fnet_matchers_ext::AddressError::AddressMatcherType(
1922            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1923                fnet_matchers_ext::AddressRangeError::FamilyMismatch)) =>
1924        FidlConversionError::AddressRangeFamilyMismatch
1925    )]
1926    #[test_case(
1927        fnet_matchers_ext::AddressError::AddressMatcherType(
1928            fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant) =>
1929            FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1930    )]
1931    #[test_case(
1932        fnet_matchers_ext::TransportProtocolError::Port(
1933            fnet_matchers_ext::PortError::InvalidPortRange) =>
1934        FidlConversionError::InvalidPortMatcherRange
1935    )]
1936    #[test_case(
1937        fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant =>
1938            FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
1939    )]
1940    fn fidl_error_from_matcher_error<E: Into<FidlConversionError>>(
1941        error: E,
1942    ) -> FidlConversionError {
1943        error.into()
1944    }
1945
1946    #[test]
1947    fn action_try_from_unknown_variant() {
1948        assert_eq!(
1949            Action::try_from(fnet_filter::Action::__SourceBreaking { unknown_ordinal: 0 }),
1950            Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
1951        );
1952    }
1953
1954    #[test]
1955    fn resource_try_from_unknown_variant() {
1956        assert_eq!(
1957            Resource::try_from(fnet_filter::Resource::__SourceBreaking { unknown_ordinal: 0 }),
1958            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
1959        );
1960    }
1961
1962    #[test]
1963    fn event_try_from_unknown_variant() {
1964        assert_eq!(
1965            Event::try_from(fnet_filter::Event::__SourceBreaking { unknown_ordinal: 0 }),
1966            Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
1967        );
1968    }
1969
1970    #[test]
1971    fn change_try_from_unknown_variant() {
1972        assert_eq!(
1973            Change::try_from(fnet_filter::Change::__SourceBreaking { unknown_ordinal: 0 }),
1974            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1975        );
1976    }
1977
1978    fn test_controller_a() -> ControllerId {
1979        ControllerId(String::from("test-controller-a"))
1980    }
1981
1982    fn test_controller_b() -> ControllerId {
1983        ControllerId(String::from("test-controller-b"))
1984    }
1985
1986    pub(crate) fn test_resource_id() -> ResourceId {
1987        ResourceId::Namespace(NamespaceId(String::from("test-namespace")))
1988    }
1989
1990    pub(crate) fn test_resource() -> Resource {
1991        Resource::Namespace(Namespace {
1992            id: NamespaceId(String::from("test-namespace")),
1993            domain: Domain::AllIp,
1994        })
1995    }
1996
1997    // We can't easily create an invalid resource, so we just pretend and fake
1998    // the server response in tests.
1999    pub(crate) fn pretend_invalid_resource() -> Resource {
2000        Resource::Namespace(Namespace {
2001            id: NamespaceId(String::from("pretend-invalid-namespace")),
2002            domain: Domain::AllIp,
2003        })
2004    }
2005
2006    pub(crate) fn unknown_resource_id() -> ResourceId {
2007        ResourceId::Namespace(NamespaceId(String::from("does-not-exist")))
2008    }
2009
2010    #[fuchsia_async::run_singlethreaded(test)]
2011    async fn event_stream_from_state_conversion_error() {
2012        let (proxy, mut request_stream) =
2013            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2014        let stream = event_stream_from_state(proxy).expect("get event stream");
2015        futures::pin_mut!(stream);
2016
2017        let send_invalid_event = async {
2018            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2019                request_stream
2020                    .next()
2021                    .await
2022                    .expect("client should call state")
2023                    .expect("request should not error");
2024            let fnet_filter::WatcherRequest::Watch { responder } = request
2025                .into_stream()
2026                .next()
2027                .await
2028                .expect("client should call watch")
2029                .expect("request should not error");
2030            responder
2031                .send(&[fnet_filter::Event::Added(fnet_filter::AddedResource {
2032                    controller: String::from("controller"),
2033                    resource: fnet_filter::Resource::Namespace(fnet_filter::Namespace {
2034                        id: None,
2035                        domain: None,
2036                        ..Default::default()
2037                    }),
2038                })])
2039                .expect("send batch with invalid event");
2040        };
2041        let ((), result) = futures::future::join(send_invalid_event, stream.next()).await;
2042        assert_matches!(
2043            result,
2044            Some(Err(WatchError::Conversion(FidlConversionError::MissingNamespaceId)))
2045        );
2046    }
2047
2048    #[fuchsia_async::run_singlethreaded(test)]
2049    async fn event_stream_from_state_empty_event_batch() {
2050        let (proxy, mut request_stream) =
2051            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2052        let stream = event_stream_from_state(proxy).expect("get event stream");
2053        futures::pin_mut!(stream);
2054
2055        let send_empty_batch = async {
2056            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2057                request_stream
2058                    .next()
2059                    .await
2060                    .expect("client should call state")
2061                    .expect("request should not error");
2062            let fnet_filter::WatcherRequest::Watch { responder } = request
2063                .into_stream()
2064                .next()
2065                .await
2066                .expect("client should call watch")
2067                .expect("request should not error");
2068            responder.send(&[]).expect("send empty batch");
2069        };
2070        let ((), result) = futures::future::join(send_empty_batch, stream.next()).await;
2071        assert_matches!(result, Some(Err(WatchError::EmptyEventBatch)));
2072    }
2073
2074    #[fuchsia_async::run_singlethreaded(test)]
2075    async fn get_existing_resources_success() {
2076        let event_stream = futures::stream::iter([
2077            Ok(Event::Existing(test_controller_a(), test_resource())),
2078            Ok(Event::Existing(test_controller_b(), test_resource())),
2079            Ok(Event::Idle),
2080            Ok(Event::Removed(test_controller_a(), test_resource_id())),
2081        ]);
2082        futures::pin_mut!(event_stream);
2083
2084        let existing = get_existing_resources::<HashMap<_, _>>(event_stream.by_ref())
2085            .await
2086            .expect("get existing resources");
2087        assert_eq!(
2088            existing,
2089            HashMap::from([
2090                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2091                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2092            ])
2093        );
2094
2095        let trailing_events = event_stream.collect::<Vec<_>>().await;
2096        assert_matches!(
2097            &trailing_events[..],
2098            [Ok(Event::Removed(controller, resource))] if controller == &test_controller_a() &&
2099                                                           resource == &test_resource_id()
2100        );
2101    }
2102
2103    #[fuchsia_async::run_singlethreaded(test)]
2104    async fn get_existing_resources_error_in_stream() {
2105        let event_stream =
2106            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2107        futures::pin_mut!(event_stream);
2108        assert_matches!(
2109            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2110            Err(GetExistingResourcesError::ErrorInStream(WatchError::EmptyEventBatch))
2111        )
2112    }
2113
2114    #[fuchsia_async::run_singlethreaded(test)]
2115    async fn get_existing_resources_unexpected_event() {
2116        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::EndOfUpdate)));
2117        futures::pin_mut!(event_stream);
2118        assert_matches!(
2119            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2120            Err(GetExistingResourcesError::UnexpectedEvent(Event::EndOfUpdate))
2121        )
2122    }
2123
2124    #[fuchsia_async::run_singlethreaded(test)]
2125    async fn get_existing_resources_duplicate_resource() {
2126        let event_stream = futures::stream::iter([
2127            Ok(Event::Existing(test_controller_a(), test_resource())),
2128            Ok(Event::Existing(test_controller_a(), test_resource())),
2129        ]);
2130        futures::pin_mut!(event_stream);
2131        assert_matches!(
2132            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2133            Err(GetExistingResourcesError::DuplicateResource(resource))
2134                if resource == test_resource()
2135        )
2136    }
2137
2138    #[fuchsia_async::run_singlethreaded(test)]
2139    async fn get_existing_resources_stream_ended() {
2140        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::Existing(
2141            test_controller_a(),
2142            test_resource(),
2143        ))));
2144        futures::pin_mut!(event_stream);
2145        assert_matches!(
2146            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2147            Err(GetExistingResourcesError::StreamEnded)
2148        )
2149    }
2150
2151    #[fuchsia_async::run_singlethreaded(test)]
2152    async fn wait_for_condition_add_remove() {
2153        let mut state = HashMap::new();
2154
2155        // Verify that checking for the presence of a resource blocks until the
2156        // resource is added.
2157        let has_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2158            resources.get(&test_controller_a()).map_or(false, |controller| {
2159                controller
2160                    .get(&test_resource_id())
2161                    .map_or(false, |resource| resource == &test_resource())
2162            })
2163        };
2164        assert_matches!(
2165            wait_for_condition(futures::stream::pending(), &mut state, has_resource).now_or_never(),
2166            None
2167        );
2168        assert!(state.is_empty());
2169        assert_matches!(
2170            wait_for_condition(
2171                futures::stream::iter([
2172                    Ok(Event::Added(test_controller_b(), test_resource())),
2173                    Ok(Event::EndOfUpdate),
2174                    Ok(Event::Added(test_controller_a(), test_resource())),
2175                    Ok(Event::EndOfUpdate),
2176                ]),
2177                &mut state,
2178                has_resource
2179            )
2180            .now_or_never(),
2181            Some(Ok(()))
2182        );
2183        assert_eq!(
2184            state,
2185            HashMap::from([
2186                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2187                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2188            ])
2189        );
2190
2191        // Re-add the resource and observe an error.
2192        assert_matches!(
2193            wait_for_condition(
2194                futures::stream::iter([
2195                    Ok(Event::Added(test_controller_a(), test_resource())),
2196                    Ok(Event::EndOfUpdate),
2197                ]),
2198                &mut state,
2199                has_resource
2200            )
2201            .now_or_never(),
2202            Some(Err(WaitForConditionError::AddedAlreadyExisting(r))) if r == test_resource()
2203        );
2204        assert_eq!(
2205            state,
2206            HashMap::from([
2207                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2208                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2209            ])
2210        );
2211
2212        // Verify that checking for the absence of a resource blocks until the
2213        // resource is removed.
2214        let does_not_have_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2215            resources.get(&test_controller_a()).map_or(false, |controller| controller.is_empty())
2216        };
2217        assert_matches!(
2218            wait_for_condition(futures::stream::pending(), &mut state, does_not_have_resource)
2219                .now_or_never(),
2220            None
2221        );
2222        assert_eq!(
2223            state,
2224            HashMap::from([
2225                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2226                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2227            ])
2228        );
2229        assert_matches!(
2230            wait_for_condition(
2231                futures::stream::iter([
2232                    Ok(Event::Removed(test_controller_b(), test_resource_id())),
2233                    Ok(Event::EndOfUpdate),
2234                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2235                    Ok(Event::EndOfUpdate),
2236                ]),
2237                &mut state,
2238                does_not_have_resource
2239            )
2240            .now_or_never(),
2241            Some(Ok(()))
2242        );
2243        assert_eq!(
2244            state,
2245            HashMap::from([
2246                (test_controller_a(), HashMap::new()),
2247                (test_controller_b(), HashMap::new()),
2248            ])
2249        );
2250
2251        // Remove a non-existent resource and observe an error.
2252        assert_matches!(
2253            wait_for_condition(
2254                futures::stream::iter([
2255                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2256                    Ok(Event::EndOfUpdate),
2257                ]),
2258                &mut state,
2259                does_not_have_resource
2260            ).now_or_never(),
2261            Some(Err(WaitForConditionError::RemovedNonExistent(r))) if r == test_resource_id()
2262        );
2263        assert_eq!(
2264            state,
2265            HashMap::from([
2266                (test_controller_a(), HashMap::new()),
2267                (test_controller_b(), HashMap::new()),
2268            ])
2269        );
2270    }
2271
2272    #[test]
2273    fn predicate_not_tested_until_update_complete() {
2274        let mut state = HashMap::new();
2275        let (mut tx, rx) = mpsc::unbounded();
2276
2277        let wait = wait_for_condition(rx, &mut state, |state| !state.is_empty()).fuse();
2278        futures::pin_mut!(wait);
2279
2280        // Sending an `Added` event should *not* allow the wait operation to
2281        // complete, because the predicate should only be tested once the full
2282        // update has been observed.
2283        let mut exec = fuchsia_async::TestExecutor::new();
2284        exec.run_singlethreaded(async {
2285            tx.send(Ok(Event::Added(test_controller_a(), test_resource())))
2286                .await
2287                .expect("receiver should not be closed");
2288        });
2289        assert_matches!(exec.run_until_stalled(&mut wait), Poll::Pending);
2290
2291        exec.run_singlethreaded(async {
2292            tx.send(Ok(Event::EndOfUpdate)).await.expect("receiver should not be closed");
2293            wait.await.expect("condition should be satisfied once update is complete");
2294        });
2295    }
2296
2297    #[fuchsia_async::run_singlethreaded(test)]
2298    async fn wait_for_condition_error_in_stream() {
2299        let mut state = HashMap::new();
2300        let event_stream =
2301            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2302        assert_matches!(
2303            wait_for_condition(event_stream, &mut state, |_| true).await,
2304            Err(WaitForConditionError::ErrorInStream(WatchError::EmptyEventBatch))
2305        );
2306        assert!(state.is_empty());
2307    }
2308
2309    #[fuchsia_async::run_singlethreaded(test)]
2310    async fn wait_for_condition_stream_ended() {
2311        let mut state = HashMap::new();
2312        let event_stream = futures::stream::empty();
2313        assert_matches!(
2314            wait_for_condition(event_stream, &mut state, |_| true).await,
2315            Err(WaitForConditionError::StreamEnded)
2316        );
2317        assert!(state.is_empty());
2318    }
2319
2320    pub(crate) async fn handle_open_controller(
2321        mut request_stream: fnet_filter::ControlRequestStream,
2322    ) -> fnet_filter::NamespaceControllerRequestStream {
2323        let (id, request, _control_handle) = request_stream
2324            .next()
2325            .await
2326            .expect("client should open controller")
2327            .expect("request should not error")
2328            .into_open_controller()
2329            .expect("client should open controller");
2330        let (stream, control_handle) = request.into_stream_and_control_handle();
2331        control_handle.send_on_id_assigned(&id).expect("send assigned ID");
2332
2333        stream
2334    }
2335
2336    pub(crate) async fn handle_push_changes(
2337        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2338        push_changes_result: fnet_filter::ChangeValidationResult,
2339    ) {
2340        let (_changes, responder) = stream
2341            .next()
2342            .await
2343            .expect("client should push changes")
2344            .expect("request should not error")
2345            .into_push_changes()
2346            .expect("client should push changes");
2347        responder.send(push_changes_result).expect("send empty batch");
2348    }
2349
2350    pub(crate) async fn handle_commit(
2351        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2352        commit_result: fnet_filter::CommitResult,
2353    ) {
2354        let (_options, responder) = stream
2355            .next()
2356            .await
2357            .expect("client should commit")
2358            .expect("request should not error")
2359            .into_commit()
2360            .expect("client should commit");
2361        responder.send(commit_result).expect("send commit result");
2362    }
2363
2364    #[fuchsia_async::run_singlethreaded(test)]
2365    async fn controller_push_changes_reports_invalid_change() {
2366        let (control, request_stream) =
2367            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2368        let push_invalid_change = async {
2369            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2370                .await
2371                .expect("create controller");
2372            let result = controller
2373                .push_changes(vec![
2374                    Change::Create(test_resource()),
2375                    // We fake the server response to say this is invalid even
2376                    // though it really isn't.
2377                    Change::Create(pretend_invalid_resource()),
2378                    Change::Remove(test_resource_id()),
2379                ])
2380                .await;
2381            assert_matches!(
2382                result,
2383                Err(PushChangesError::ErrorOnChange(errors)) if errors == vec![(
2384                    Change::Create(pretend_invalid_resource()),
2385                    ChangeValidationError::InvalidPortMatcher
2386                )]
2387            );
2388        };
2389
2390        let handle_controller = async {
2391            let mut stream = handle_open_controller(request_stream).await;
2392            handle_push_changes(
2393                &mut stream,
2394                fnet_filter::ChangeValidationResult::ErrorOnChange(vec![
2395                    fnet_filter::ChangeValidationError::Ok,
2396                    fnet_filter::ChangeValidationError::InvalidPortMatcher,
2397                    fnet_filter::ChangeValidationError::NotReached,
2398                ]),
2399            )
2400            .await;
2401        };
2402
2403        let ((), ()) = futures::future::join(push_invalid_change, handle_controller).await;
2404    }
2405
2406    #[fuchsia_async::run_singlethreaded(test)]
2407    async fn controller_commit_reports_invalid_change() {
2408        let (control, request_stream) =
2409            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2410        let commit_invalid_change = async {
2411            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2412                .await
2413                .expect("create controller");
2414            controller
2415                .push_changes(vec![
2416                    Change::Create(test_resource()),
2417                    Change::Remove(unknown_resource_id()),
2418                    Change::Remove(test_resource_id()),
2419                ])
2420                .await
2421                .expect("push changes");
2422            let result = controller.commit().await;
2423            assert_matches!(
2424                result,
2425                Err(CommitError::ErrorOnChange(errors)) if errors == vec![(
2426                    Change::Remove(unknown_resource_id()),
2427                    ChangeCommitError::NamespaceNotFound,
2428                )]
2429            );
2430        };
2431        let handle_controller = async {
2432            let mut stream = handle_open_controller(request_stream).await;
2433            handle_push_changes(
2434                &mut stream,
2435                fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}),
2436            )
2437            .await;
2438            handle_commit(
2439                &mut stream,
2440                fnet_filter::CommitResult::ErrorOnChange(vec![
2441                    fnet_filter::CommitError::Ok,
2442                    fnet_filter::CommitError::NamespaceNotFound,
2443                    fnet_filter::CommitError::Ok,
2444                ]),
2445            )
2446            .await;
2447        };
2448        let ((), ()) = futures::future::join(commit_invalid_change, handle_controller).await;
2449    }
2450}