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    #[error("the change includes an eBPF matcher with an invalid program ID")]
1265    InvalidEbpfProgramId,
1266}
1267
1268impl TryFrom<fnet_filter::CommitError> for ChangeCommitError {
1269    type Error = FidlConversionError;
1270
1271    fn try_from(error: fnet_filter::CommitError) -> Result<Self, Self::Error> {
1272        match error {
1273            fnet_filter::CommitError::NamespaceNotFound => Ok(Self::NamespaceNotFound),
1274            fnet_filter::CommitError::RoutineNotFound => Ok(Self::RoutineNotFound),
1275            fnet_filter::CommitError::RuleNotFound => Ok(Self::RuleNotFound),
1276            fnet_filter::CommitError::AlreadyExists => Ok(Self::AlreadyExists),
1277            fnet_filter::CommitError::TargetRoutineIsInstalled => {
1278                Ok(Self::TargetRoutineIsInstalled)
1279            }
1280            fnet_filter::CommitError::InvalidEbpfProgramId => Ok(Self::InvalidEbpfProgramId),
1281            fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1282                Err(FidlConversionError::NotAnError)
1283            }
1284            fnet_filter::CommitError::__SourceBreaking { unknown_ordinal: _ } => {
1285                Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR))
1286            }
1287        }
1288    }
1289}
1290
1291/// Errors for the NamespaceController.Commit method.
1292#[derive(Debug, Error)]
1293pub enum CommitError {
1294    #[error("failed to call FIDL method: {0}")]
1295    CallMethod(fidl::Error),
1296    #[error("rule has a matcher that is unavailable in its context: {0:?}")]
1297    RuleWithInvalidMatcher(RuleId),
1298    #[error("rule has an action that is invalid for its routine: {0:?}")]
1299    RuleWithInvalidAction(RuleId),
1300    #[error("rule has a TransparentProxy action but not a valid transport protocol matcher: {0:?}")]
1301    TransparentProxyWithInvalidMatcher(RuleId),
1302    #[error(
1303        "rule has a Redirect action that specifies a destination port but not a valid transport \
1304        protocol matcher: {0:?}"
1305    )]
1306    RedirectWithInvalidMatcher(RuleId),
1307    #[error(
1308        "rule has a Masquerade action that specifies a source port but not a valid transport \
1309        protocol matcher: {0:?}"
1310    )]
1311    MasqueradeWithInvalidMatcher(RuleId),
1312    #[error("routine forms a cycle {0:?}")]
1313    CyclicalRoutineGraph(RoutineId),
1314    #[error("invalid change was pushed: {0:?}")]
1315    ErrorOnChange(Vec<(Change, ChangeCommitError)>),
1316    #[error("unknown FIDL type: {0}")]
1317    FidlConversion(#[from] FidlConversionError),
1318}
1319
1320/// Extension type for [`fnet_filter::Change`].
1321#[derive(Debug, Clone, PartialEq)]
1322pub enum Change {
1323    Create(Resource),
1324    Remove(ResourceId),
1325}
1326
1327impl From<Change> for fnet_filter::Change {
1328    fn from(change: Change) -> Self {
1329        match change {
1330            Change::Create(resource) => Self::Create(resource.into()),
1331            Change::Remove(resource) => Self::Remove(resource.into()),
1332        }
1333    }
1334}
1335
1336impl TryFrom<fnet_filter::Change> for Change {
1337    type Error = FidlConversionError;
1338
1339    fn try_from(change: fnet_filter::Change) -> Result<Self, Self::Error> {
1340        match change {
1341            fnet_filter::Change::Create(resource) => Ok(Self::Create(resource.try_into()?)),
1342            fnet_filter::Change::Remove(resource) => Ok(Self::Remove(resource.try_into()?)),
1343            fnet_filter::Change::__SourceBreaking { .. } => {
1344                Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1345            }
1346        }
1347    }
1348}
1349
1350/// A controller for filtering state.
1351pub struct Controller {
1352    controller: fnet_filter::NamespaceControllerProxy,
1353    // The client provides an ID when creating a new controller, but the server
1354    // may need to assign a different ID to avoid conflicts; either way, the
1355    // server informs the client of the final `ControllerId` on creation.
1356    id: ControllerId,
1357    // Changes that have been pushed to the server but not yet committed. This
1358    // allows the `Controller` to report more informative errors by correlating
1359    // error codes with particular changes.
1360    pending_changes: Vec<Change>,
1361}
1362
1363impl Controller {
1364    pub async fn new_root(
1365        root: &fnet_root::FilterProxy,
1366        ControllerId(id): &ControllerId,
1367    ) -> Result<Self, ControllerCreationError> {
1368        let (controller, server_end) = fidl::endpoints::create_proxy();
1369        root.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1370
1371        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1372            .take_event_stream()
1373            .next()
1374            .await
1375            .ok_or(ControllerCreationError::NoIdAssigned)?
1376            .map_err(ControllerCreationError::IdAssignment)?;
1377        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1378    }
1379
1380    /// Creates a new `Controller`.
1381    ///
1382    /// Note that the provided `ControllerId` may need to be modified server-
1383    /// side to avoid collisions; to obtain the final ID assigned to the
1384    /// `Controller`, use the `id` method.
1385    pub async fn new(
1386        control: &fnet_filter::ControlProxy,
1387        ControllerId(id): &ControllerId,
1388    ) -> Result<Self, ControllerCreationError> {
1389        let (controller, server_end) = fidl::endpoints::create_proxy();
1390        control.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1391
1392        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1393            .take_event_stream()
1394            .next()
1395            .await
1396            .ok_or(ControllerCreationError::NoIdAssigned)?
1397            .map_err(ControllerCreationError::IdAssignment)?;
1398        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1399    }
1400
1401    pub fn id(&self) -> &ControllerId {
1402        &self.id
1403    }
1404
1405    pub async fn register_ebpf_program(
1406        &mut self,
1407        handle: febpf::ProgramHandle,
1408        program: febpf::VerifiedProgram,
1409    ) -> Result<(), RegisterEbpfProgramError> {
1410        self.controller
1411            .register_ebpf_program(handle, program)
1412            .await
1413            .map_err(RegisterEbpfProgramError::CallMethod)?
1414            .map_err(RegisterEbpfProgramError::from)
1415    }
1416
1417    pub async fn push_changes(&mut self, changes: Vec<Change>) -> Result<(), PushChangesError> {
1418        let fidl_changes = changes.iter().cloned().map(Into::into).collect::<Vec<_>>();
1419        let result = self
1420            .controller
1421            .push_changes(&fidl_changes)
1422            .await
1423            .map_err(PushChangesError::CallMethod)?;
1424        handle_change_validation_result(result, &changes)?;
1425        // Maintain a client-side copy of the pending changes we've pushed to
1426        // the server in order to provide better error messages if a commit
1427        // fails.
1428        self.pending_changes.extend(changes);
1429        Ok(())
1430    }
1431
1432    async fn commit_with_options(
1433        &mut self,
1434        options: fnet_filter::CommitOptions,
1435    ) -> Result<(), CommitError> {
1436        let committed_changes = std::mem::take(&mut self.pending_changes);
1437        let result = self.controller.commit(options).await.map_err(CommitError::CallMethod)?;
1438        handle_commit_result(result, committed_changes)
1439    }
1440
1441    pub async fn commit(&mut self) -> Result<(), CommitError> {
1442        self.commit_with_options(fnet_filter::CommitOptions::default()).await
1443    }
1444
1445    pub async fn commit_idempotent(&mut self) -> Result<(), CommitError> {
1446        self.commit_with_options(fnet_filter::CommitOptions {
1447            idempotent: Some(true),
1448            __source_breaking: SourceBreaking,
1449        })
1450        .await
1451    }
1452}
1453
1454pub(crate) fn handle_change_validation_result(
1455    change_validation_result: fnet_filter::ChangeValidationResult,
1456    changes: &Vec<Change>,
1457) -> Result<(), PushChangesError> {
1458    match change_validation_result {
1459        fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}) => Ok(()),
1460        fnet_filter::ChangeValidationResult::TooManyChanges(fnet_filter::Empty {}) => {
1461            Err(PushChangesError::TooManyChanges)
1462        }
1463        fnet_filter::ChangeValidationResult::ErrorOnChange(results) => {
1464            let errors: Result<_, PushChangesError> =
1465                changes.iter().zip(results).try_fold(Vec::new(), |mut errors, (change, result)| {
1466                    match result {
1467                        fnet_filter::ChangeValidationError::Ok
1468                        | fnet_filter::ChangeValidationError::NotReached => Ok(errors),
1469                        error @ (fnet_filter::ChangeValidationError::MissingRequiredField
1470                        | fnet_filter::ChangeValidationError::InvalidInterfaceMatcher
1471                        | fnet_filter::ChangeValidationError::InvalidAddressMatcher
1472                        | fnet_filter::ChangeValidationError::InvalidPortMatcher
1473                        | fnet_filter::ChangeValidationError::InvalidTransparentProxyAction
1474                        | fnet_filter::ChangeValidationError::InvalidNatAction
1475                        | fnet_filter::ChangeValidationError::InvalidPortRange) => {
1476                            let error = error
1477                                .try_into()
1478                                .expect("`Ok` and `NotReached` are handled in another arm");
1479                            errors.push((change.clone(), error));
1480                            Ok(errors)
1481                        }
1482                        fnet_filter::ChangeValidationError::__SourceBreaking { .. } => {
1483                            Err(FidlConversionError::UnknownUnionVariant(
1484                                type_names::CHANGE_VALIDATION_ERROR,
1485                            )
1486                            .into())
1487                        }
1488                    }
1489                });
1490            Err(PushChangesError::ErrorOnChange(errors?))
1491        }
1492        fnet_filter::ChangeValidationResult::__SourceBreaking { .. } => {
1493            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_RESULT)
1494                .into())
1495        }
1496    }
1497}
1498
1499pub(crate) fn handle_commit_result(
1500    commit_result: fnet_filter::CommitResult,
1501    committed_changes: Vec<Change>,
1502) -> Result<(), CommitError> {
1503    match commit_result {
1504        fnet_filter::CommitResult::Ok(fnet_filter::Empty {}) => Ok(()),
1505        fnet_filter::CommitResult::RuleWithInvalidMatcher(rule_id) => {
1506            Err(CommitError::RuleWithInvalidMatcher(rule_id.into()))
1507        }
1508        fnet_filter::CommitResult::RuleWithInvalidAction(rule_id) => {
1509            Err(CommitError::RuleWithInvalidAction(rule_id.into()))
1510        }
1511        fnet_filter::CommitResult::TransparentProxyWithInvalidMatcher(rule_id) => {
1512            Err(CommitError::TransparentProxyWithInvalidMatcher(rule_id.into()))
1513        }
1514        fnet_filter::CommitResult::RedirectWithInvalidMatcher(rule_id) => {
1515            Err(CommitError::RedirectWithInvalidMatcher(rule_id.into()))
1516        }
1517        fnet_filter::CommitResult::MasqueradeWithInvalidMatcher(rule_id) => {
1518            Err(CommitError::MasqueradeWithInvalidMatcher(rule_id.into()))
1519        }
1520        fnet_filter::CommitResult::CyclicalRoutineGraph(routine_id) => {
1521            Err(CommitError::CyclicalRoutineGraph(routine_id.into()))
1522        }
1523        fnet_filter::CommitResult::ErrorOnChange(results) => {
1524            let errors: Result<_, CommitError> = committed_changes
1525                .into_iter()
1526                .zip(results)
1527                .try_fold(Vec::new(), |mut errors, (change, result)| match result {
1528                    fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1529                        Ok(errors)
1530                    }
1531                    error @ (fnet_filter::CommitError::NamespaceNotFound
1532                    | fnet_filter::CommitError::RoutineNotFound
1533                    | fnet_filter::CommitError::RuleNotFound
1534                    | fnet_filter::CommitError::AlreadyExists
1535                    | fnet_filter::CommitError::TargetRoutineIsInstalled
1536                    | fnet_filter::CommitError::InvalidEbpfProgramId) => {
1537                        let error = error
1538                            .try_into()
1539                            .expect("`Ok` and `NotReached` are handled in another arm");
1540                        errors.push((change, error));
1541                        Ok(errors)
1542                    }
1543                    fnet_filter::CommitError::__SourceBreaking { .. } => {
1544                        Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR)
1545                            .into())
1546                    }
1547                });
1548            Err(CommitError::ErrorOnChange(errors?))
1549        }
1550        fnet_filter::CommitResult::__SourceBreaking { .. } => {
1551            Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_RESULT).into())
1552        }
1553    }
1554}
1555
1556#[cfg(test)]
1557mod tests {
1558
1559    use assert_matches::assert_matches;
1560    use fidl_fuchsia_net_matchers as fnet_matchers;
1561    use futures::channel::mpsc;
1562    use futures::task::Poll;
1563    use futures::{FutureExt as _, SinkExt as _};
1564    use test_case::test_case;
1565
1566    use {
1567        fidl_fuchsia_hardware_network as fhardware_network,
1568        fidl_fuchsia_net_interfaces as fnet_interfaces,
1569    };
1570
1571    use super::*;
1572
1573    #[test_case(
1574        fnet_filter::ResourceId::Namespace(String::from("namespace")),
1575        ResourceId::Namespace(NamespaceId(String::from("namespace")));
1576        "NamespaceId"
1577    )]
1578    #[test_case(fnet_filter::Domain::Ipv4, Domain::Ipv4; "Domain")]
1579    #[test_case(
1580        fnet_filter::Namespace {
1581            id: Some(String::from("namespace")),
1582            domain: Some(fnet_filter::Domain::Ipv4),
1583            ..Default::default()
1584        },
1585        Namespace { id: NamespaceId(String::from("namespace")), domain: Domain::Ipv4 };
1586        "Namespace"
1587    )]
1588    #[test_case(fnet_filter::IpInstallationHook::Egress, IpHook::Egress; "IpHook")]
1589    #[test_case(fnet_filter::NatInstallationHook::Egress, NatHook::Egress; "NatHook")]
1590    #[test_case(
1591        fnet_filter::InstalledIpRoutine {
1592            hook: Some(fnet_filter::IpInstallationHook::Egress),
1593            priority: Some(1),
1594            ..Default::default()
1595        },
1596        InstalledIpRoutine {
1597            hook: IpHook::Egress,
1598            priority: 1,
1599        };
1600        "InstalledIpRoutine"
1601    )]
1602    #[test_case(
1603        fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
1604            installation: Some(fnet_filter::InstalledIpRoutine {
1605                hook: Some(fnet_filter::IpInstallationHook::LocalEgress),
1606                priority: Some(1),
1607                ..Default::default()
1608            }),
1609            ..Default::default()
1610        }),
1611        RoutineType::Ip(Some(InstalledIpRoutine { hook: IpHook::LocalEgress, priority: 1 }));
1612        "RoutineType"
1613    )]
1614    #[test_case(
1615        fnet_filter::Routine {
1616            id: Some(fnet_filter::RoutineId {
1617                namespace: String::from("namespace"),
1618                name: String::from("routine"),
1619            }),
1620            type_: Some(fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine::default())),
1621            ..Default::default()
1622        },
1623        Routine {
1624            id: RoutineId {
1625                namespace: NamespaceId(String::from("namespace")),
1626                name: String::from("routine"),
1627            },
1628            routine_type: RoutineType::Nat(None),
1629        };
1630        "Routine"
1631    )]
1632    #[test_case(
1633        fnet_filter::Matchers {
1634            in_interface: Some(fnet_matchers::Interface::Name(String::from("wlan"))),
1635            transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
1636                src_port: None,
1637                dst_port: Some(fnet_matchers::Port { start: 22, end: 22, invert: false }),
1638                ..Default::default()
1639            })),
1640            ..Default::default()
1641        },
1642        Matchers {
1643            in_interface: Some(fnet_matchers_ext::Interface::Name(String::from("wlan"))),
1644            transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
1645                src_port: None,
1646                dst_port: Some(fnet_matchers_ext::Port::new(22, 22, false).unwrap()),
1647            }),
1648            ..Default::default()
1649        };
1650        "Matchers"
1651    )]
1652    #[test_case(
1653        fnet_filter::Action::Accept(fnet_filter::Empty {}),
1654        Action::Accept;
1655        "Action"
1656    )]
1657    #[test_case(
1658        fnet_filter::Rule {
1659            id: fnet_filter::RuleId {
1660                routine: fnet_filter::RoutineId {
1661                    namespace: String::from("namespace"),
1662                    name: String::from("routine"),
1663                },
1664                index: 1,
1665            },
1666            matchers: fnet_filter::Matchers {
1667                transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Icmp(
1668                    fnet_matchers::IcmpPacket::default()
1669                )),
1670                ..Default::default()
1671            },
1672            action: fnet_filter::Action::Drop(fnet_filter::Empty {}),
1673        },
1674        Rule {
1675            id: RuleId {
1676                routine: RoutineId {
1677                    namespace: NamespaceId(String::from("namespace")),
1678                    name: String::from("routine"),
1679                },
1680                index: 1,
1681            },
1682            matchers: Matchers {
1683                transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
1684                ..Default::default()
1685            },
1686            action: Action::Drop,
1687        };
1688        "Rule"
1689    )]
1690    #[test_case(
1691        fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1692            id: Some(String::from("namespace")),
1693            domain: Some(fnet_filter::Domain::Ipv4),
1694            ..Default::default()
1695        }),
1696        Resource::Namespace(Namespace {
1697            id: NamespaceId(String::from("namespace")),
1698            domain: Domain::Ipv4
1699        });
1700        "Resource"
1701    )]
1702    #[test_case(
1703        fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}),
1704        Event::EndOfUpdate;
1705        "Event"
1706    )]
1707    #[test_case(
1708        fnet_filter::Change::Remove(fnet_filter::ResourceId::Namespace(String::from("namespace"))),
1709        Change::Remove(ResourceId::Namespace(NamespaceId(String::from("namespace"))));
1710        "Change"
1711    )]
1712    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1713    where
1714        E: TryFrom<F> + Clone + Debug + PartialEq,
1715        <E as TryFrom<F>>::Error: Debug + PartialEq,
1716        F: From<E> + Clone + Debug + PartialEq,
1717    {
1718        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1719        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1720    }
1721
1722    #[test]
1723    fn resource_id_try_from_unknown_variant() {
1724        assert_eq!(
1725            ResourceId::try_from(fnet_filter::ResourceId::__SourceBreaking { unknown_ordinal: 0 }),
1726            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
1727        );
1728    }
1729
1730    #[test]
1731    fn domain_try_from_unknown_variant() {
1732        assert_eq!(
1733            Domain::try_from(fnet_filter::Domain::__SourceBreaking { unknown_ordinal: 0 }),
1734            Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
1735        );
1736    }
1737
1738    #[test]
1739    fn namespace_try_from_missing_properties() {
1740        assert_eq!(
1741            Namespace::try_from(fnet_filter::Namespace {
1742                id: None,
1743                domain: Some(fnet_filter::Domain::Ipv4),
1744                ..Default::default()
1745            }),
1746            Err(FidlConversionError::MissingNamespaceId)
1747        );
1748        assert_eq!(
1749            Namespace::try_from(fnet_filter::Namespace {
1750                id: Some(String::from("namespace")),
1751                domain: None,
1752                ..Default::default()
1753            }),
1754            Err(FidlConversionError::MissingNamespaceDomain)
1755        );
1756    }
1757
1758    #[test]
1759    fn ip_installation_hook_try_from_unknown_variant() {
1760        assert_eq!(
1761            IpHook::try_from(fnet_filter::IpInstallationHook::__SourceBreaking {
1762                unknown_ordinal: 0
1763            }),
1764            Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
1765        );
1766    }
1767
1768    #[test]
1769    fn nat_installation_hook_try_from_unknown_variant() {
1770        assert_eq!(
1771            NatHook::try_from(fnet_filter::NatInstallationHook::__SourceBreaking {
1772                unknown_ordinal: 0
1773            }),
1774            Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
1775        );
1776    }
1777
1778    #[test]
1779    fn installed_ip_routine_try_from_missing_hook() {
1780        assert_eq!(
1781            InstalledIpRoutine::try_from(fnet_filter::InstalledIpRoutine {
1782                hook: None,
1783                ..Default::default()
1784            }),
1785            Err(FidlConversionError::MissingIpInstallationHook)
1786        );
1787    }
1788
1789    #[test]
1790    fn installed_nat_routine_try_from_missing_hook() {
1791        assert_eq!(
1792            InstalledNatRoutine::try_from(fnet_filter::InstalledNatRoutine {
1793                hook: None,
1794                ..Default::default()
1795            }),
1796            Err(FidlConversionError::MissingNatInstallationHook)
1797        );
1798    }
1799
1800    #[test]
1801    fn routine_type_try_from_unknown_variant() {
1802        assert_eq!(
1803            RoutineType::try_from(fnet_filter::RoutineType::__SourceBreaking {
1804                unknown_ordinal: 0
1805            }),
1806            Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
1807        );
1808    }
1809
1810    #[test]
1811    fn routine_try_from_missing_properties() {
1812        assert_eq!(
1813            Routine::try_from(fnet_filter::Routine { id: None, ..Default::default() }),
1814            Err(FidlConversionError::MissingRoutineId)
1815        );
1816        assert_eq!(
1817            Routine::try_from(fnet_filter::Routine {
1818                id: Some(fnet_filter::RoutineId {
1819                    namespace: String::from("namespace"),
1820                    name: String::from("routine"),
1821                }),
1822                type_: None,
1823                ..Default::default()
1824            }),
1825            Err(FidlConversionError::MissingRoutineType)
1826        );
1827    }
1828
1829    #[test_case(
1830        fnet_matchers_ext::PortError::InvalidPortRange =>
1831        FidlConversionError::InvalidPortMatcherRange
1832    )]
1833    #[test_case(
1834        fnet_matchers_ext::InterfaceError::ZeroId =>
1835        FidlConversionError::ZeroInterfaceId
1836    )]
1837    #[test_case(
1838        fnet_matchers_ext::InterfaceError::UnknownUnionVariant =>
1839        FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
1840    )]
1841    #[test_case(
1842        {
1843            let invalid_port_class = fnet_interfaces::PortClass::__SourceBreaking {
1844                unknown_ordinal: 0
1845            };
1846            let error = fnet_interfaces_ext::PortClass::try_from(
1847                invalid_port_class
1848            ).unwrap_err();
1849            fnet_matchers_ext::InterfaceError::UnknownPortClass(error)
1850        } =>
1851        FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
1852    )]
1853    #[test_case(
1854        {
1855            let invalid_port_class = fhardware_network::PortClass::__SourceBreaking {
1856                unknown_ordinal: 0
1857            };
1858            let error = fnet_interfaces_ext::PortClass::try_from(
1859                invalid_port_class
1860            ).unwrap_err();
1861            fnet_matchers_ext::InterfaceError::UnknownPortClass(
1862                fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(error))
1863        } =>
1864        FidlConversionError::UnknownUnionVariant(type_names::HARDWARE_NETWORK_PORT_CLASS)
1865    )]
1866    #[test_case(
1867        fnet_matchers_ext::SubnetError::PrefixTooLong =>
1868        FidlConversionError::SubnetPrefixTooLong
1869    )]
1870    #[test_case(
1871        fnet_matchers_ext::SubnetError::HostBitsSet =>
1872        FidlConversionError::SubnetHostBitsSet
1873    )]
1874    #[test_case(
1875        fnet_matchers_ext::AddressRangeError::Invalid =>
1876        FidlConversionError::InvalidAddressRange
1877    )]
1878    #[test_case(
1879        fnet_matchers_ext::AddressRangeError::FamilyMismatch =>
1880        FidlConversionError::AddressRangeFamilyMismatch
1881    )]
1882    #[test_case(
1883        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1884            fnet_matchers_ext::SubnetError::PrefixTooLong) =>
1885        FidlConversionError::SubnetPrefixTooLong
1886    )]
1887    #[test_case(
1888        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1889            fnet_matchers_ext::SubnetError::HostBitsSet) =>
1890        FidlConversionError::SubnetHostBitsSet
1891    )]
1892    #[test_case(
1893        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1894            fnet_matchers_ext::AddressRangeError::Invalid) =>
1895        FidlConversionError::InvalidAddressRange
1896    )]
1897    #[test_case(
1898        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1899            fnet_matchers_ext::AddressRangeError::FamilyMismatch) =>
1900        FidlConversionError::AddressRangeFamilyMismatch
1901    )]
1902    #[test_case(
1903        fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant =>
1904        FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1905    )]
1906    #[test_case(
1907        fnet_matchers_ext::AddressError::AddressMatcherType(
1908            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1909                fnet_matchers_ext::SubnetError::PrefixTooLong)) =>
1910        FidlConversionError::SubnetPrefixTooLong
1911    )]
1912    #[test_case(
1913        fnet_matchers_ext::AddressError::AddressMatcherType(
1914            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1915                fnet_matchers_ext::SubnetError::HostBitsSet)) =>
1916        FidlConversionError::SubnetHostBitsSet
1917    )]
1918    #[test_case(
1919        fnet_matchers_ext::AddressError::AddressMatcherType(
1920            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1921                fnet_matchers_ext::AddressRangeError::Invalid)) =>
1922        FidlConversionError::InvalidAddressRange
1923    )]
1924    #[test_case(
1925        fnet_matchers_ext::AddressError::AddressMatcherType(
1926            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1927                fnet_matchers_ext::AddressRangeError::FamilyMismatch)) =>
1928        FidlConversionError::AddressRangeFamilyMismatch
1929    )]
1930    #[test_case(
1931        fnet_matchers_ext::AddressError::AddressMatcherType(
1932            fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant) =>
1933            FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1934    )]
1935    #[test_case(
1936        fnet_matchers_ext::TransportProtocolError::Port(
1937            fnet_matchers_ext::PortError::InvalidPortRange) =>
1938        FidlConversionError::InvalidPortMatcherRange
1939    )]
1940    #[test_case(
1941        fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant =>
1942            FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
1943    )]
1944    fn fidl_error_from_matcher_error<E: Into<FidlConversionError>>(
1945        error: E,
1946    ) -> FidlConversionError {
1947        error.into()
1948    }
1949
1950    #[test]
1951    fn action_try_from_unknown_variant() {
1952        assert_eq!(
1953            Action::try_from(fnet_filter::Action::__SourceBreaking { unknown_ordinal: 0 }),
1954            Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
1955        );
1956    }
1957
1958    #[test]
1959    fn resource_try_from_unknown_variant() {
1960        assert_eq!(
1961            Resource::try_from(fnet_filter::Resource::__SourceBreaking { unknown_ordinal: 0 }),
1962            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
1963        );
1964    }
1965
1966    #[test]
1967    fn event_try_from_unknown_variant() {
1968        assert_eq!(
1969            Event::try_from(fnet_filter::Event::__SourceBreaking { unknown_ordinal: 0 }),
1970            Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
1971        );
1972    }
1973
1974    #[test]
1975    fn change_try_from_unknown_variant() {
1976        assert_eq!(
1977            Change::try_from(fnet_filter::Change::__SourceBreaking { unknown_ordinal: 0 }),
1978            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1979        );
1980    }
1981
1982    fn test_controller_a() -> ControllerId {
1983        ControllerId(String::from("test-controller-a"))
1984    }
1985
1986    fn test_controller_b() -> ControllerId {
1987        ControllerId(String::from("test-controller-b"))
1988    }
1989
1990    pub(crate) fn test_resource_id() -> ResourceId {
1991        ResourceId::Namespace(NamespaceId(String::from("test-namespace")))
1992    }
1993
1994    pub(crate) fn test_resource() -> Resource {
1995        Resource::Namespace(Namespace {
1996            id: NamespaceId(String::from("test-namespace")),
1997            domain: Domain::AllIp,
1998        })
1999    }
2000
2001    // We can't easily create an invalid resource, so we just pretend and fake
2002    // the server response in tests.
2003    pub(crate) fn pretend_invalid_resource() -> Resource {
2004        Resource::Namespace(Namespace {
2005            id: NamespaceId(String::from("pretend-invalid-namespace")),
2006            domain: Domain::AllIp,
2007        })
2008    }
2009
2010    pub(crate) fn unknown_resource_id() -> ResourceId {
2011        ResourceId::Namespace(NamespaceId(String::from("does-not-exist")))
2012    }
2013
2014    #[fuchsia_async::run_singlethreaded(test)]
2015    async fn event_stream_from_state_conversion_error() {
2016        let (proxy, mut request_stream) =
2017            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2018        let stream = event_stream_from_state(proxy).expect("get event stream");
2019        futures::pin_mut!(stream);
2020
2021        let send_invalid_event = async {
2022            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2023                request_stream
2024                    .next()
2025                    .await
2026                    .expect("client should call state")
2027                    .expect("request should not error");
2028            let fnet_filter::WatcherRequest::Watch { responder } = request
2029                .into_stream()
2030                .next()
2031                .await
2032                .expect("client should call watch")
2033                .expect("request should not error");
2034            responder
2035                .send(&[fnet_filter::Event::Added(fnet_filter::AddedResource {
2036                    controller: String::from("controller"),
2037                    resource: fnet_filter::Resource::Namespace(fnet_filter::Namespace {
2038                        id: None,
2039                        domain: None,
2040                        ..Default::default()
2041                    }),
2042                })])
2043                .expect("send batch with invalid event");
2044        };
2045        let ((), result) = futures::future::join(send_invalid_event, stream.next()).await;
2046        assert_matches!(
2047            result,
2048            Some(Err(WatchError::Conversion(FidlConversionError::MissingNamespaceId)))
2049        );
2050    }
2051
2052    #[fuchsia_async::run_singlethreaded(test)]
2053    async fn event_stream_from_state_empty_event_batch() {
2054        let (proxy, mut request_stream) =
2055            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2056        let stream = event_stream_from_state(proxy).expect("get event stream");
2057        futures::pin_mut!(stream);
2058
2059        let send_empty_batch = async {
2060            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2061                request_stream
2062                    .next()
2063                    .await
2064                    .expect("client should call state")
2065                    .expect("request should not error");
2066            let fnet_filter::WatcherRequest::Watch { responder } = request
2067                .into_stream()
2068                .next()
2069                .await
2070                .expect("client should call watch")
2071                .expect("request should not error");
2072            responder.send(&[]).expect("send empty batch");
2073        };
2074        let ((), result) = futures::future::join(send_empty_batch, stream.next()).await;
2075        assert_matches!(result, Some(Err(WatchError::EmptyEventBatch)));
2076    }
2077
2078    #[fuchsia_async::run_singlethreaded(test)]
2079    async fn get_existing_resources_success() {
2080        let event_stream = futures::stream::iter([
2081            Ok(Event::Existing(test_controller_a(), test_resource())),
2082            Ok(Event::Existing(test_controller_b(), test_resource())),
2083            Ok(Event::Idle),
2084            Ok(Event::Removed(test_controller_a(), test_resource_id())),
2085        ]);
2086        futures::pin_mut!(event_stream);
2087
2088        let existing = get_existing_resources::<HashMap<_, _>>(event_stream.by_ref())
2089            .await
2090            .expect("get existing resources");
2091        assert_eq!(
2092            existing,
2093            HashMap::from([
2094                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2095                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2096            ])
2097        );
2098
2099        let trailing_events = event_stream.collect::<Vec<_>>().await;
2100        assert_matches!(
2101            &trailing_events[..],
2102            [Ok(Event::Removed(controller, resource))] if controller == &test_controller_a() &&
2103                                                           resource == &test_resource_id()
2104        );
2105    }
2106
2107    #[fuchsia_async::run_singlethreaded(test)]
2108    async fn get_existing_resources_error_in_stream() {
2109        let event_stream =
2110            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2111        futures::pin_mut!(event_stream);
2112        assert_matches!(
2113            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2114            Err(GetExistingResourcesError::ErrorInStream(WatchError::EmptyEventBatch))
2115        )
2116    }
2117
2118    #[fuchsia_async::run_singlethreaded(test)]
2119    async fn get_existing_resources_unexpected_event() {
2120        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::EndOfUpdate)));
2121        futures::pin_mut!(event_stream);
2122        assert_matches!(
2123            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2124            Err(GetExistingResourcesError::UnexpectedEvent(Event::EndOfUpdate))
2125        )
2126    }
2127
2128    #[fuchsia_async::run_singlethreaded(test)]
2129    async fn get_existing_resources_duplicate_resource() {
2130        let event_stream = futures::stream::iter([
2131            Ok(Event::Existing(test_controller_a(), test_resource())),
2132            Ok(Event::Existing(test_controller_a(), test_resource())),
2133        ]);
2134        futures::pin_mut!(event_stream);
2135        assert_matches!(
2136            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2137            Err(GetExistingResourcesError::DuplicateResource(resource))
2138                if resource == test_resource()
2139        )
2140    }
2141
2142    #[fuchsia_async::run_singlethreaded(test)]
2143    async fn get_existing_resources_stream_ended() {
2144        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::Existing(
2145            test_controller_a(),
2146            test_resource(),
2147        ))));
2148        futures::pin_mut!(event_stream);
2149        assert_matches!(
2150            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2151            Err(GetExistingResourcesError::StreamEnded)
2152        )
2153    }
2154
2155    #[fuchsia_async::run_singlethreaded(test)]
2156    async fn wait_for_condition_add_remove() {
2157        let mut state = HashMap::new();
2158
2159        // Verify that checking for the presence of a resource blocks until the
2160        // resource is added.
2161        let has_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2162            resources.get(&test_controller_a()).map_or(false, |controller| {
2163                controller
2164                    .get(&test_resource_id())
2165                    .map_or(false, |resource| resource == &test_resource())
2166            })
2167        };
2168        assert_matches!(
2169            wait_for_condition(futures::stream::pending(), &mut state, has_resource).now_or_never(),
2170            None
2171        );
2172        assert!(state.is_empty());
2173        assert_matches!(
2174            wait_for_condition(
2175                futures::stream::iter([
2176                    Ok(Event::Added(test_controller_b(), test_resource())),
2177                    Ok(Event::EndOfUpdate),
2178                    Ok(Event::Added(test_controller_a(), test_resource())),
2179                    Ok(Event::EndOfUpdate),
2180                ]),
2181                &mut state,
2182                has_resource
2183            )
2184            .now_or_never(),
2185            Some(Ok(()))
2186        );
2187        assert_eq!(
2188            state,
2189            HashMap::from([
2190                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2191                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2192            ])
2193        );
2194
2195        // Re-add the resource and observe an error.
2196        assert_matches!(
2197            wait_for_condition(
2198                futures::stream::iter([
2199                    Ok(Event::Added(test_controller_a(), test_resource())),
2200                    Ok(Event::EndOfUpdate),
2201                ]),
2202                &mut state,
2203                has_resource
2204            )
2205            .now_or_never(),
2206            Some(Err(WaitForConditionError::AddedAlreadyExisting(r))) if r == test_resource()
2207        );
2208        assert_eq!(
2209            state,
2210            HashMap::from([
2211                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2212                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2213            ])
2214        );
2215
2216        // Verify that checking for the absence of a resource blocks until the
2217        // resource is removed.
2218        let does_not_have_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2219            resources.get(&test_controller_a()).map_or(false, |controller| controller.is_empty())
2220        };
2221        assert_matches!(
2222            wait_for_condition(futures::stream::pending(), &mut state, does_not_have_resource)
2223                .now_or_never(),
2224            None
2225        );
2226        assert_eq!(
2227            state,
2228            HashMap::from([
2229                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2230                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2231            ])
2232        );
2233        assert_matches!(
2234            wait_for_condition(
2235                futures::stream::iter([
2236                    Ok(Event::Removed(test_controller_b(), test_resource_id())),
2237                    Ok(Event::EndOfUpdate),
2238                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2239                    Ok(Event::EndOfUpdate),
2240                ]),
2241                &mut state,
2242                does_not_have_resource
2243            )
2244            .now_or_never(),
2245            Some(Ok(()))
2246        );
2247        assert_eq!(
2248            state,
2249            HashMap::from([
2250                (test_controller_a(), HashMap::new()),
2251                (test_controller_b(), HashMap::new()),
2252            ])
2253        );
2254
2255        // Remove a non-existent resource and observe an error.
2256        assert_matches!(
2257            wait_for_condition(
2258                futures::stream::iter([
2259                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2260                    Ok(Event::EndOfUpdate),
2261                ]),
2262                &mut state,
2263                does_not_have_resource
2264            ).now_or_never(),
2265            Some(Err(WaitForConditionError::RemovedNonExistent(r))) if r == test_resource_id()
2266        );
2267        assert_eq!(
2268            state,
2269            HashMap::from([
2270                (test_controller_a(), HashMap::new()),
2271                (test_controller_b(), HashMap::new()),
2272            ])
2273        );
2274    }
2275
2276    #[test]
2277    fn predicate_not_tested_until_update_complete() {
2278        let mut state = HashMap::new();
2279        let (mut tx, rx) = mpsc::unbounded();
2280
2281        let wait = wait_for_condition(rx, &mut state, |state| !state.is_empty()).fuse();
2282        futures::pin_mut!(wait);
2283
2284        // Sending an `Added` event should *not* allow the wait operation to
2285        // complete, because the predicate should only be tested once the full
2286        // update has been observed.
2287        let mut exec = fuchsia_async::TestExecutor::new();
2288        exec.run_singlethreaded(async {
2289            tx.send(Ok(Event::Added(test_controller_a(), test_resource())))
2290                .await
2291                .expect("receiver should not be closed");
2292        });
2293        assert_matches!(exec.run_until_stalled(&mut wait), Poll::Pending);
2294
2295        exec.run_singlethreaded(async {
2296            tx.send(Ok(Event::EndOfUpdate)).await.expect("receiver should not be closed");
2297            wait.await.expect("condition should be satisfied once update is complete");
2298        });
2299    }
2300
2301    #[fuchsia_async::run_singlethreaded(test)]
2302    async fn wait_for_condition_error_in_stream() {
2303        let mut state = HashMap::new();
2304        let event_stream =
2305            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2306        assert_matches!(
2307            wait_for_condition(event_stream, &mut state, |_| true).await,
2308            Err(WaitForConditionError::ErrorInStream(WatchError::EmptyEventBatch))
2309        );
2310        assert!(state.is_empty());
2311    }
2312
2313    #[fuchsia_async::run_singlethreaded(test)]
2314    async fn wait_for_condition_stream_ended() {
2315        let mut state = HashMap::new();
2316        let event_stream = futures::stream::empty();
2317        assert_matches!(
2318            wait_for_condition(event_stream, &mut state, |_| true).await,
2319            Err(WaitForConditionError::StreamEnded)
2320        );
2321        assert!(state.is_empty());
2322    }
2323
2324    pub(crate) async fn handle_open_controller(
2325        mut request_stream: fnet_filter::ControlRequestStream,
2326    ) -> fnet_filter::NamespaceControllerRequestStream {
2327        let (id, request, _control_handle) = request_stream
2328            .next()
2329            .await
2330            .expect("client should open controller")
2331            .expect("request should not error")
2332            .into_open_controller()
2333            .expect("client should open controller");
2334        let (stream, control_handle) = request.into_stream_and_control_handle();
2335        control_handle.send_on_id_assigned(&id).expect("send assigned ID");
2336
2337        stream
2338    }
2339
2340    pub(crate) async fn handle_push_changes(
2341        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2342        push_changes_result: fnet_filter::ChangeValidationResult,
2343    ) {
2344        let (_changes, responder) = stream
2345            .next()
2346            .await
2347            .expect("client should push changes")
2348            .expect("request should not error")
2349            .into_push_changes()
2350            .expect("client should push changes");
2351        responder.send(push_changes_result).expect("send empty batch");
2352    }
2353
2354    pub(crate) async fn handle_commit(
2355        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2356        commit_result: fnet_filter::CommitResult,
2357    ) {
2358        let (_options, responder) = stream
2359            .next()
2360            .await
2361            .expect("client should commit")
2362            .expect("request should not error")
2363            .into_commit()
2364            .expect("client should commit");
2365        responder.send(commit_result).expect("send commit result");
2366    }
2367
2368    #[fuchsia_async::run_singlethreaded(test)]
2369    async fn controller_push_changes_reports_invalid_change() {
2370        let (control, request_stream) =
2371            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2372        let push_invalid_change = async {
2373            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2374                .await
2375                .expect("create controller");
2376            let result = controller
2377                .push_changes(vec![
2378                    Change::Create(test_resource()),
2379                    // We fake the server response to say this is invalid even
2380                    // though it really isn't.
2381                    Change::Create(pretend_invalid_resource()),
2382                    Change::Remove(test_resource_id()),
2383                ])
2384                .await;
2385            assert_matches!(
2386                result,
2387                Err(PushChangesError::ErrorOnChange(errors)) if errors == vec![(
2388                    Change::Create(pretend_invalid_resource()),
2389                    ChangeValidationError::InvalidPortMatcher
2390                )]
2391            );
2392        };
2393
2394        let handle_controller = async {
2395            let mut stream = handle_open_controller(request_stream).await;
2396            handle_push_changes(
2397                &mut stream,
2398                fnet_filter::ChangeValidationResult::ErrorOnChange(vec![
2399                    fnet_filter::ChangeValidationError::Ok,
2400                    fnet_filter::ChangeValidationError::InvalidPortMatcher,
2401                    fnet_filter::ChangeValidationError::NotReached,
2402                ]),
2403            )
2404            .await;
2405        };
2406
2407        let ((), ()) = futures::future::join(push_invalid_change, handle_controller).await;
2408    }
2409
2410    #[fuchsia_async::run_singlethreaded(test)]
2411    async fn controller_commit_reports_invalid_change() {
2412        let (control, request_stream) =
2413            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2414        let commit_invalid_change = async {
2415            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2416                .await
2417                .expect("create controller");
2418            controller
2419                .push_changes(vec![
2420                    Change::Create(test_resource()),
2421                    Change::Remove(unknown_resource_id()),
2422                    Change::Remove(test_resource_id()),
2423                ])
2424                .await
2425                .expect("push changes");
2426            let result = controller.commit().await;
2427            assert_matches!(
2428                result,
2429                Err(CommitError::ErrorOnChange(errors)) if errors == vec![(
2430                    Change::Remove(unknown_resource_id()),
2431                    ChangeCommitError::NamespaceNotFound,
2432                )]
2433            );
2434        };
2435        let handle_controller = async {
2436            let mut stream = handle_open_controller(request_stream).await;
2437            handle_push_changes(
2438                &mut stream,
2439                fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}),
2440            )
2441            .await;
2442            handle_commit(
2443                &mut stream,
2444                fnet_filter::CommitResult::ErrorOnChange(vec![
2445                    fnet_filter::CommitError::Ok,
2446                    fnet_filter::CommitError::NamespaceNotFound,
2447                    fnet_filter::CommitError::Ok,
2448                ]),
2449            )
2450            .await;
2451        };
2452        let ((), ()) = futures::future::join(commit_invalid_change, handle_controller).await;
2453    }
2454}