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_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}
550
551impl From<Matchers> for fnet_filter::Matchers {
552    fn from(matchers: Matchers) -> Self {
553        let Matchers { in_interface, out_interface, src_addr, dst_addr, transport_protocol } =
554            matchers;
555        Self {
556            in_interface: in_interface.map(Into::into),
557            out_interface: out_interface.map(Into::into),
558            src_addr: src_addr.map(Into::into),
559            dst_addr: dst_addr.map(Into::into),
560            transport_protocol: transport_protocol.map(Into::into),
561            __source_breaking: SourceBreaking,
562        }
563    }
564}
565
566impl TryFrom<fnet_filter::Matchers> for Matchers {
567    type Error = FidlConversionError;
568
569    fn try_from(matchers: fnet_filter::Matchers) -> Result<Self, Self::Error> {
570        let fnet_filter::Matchers {
571            in_interface,
572            out_interface,
573            src_addr,
574            dst_addr,
575            transport_protocol,
576            __source_breaking,
577        } = matchers;
578        Ok(Self {
579            in_interface: in_interface.map(TryInto::try_into).transpose()?,
580            out_interface: out_interface.map(TryInto::try_into).transpose()?,
581            src_addr: src_addr.map(TryInto::try_into).transpose()?,
582            dst_addr: dst_addr.map(TryInto::try_into).transpose()?,
583            transport_protocol: transport_protocol.map(TryInto::try_into).transpose()?,
584        })
585    }
586}
587
588impl Debug for Matchers {
589    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590        let mut debug_struct = f.debug_struct("Matchers");
591
592        let Matchers { in_interface, out_interface, src_addr, dst_addr, transport_protocol } =
593            &self;
594
595        // Omit empty fields.
596        if let Some(matcher) = in_interface {
597            let _ = debug_struct.field("in_interface", matcher);
598        }
599
600        if let Some(matcher) = out_interface {
601            let _ = debug_struct.field("out_interface", matcher);
602        }
603
604        if let Some(matcher) = src_addr {
605            let _ = debug_struct.field("src_addr", matcher);
606        }
607
608        if let Some(matcher) = dst_addr {
609            let _ = debug_struct.field("dst_addr", matcher);
610        }
611
612        if let Some(matcher) = transport_protocol {
613            let _ = debug_struct.field("transport_protocol", matcher);
614        }
615
616        debug_struct.finish()
617    }
618}
619
620/// Extension type for [`fnet_filter::Action`].
621#[derive(Debug, Clone, PartialEq)]
622pub enum Action {
623    Accept,
624    Drop,
625    Jump(String),
626    Return,
627    TransparentProxy(TransparentProxy),
628    Redirect { dst_port: Option<PortRange> },
629    Masquerade { src_port: Option<PortRange> },
630    Mark { domain: fnet::MarkDomain, action: MarkAction },
631}
632
633#[derive(Debug, Clone, PartialEq)]
634pub enum MarkAction {
635    SetMark { clearing_mask: fnet::Mark, mark: fnet::Mark },
636}
637
638/// Extension type for [`fnet_filter::TransparentProxy_`].
639#[derive(Debug, Clone, PartialEq)]
640pub enum TransparentProxy {
641    LocalAddr(fnet::IpAddress),
642    LocalPort(NonZeroU16),
643    LocalAddrAndPort(fnet::IpAddress, NonZeroU16),
644}
645
646#[derive(Debug, Clone, PartialEq)]
647pub struct PortRange(pub RangeInclusive<NonZeroU16>);
648
649impl From<PortRange> for fnet_filter::PortRange {
650    fn from(range: PortRange) -> Self {
651        let PortRange(range) = range;
652        Self { start: range.start().get(), end: range.end().get() }
653    }
654}
655
656impl TryFrom<fnet_filter::PortRange> for PortRange {
657    type Error = FidlConversionError;
658
659    fn try_from(range: fnet_filter::PortRange) -> Result<Self, Self::Error> {
660        let fnet_filter::PortRange { start, end } = range;
661        if start > end {
662            Err(FidlConversionError::InvalidPortRange)
663        } else {
664            let start = NonZeroU16::new(start).ok_or(FidlConversionError::UnspecifiedNatPort)?;
665            let end = NonZeroU16::new(end).ok_or(FidlConversionError::UnspecifiedNatPort)?;
666            Ok(Self(start..=end))
667        }
668    }
669}
670
671impl From<Action> for fnet_filter::Action {
672    fn from(action: Action) -> Self {
673        match action {
674            Action::Accept => Self::Accept(fnet_filter::Empty {}),
675            Action::Drop => Self::Drop(fnet_filter::Empty {}),
676            Action::Jump(target) => Self::Jump(target),
677            Action::Return => Self::Return_(fnet_filter::Empty {}),
678            Action::TransparentProxy(proxy) => Self::TransparentProxy(match proxy {
679                TransparentProxy::LocalAddr(addr) => {
680                    fnet_filter::TransparentProxy_::LocalAddr(addr)
681                }
682                TransparentProxy::LocalPort(port) => {
683                    fnet_filter::TransparentProxy_::LocalPort(port.get())
684                }
685                TransparentProxy::LocalAddrAndPort(addr, port) => {
686                    fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
687                        addr,
688                        port: port.get(),
689                    })
690                }
691            }),
692            Action::Redirect { dst_port } => Self::Redirect(fnet_filter::Redirect {
693                dst_port: dst_port.map(Into::into),
694                __source_breaking: SourceBreaking,
695            }),
696            Action::Masquerade { src_port } => Self::Masquerade(fnet_filter::Masquerade {
697                src_port: src_port.map(Into::into),
698                __source_breaking: SourceBreaking,
699            }),
700            Action::Mark { domain, action } => {
701                Self::Mark(fnet_filter::Mark { domain, action: action.into() })
702            }
703        }
704    }
705}
706
707impl TryFrom<fnet_filter::Action> for Action {
708    type Error = FidlConversionError;
709
710    fn try_from(action: fnet_filter::Action) -> Result<Self, Self::Error> {
711        match action {
712            fnet_filter::Action::Accept(fnet_filter::Empty {}) => Ok(Self::Accept),
713            fnet_filter::Action::Drop(fnet_filter::Empty {}) => Ok(Self::Drop),
714            fnet_filter::Action::Jump(target) => Ok(Self::Jump(target)),
715            fnet_filter::Action::Return_(fnet_filter::Empty {}) => Ok(Self::Return),
716            fnet_filter::Action::TransparentProxy(proxy) => {
717                Ok(Self::TransparentProxy(match proxy {
718                    fnet_filter::TransparentProxy_::LocalAddr(addr) => {
719                        TransparentProxy::LocalAddr(addr)
720                    }
721                    fnet_filter::TransparentProxy_::LocalPort(port) => {
722                        let port = NonZeroU16::new(port)
723                            .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
724                        TransparentProxy::LocalPort(port)
725                    }
726                    fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
727                        addr,
728                        port,
729                    }) => {
730                        let port = NonZeroU16::new(port)
731                            .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
732                        TransparentProxy::LocalAddrAndPort(addr, port)
733                    }
734                    fnet_filter::TransparentProxy_::__SourceBreaking { .. } => {
735                        return Err(FidlConversionError::UnknownUnionVariant(
736                            type_names::TRANSPARENT_PROXY,
737                        ));
738                    }
739                }))
740            }
741            fnet_filter::Action::Redirect(fnet_filter::Redirect {
742                dst_port,
743                __source_breaking,
744            }) => Ok(Self::Redirect { dst_port: dst_port.map(TryInto::try_into).transpose()? }),
745            fnet_filter::Action::Masquerade(fnet_filter::Masquerade {
746                src_port,
747                __source_breaking,
748            }) => Ok(Self::Masquerade { src_port: src_port.map(TryInto::try_into).transpose()? }),
749            fnet_filter::Action::Mark(fnet_filter::Mark { domain, action }) => {
750                Ok(Self::Mark { domain, action: action.try_into()? })
751            }
752            fnet_filter::Action::__SourceBreaking { .. } => {
753                Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
754            }
755        }
756    }
757}
758
759impl From<MarkAction> for fnet_filter::MarkAction {
760    fn from(action: MarkAction) -> Self {
761        match action {
762            MarkAction::SetMark { clearing_mask, mark } => {
763                Self::SetMark(fnet_filter::SetMark { clearing_mask, mark })
764            }
765        }
766    }
767}
768
769impl TryFrom<fnet_filter::MarkAction> for MarkAction {
770    type Error = FidlConversionError;
771    fn try_from(action: fnet_filter::MarkAction) -> Result<Self, Self::Error> {
772        match action {
773            fnet_filter::MarkAction::SetMark(fnet_filter::SetMark { clearing_mask, mark }) => {
774                Ok(Self::SetMark { clearing_mask, mark })
775            }
776            fnet_filter::MarkAction::__SourceBreaking { .. } => {
777                Err(FidlConversionError::UnknownUnionVariant(type_names::MARK_ACTION))
778            }
779        }
780    }
781}
782
783/// Extension type for [`fnet_filter::Rule`].
784#[derive(Debug, Clone, PartialEq)]
785pub struct Rule {
786    pub id: RuleId,
787    pub matchers: Matchers,
788    pub action: Action,
789}
790
791impl From<Rule> for fnet_filter::Rule {
792    fn from(rule: Rule) -> Self {
793        let Rule { id, matchers, action } = rule;
794        Self { id: id.into(), matchers: matchers.into(), action: action.into() }
795    }
796}
797
798impl TryFrom<fnet_filter::Rule> for Rule {
799    type Error = FidlConversionError;
800
801    fn try_from(rule: fnet_filter::Rule) -> Result<Self, Self::Error> {
802        let fnet_filter::Rule { id, matchers, action } = rule;
803        Ok(Self { id: id.into(), matchers: matchers.try_into()?, action: action.try_into()? })
804    }
805}
806
807/// Extension type for [`fnet_filter::Resource`].
808#[derive(Debug, Clone, PartialEq)]
809pub enum Resource {
810    Namespace(Namespace),
811    Routine(Routine),
812    Rule(Rule),
813}
814
815impl Resource {
816    pub fn id(&self) -> ResourceId {
817        match self {
818            Self::Namespace(Namespace { id, domain: _ }) => ResourceId::Namespace(id.clone()),
819            Self::Routine(Routine { id, routine_type: _ }) => ResourceId::Routine(id.clone()),
820            Self::Rule(Rule { id, matchers: _, action: _ }) => ResourceId::Rule(id.clone()),
821        }
822    }
823}
824
825impl From<Resource> for fnet_filter::Resource {
826    fn from(resource: Resource) -> Self {
827        match resource {
828            Resource::Namespace(namespace) => Self::Namespace(namespace.into()),
829            Resource::Routine(routine) => Self::Routine(routine.into()),
830            Resource::Rule(rule) => Self::Rule(rule.into()),
831        }
832    }
833}
834
835impl TryFrom<fnet_filter::Resource> for Resource {
836    type Error = FidlConversionError;
837
838    fn try_from(resource: fnet_filter::Resource) -> Result<Self, Self::Error> {
839        match resource {
840            fnet_filter::Resource::Namespace(namespace) => {
841                Ok(Self::Namespace(namespace.try_into()?))
842            }
843            fnet_filter::Resource::Routine(routine) => Ok(Self::Routine(routine.try_into()?)),
844            fnet_filter::Resource::Rule(rule) => Ok(Self::Rule(rule.try_into()?)),
845            fnet_filter::Resource::__SourceBreaking { .. } => {
846                Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
847            }
848        }
849    }
850}
851
852/// Extension type for [`fnet_filter::ControllerId`].
853#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
854pub struct ControllerId(pub String);
855
856/// Extension type for [`fnet_filter::Event`].
857#[derive(Debug, Clone, PartialEq)]
858pub enum Event {
859    Existing(ControllerId, Resource),
860    Idle,
861    Added(ControllerId, Resource),
862    Removed(ControllerId, ResourceId),
863    EndOfUpdate,
864}
865
866impl From<Event> for fnet_filter::Event {
867    fn from(event: Event) -> Self {
868        match event {
869            Event::Existing(controller, resource) => {
870                let ControllerId(id) = controller;
871                Self::Existing(fnet_filter::ExistingResource {
872                    controller: id,
873                    resource: resource.into(),
874                })
875            }
876            Event::Idle => Self::Idle(fnet_filter::Empty {}),
877            Event::Added(controller, resource) => {
878                let ControllerId(id) = controller;
879                Self::Added(fnet_filter::AddedResource {
880                    controller: id,
881                    resource: resource.into(),
882                })
883            }
884            Event::Removed(controller, resource) => {
885                let ControllerId(id) = controller;
886                Self::Removed(fnet_filter::RemovedResource {
887                    controller: id,
888                    resource: resource.into(),
889                })
890            }
891            Event::EndOfUpdate => Self::EndOfUpdate(fnet_filter::Empty {}),
892        }
893    }
894}
895
896impl TryFrom<fnet_filter::Event> for Event {
897    type Error = FidlConversionError;
898
899    fn try_from(event: fnet_filter::Event) -> Result<Self, Self::Error> {
900        match event {
901            fnet_filter::Event::Existing(fnet_filter::ExistingResource {
902                controller,
903                resource,
904            }) => Ok(Self::Existing(ControllerId(controller), resource.try_into()?)),
905            fnet_filter::Event::Idle(fnet_filter::Empty {}) => Ok(Self::Idle),
906            fnet_filter::Event::Added(fnet_filter::AddedResource { controller, resource }) => {
907                Ok(Self::Added(ControllerId(controller), resource.try_into()?))
908            }
909            fnet_filter::Event::Removed(fnet_filter::RemovedResource { controller, resource }) => {
910                Ok(Self::Removed(ControllerId(controller), resource.try_into()?))
911            }
912            fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}) => Ok(Self::EndOfUpdate),
913            fnet_filter::Event::__SourceBreaking { .. } => {
914                Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
915            }
916        }
917    }
918}
919
920/// Filter watcher creation errors.
921#[derive(Debug, Error)]
922pub enum WatcherCreationError {
923    #[error("failed to create filter watcher proxy: {0}")]
924    CreateProxy(fidl::Error),
925    #[error("failed to get filter watcher: {0}")]
926    GetWatcher(fidl::Error),
927}
928
929/// Filter watcher `Watch` errors.
930#[derive(Debug, Error)]
931pub enum WatchError {
932    /// The call to `Watch` returned a FIDL error.
933    #[error("the call to `Watch()` failed: {0}")]
934    Fidl(fidl::Error),
935    /// The event returned by `Watch` encountered a conversion error.
936    #[error("failed to convert event returned by `Watch()`: {0}")]
937    Conversion(FidlConversionError),
938    /// The server returned an empty batch of events.
939    #[error("the call to `Watch()` returned an empty batch of events")]
940    EmptyEventBatch,
941}
942
943/// Connects to the watcher protocol and converts the Hanging-Get style API into
944/// an Event stream.
945///
946/// Each call to `Watch` returns a batch of events, which are flattened into a
947/// single stream. If an error is encountered while calling `Watch` or while
948/// converting the event, the stream is immediately terminated.
949pub fn event_stream_from_state(
950    state: fnet_filter::StateProxy,
951) -> Result<impl Stream<Item = Result<Event, WatchError>>, WatcherCreationError> {
952    let (watcher, server_end) = fidl::endpoints::create_proxy::<fnet_filter::WatcherMarker>();
953    state
954        .get_watcher(&fnet_filter::WatcherOptions::default(), server_end)
955        .map_err(WatcherCreationError::GetWatcher)?;
956
957    let stream = futures::stream::try_unfold(watcher, |watcher| async {
958        let events = watcher.watch().await.map_err(WatchError::Fidl)?;
959        if events.is_empty() {
960            return Err(WatchError::EmptyEventBatch);
961        }
962
963        let event_stream = futures::stream::iter(events).map(Ok).and_then(|event| {
964            futures::future::ready(event.try_into().map_err(WatchError::Conversion))
965        });
966        Ok(Some((event_stream, watcher)))
967    })
968    .try_flatten();
969
970    Ok(stream)
971}
972
973/// Errors returned by [`get_existing_resources`].
974#[derive(Debug, Error)]
975pub enum GetExistingResourcesError {
976    /// There was an error in the event stream.
977    #[error("there was an error in the event stream: {0}")]
978    ErrorInStream(WatchError),
979    /// There was an unexpected event in the event stream. Only `existing` or
980    /// `idle` events are expected.
981    #[error("there was an unexpected event in the event stream: {0:?}")]
982    UnexpectedEvent(Event),
983    /// A duplicate existing resource was reported in the event stream.
984    #[error("a duplicate existing resource was reported")]
985    DuplicateResource(Resource),
986    /// The event stream unexpectedly ended.
987    #[error("the event stream unexpectedly ended")]
988    StreamEnded,
989}
990
991/// A trait for types holding filtering state that can be updated by change
992/// events.
993pub trait Update {
994    /// Add the resource to the specified controller's state.
995    ///
996    /// Optionally returns a resource that has already been added to the
997    /// controller with the same [`ResourceId`].
998    fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource>;
999
1000    /// Remove the resource from the specified controller's state.
1001    ///
1002    /// Returns the removed resource, if present.
1003    fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource>;
1004}
1005
1006impl Update for HashMap<ControllerId, HashMap<ResourceId, Resource>> {
1007    fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource> {
1008        self.entry(controller).or_default().insert(resource.id(), resource)
1009    }
1010
1011    fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource> {
1012        self.get_mut(&controller)?.remove(resource)
1013    }
1014}
1015
1016/// Collects all `existing` events from the stream, stopping once the `idle`
1017/// event is observed.
1018pub async fn get_existing_resources<C: Update + Default>(
1019    stream: impl Stream<Item = Result<Event, WatchError>>,
1020) -> Result<C, GetExistingResourcesError> {
1021    async_utils::fold::fold_while(
1022        stream,
1023        Ok(C::default()),
1024        |resources: Result<C, GetExistingResourcesError>, event| {
1025            let mut resources =
1026                resources.expect("`resources` must be `Ok`, because we stop folding on err");
1027            futures::future::ready(match event {
1028                Err(e) => FoldWhile::Done(Err(GetExistingResourcesError::ErrorInStream(e))),
1029                Ok(e) => match e {
1030                    Event::Existing(controller, resource) => {
1031                        if let Some(resource) = resources.add(controller, resource) {
1032                            FoldWhile::Done(Err(GetExistingResourcesError::DuplicateResource(
1033                                resource,
1034                            )))
1035                        } else {
1036                            FoldWhile::Continue(Ok(resources))
1037                        }
1038                    }
1039                    Event::Idle => FoldWhile::Done(Ok(resources)),
1040                    e @ (Event::Added(_, _) | Event::Removed(_, _) | Event::EndOfUpdate) => {
1041                        FoldWhile::Done(Err(GetExistingResourcesError::UnexpectedEvent(e)))
1042                    }
1043                },
1044            })
1045        },
1046    )
1047    .await
1048    .short_circuited()
1049    .map_err(|_resources| GetExistingResourcesError::StreamEnded)?
1050}
1051
1052/// Errors returned by [`wait_for_condition`].
1053#[derive(Debug, Error)]
1054pub enum WaitForConditionError {
1055    /// There was an error in the event stream.
1056    #[error("there was an error in the event stream: {0}")]
1057    ErrorInStream(WatchError),
1058    /// There was an `Added` event for an already existing resource.
1059    #[error("observed an added event for an already existing resource: {0:?}")]
1060    AddedAlreadyExisting(Resource),
1061    /// There was a `Removed` event for a non-existent resource.
1062    #[error("observed a removed event for a non-existent resource: {0:?}")]
1063    RemovedNonExistent(ResourceId),
1064    /// The event stream unexpectedly ended.
1065    #[error("the event stream unexpectedly ended")]
1066    StreamEnded,
1067}
1068
1069/// Wait for a condition on filtering state to be satisfied.
1070///
1071/// With the given `initial_state`, take events from `event_stream` and update
1072/// the state, calling `predicate` whenever the state changes. When predicates
1073/// returns `True` yield `Ok(())`.
1074pub async fn wait_for_condition<
1075    C: Update,
1076    S: Stream<Item = Result<Event, WatchError>>,
1077    F: Fn(&C) -> bool,
1078>(
1079    event_stream: S,
1080    initial_state: &mut C,
1081    predicate: F,
1082) -> Result<(), WaitForConditionError> {
1083    async_utils::fold::try_fold_while(
1084        event_stream.map_err(WaitForConditionError::ErrorInStream),
1085        initial_state,
1086        |resources: &mut C, event| {
1087            futures::future::ready(match event {
1088                Event::Existing(controller, resource) | Event::Added(controller, resource) => {
1089                    if let Some(resource) = resources.add(controller, resource) {
1090                        Err(WaitForConditionError::AddedAlreadyExisting(resource))
1091                    } else {
1092                        Ok(FoldWhile::Continue(resources))
1093                    }
1094                }
1095                Event::Removed(controller, resource) => resources
1096                    .remove(controller, &resource)
1097                    .map(|_| FoldWhile::Continue(resources))
1098                    .ok_or(WaitForConditionError::RemovedNonExistent(resource)),
1099                // Wait until a transactional update has been completed to call
1100                // the predicate so it's not run against partially-updated
1101                // state.
1102                Event::Idle | Event::EndOfUpdate => {
1103                    if predicate(&resources) {
1104                        Ok(FoldWhile::Done(()))
1105                    } else {
1106                        Ok(FoldWhile::Continue(resources))
1107                    }
1108                }
1109            })
1110        },
1111    )
1112    .await?
1113    .short_circuited()
1114    .map_err(|_resources: &mut C| WaitForConditionError::StreamEnded)
1115}
1116
1117/// Namespace controller creation errors.
1118#[derive(Debug, Error)]
1119pub enum ControllerCreationError {
1120    #[error("failed to create namespace controller proxy: {0}")]
1121    CreateProxy(fidl::Error),
1122    #[error("failed to open namespace controller: {0}")]
1123    OpenController(fidl::Error),
1124    #[error("server did not emit OnIdAssigned event")]
1125    NoIdAssigned,
1126    #[error("failed to observe ID assignment event: {0}")]
1127    IdAssignment(fidl::Error),
1128}
1129
1130/// Errors for individual changes pushed.
1131///
1132/// Extension type for the error variants of [`fnet_filter::ChangeValidationError`].
1133#[derive(Debug, Error, PartialEq)]
1134pub enum ChangeValidationError {
1135    #[error("change contains a resource that is missing a required field")]
1136    MissingRequiredField,
1137    #[error("rule specifies an invalid interface matcher")]
1138    InvalidInterfaceMatcher,
1139    #[error("rule specifies an invalid address matcher")]
1140    InvalidAddressMatcher,
1141    #[error("rule specifies an invalid port matcher")]
1142    InvalidPortMatcher,
1143    #[error("rule specifies an invalid transparent proxy action")]
1144    InvalidTransparentProxyAction,
1145    #[error("rule specifies an invalid NAT action")]
1146    InvalidNatAction,
1147    #[error("rule specifies an invalid port range")]
1148    InvalidPortRange,
1149}
1150
1151impl TryFrom<fnet_filter::ChangeValidationError> for ChangeValidationError {
1152    type Error = FidlConversionError;
1153
1154    fn try_from(error: fnet_filter::ChangeValidationError) -> Result<Self, Self::Error> {
1155        match error {
1156            fnet_filter::ChangeValidationError::MissingRequiredField => {
1157                Ok(Self::MissingRequiredField)
1158            }
1159            fnet_filter::ChangeValidationError::InvalidInterfaceMatcher => {
1160                Ok(Self::InvalidInterfaceMatcher)
1161            }
1162            fnet_filter::ChangeValidationError::InvalidAddressMatcher => {
1163                Ok(Self::InvalidAddressMatcher)
1164            }
1165            fnet_filter::ChangeValidationError::InvalidPortMatcher => Ok(Self::InvalidPortMatcher),
1166            fnet_filter::ChangeValidationError::InvalidTransparentProxyAction => {
1167                Ok(Self::InvalidTransparentProxyAction)
1168            }
1169            fnet_filter::ChangeValidationError::InvalidNatAction => Ok(Self::InvalidNatAction),
1170            fnet_filter::ChangeValidationError::InvalidPortRange => Ok(Self::InvalidPortRange),
1171            fnet_filter::ChangeValidationError::Ok
1172            | fnet_filter::ChangeValidationError::NotReached => {
1173                Err(FidlConversionError::NotAnError)
1174            }
1175            fnet_filter::ChangeValidationError::__SourceBreaking { unknown_ordinal: _ } => {
1176                Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_ERROR))
1177            }
1178        }
1179    }
1180}
1181
1182/// Errors for the NamespaceController.PushChanges method.
1183#[derive(Debug, Error)]
1184pub enum PushChangesError {
1185    #[error("failed to call FIDL method: {0}")]
1186    CallMethod(fidl::Error),
1187    #[error("too many changes were pushed to the server")]
1188    TooManyChanges,
1189    #[error("invalid change(s) pushed: {0:?}")]
1190    ErrorOnChange(Vec<(Change, ChangeValidationError)>),
1191    #[error("unknown FIDL type: {0}")]
1192    FidlConversion(#[from] FidlConversionError),
1193}
1194
1195/// Errors for individual changes committed.
1196///
1197/// Extension type for the error variants of [`fnet_filter::CommitError`].
1198#[derive(Debug, Error, PartialEq)]
1199pub enum ChangeCommitError {
1200    #[error("the change referred to an unknown namespace")]
1201    NamespaceNotFound,
1202    #[error("the change referred to an unknown routine")]
1203    RoutineNotFound,
1204    #[error("the change referred to an unknown rule")]
1205    RuleNotFound,
1206    #[error("the specified resource already exists")]
1207    AlreadyExists,
1208    #[error("the change includes a rule that jumps to an installed routine")]
1209    TargetRoutineIsInstalled,
1210}
1211
1212impl TryFrom<fnet_filter::CommitError> for ChangeCommitError {
1213    type Error = FidlConversionError;
1214
1215    fn try_from(error: fnet_filter::CommitError) -> Result<Self, Self::Error> {
1216        match error {
1217            fnet_filter::CommitError::NamespaceNotFound => Ok(Self::NamespaceNotFound),
1218            fnet_filter::CommitError::RoutineNotFound => Ok(Self::RoutineNotFound),
1219            fnet_filter::CommitError::RuleNotFound => Ok(Self::RuleNotFound),
1220            fnet_filter::CommitError::AlreadyExists => Ok(Self::AlreadyExists),
1221            fnet_filter::CommitError::TargetRoutineIsInstalled => {
1222                Ok(Self::TargetRoutineIsInstalled)
1223            }
1224            fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1225                Err(FidlConversionError::NotAnError)
1226            }
1227            fnet_filter::CommitError::__SourceBreaking { unknown_ordinal: _ } => {
1228                Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR))
1229            }
1230        }
1231    }
1232}
1233
1234/// Errors for the NamespaceController.Commit method.
1235#[derive(Debug, Error)]
1236pub enum CommitError {
1237    #[error("failed to call FIDL method: {0}")]
1238    CallMethod(fidl::Error),
1239    #[error("rule has a matcher that is unavailable in its context: {0:?}")]
1240    RuleWithInvalidMatcher(RuleId),
1241    #[error("rule has an action that is invalid for its routine: {0:?}")]
1242    RuleWithInvalidAction(RuleId),
1243    #[error("rule has a TransparentProxy action but not a valid transport protocol matcher: {0:?}")]
1244    TransparentProxyWithInvalidMatcher(RuleId),
1245    #[error(
1246        "rule has a Redirect action that specifies a destination port but not a valid transport \
1247        protocol matcher: {0:?}"
1248    )]
1249    RedirectWithInvalidMatcher(RuleId),
1250    #[error(
1251        "rule has a Masquerade action that specifies a source port but not a valid transport \
1252        protocol matcher: {0:?}"
1253    )]
1254    MasqueradeWithInvalidMatcher(RuleId),
1255    #[error("routine forms a cycle {0:?}")]
1256    CyclicalRoutineGraph(RoutineId),
1257    #[error("invalid change was pushed: {0:?}")]
1258    ErrorOnChange(Vec<(Change, ChangeCommitError)>),
1259    #[error("unknown FIDL type: {0}")]
1260    FidlConversion(#[from] FidlConversionError),
1261}
1262
1263/// Extension type for [`fnet_filter::Change`].
1264#[derive(Debug, Clone, PartialEq)]
1265pub enum Change {
1266    Create(Resource),
1267    Remove(ResourceId),
1268}
1269
1270impl From<Change> for fnet_filter::Change {
1271    fn from(change: Change) -> Self {
1272        match change {
1273            Change::Create(resource) => Self::Create(resource.into()),
1274            Change::Remove(resource) => Self::Remove(resource.into()),
1275        }
1276    }
1277}
1278
1279impl TryFrom<fnet_filter::Change> for Change {
1280    type Error = FidlConversionError;
1281
1282    fn try_from(change: fnet_filter::Change) -> Result<Self, Self::Error> {
1283        match change {
1284            fnet_filter::Change::Create(resource) => Ok(Self::Create(resource.try_into()?)),
1285            fnet_filter::Change::Remove(resource) => Ok(Self::Remove(resource.try_into()?)),
1286            fnet_filter::Change::__SourceBreaking { .. } => {
1287                Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1288            }
1289        }
1290    }
1291}
1292
1293/// A controller for filtering state.
1294pub struct Controller {
1295    controller: fnet_filter::NamespaceControllerProxy,
1296    // The client provides an ID when creating a new controller, but the server
1297    // may need to assign a different ID to avoid conflicts; either way, the
1298    // server informs the client of the final `ControllerId` on creation.
1299    id: ControllerId,
1300    // Changes that have been pushed to the server but not yet committed. This
1301    // allows the `Controller` to report more informative errors by correlating
1302    // error codes with particular changes.
1303    pending_changes: Vec<Change>,
1304}
1305
1306impl Controller {
1307    pub async fn new_root(
1308        root: &fnet_root::FilterProxy,
1309        ControllerId(id): &ControllerId,
1310    ) -> Result<Self, ControllerCreationError> {
1311        let (controller, server_end) = fidl::endpoints::create_proxy();
1312        root.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1313
1314        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1315            .take_event_stream()
1316            .next()
1317            .await
1318            .ok_or(ControllerCreationError::NoIdAssigned)?
1319            .map_err(ControllerCreationError::IdAssignment)?;
1320        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1321    }
1322
1323    /// Creates a new `Controller`.
1324    ///
1325    /// Note that the provided `ControllerId` may need to be modified server-
1326    /// side to avoid collisions; to obtain the final ID assigned to the
1327    /// `Controller`, use the `id` method.
1328    pub async fn new(
1329        control: &fnet_filter::ControlProxy,
1330        ControllerId(id): &ControllerId,
1331    ) -> Result<Self, ControllerCreationError> {
1332        let (controller, server_end) = fidl::endpoints::create_proxy();
1333        control.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1334
1335        let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1336            .take_event_stream()
1337            .next()
1338            .await
1339            .ok_or(ControllerCreationError::NoIdAssigned)?
1340            .map_err(ControllerCreationError::IdAssignment)?;
1341        Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1342    }
1343
1344    pub fn id(&self) -> &ControllerId {
1345        &self.id
1346    }
1347
1348    pub async fn push_changes(&mut self, changes: Vec<Change>) -> Result<(), PushChangesError> {
1349        let fidl_changes = changes.iter().cloned().map(Into::into).collect::<Vec<_>>();
1350        let result = self
1351            .controller
1352            .push_changes(&fidl_changes)
1353            .await
1354            .map_err(PushChangesError::CallMethod)?;
1355        handle_change_validation_result(result, &changes)?;
1356        // Maintain a client-side copy of the pending changes we've pushed to
1357        // the server in order to provide better error messages if a commit
1358        // fails.
1359        self.pending_changes.extend(changes);
1360        Ok(())
1361    }
1362
1363    async fn commit_with_options(
1364        &mut self,
1365        options: fnet_filter::CommitOptions,
1366    ) -> Result<(), CommitError> {
1367        let committed_changes = std::mem::take(&mut self.pending_changes);
1368        let result = self.controller.commit(options).await.map_err(CommitError::CallMethod)?;
1369        handle_commit_result(result, committed_changes)
1370    }
1371
1372    pub async fn commit(&mut self) -> Result<(), CommitError> {
1373        self.commit_with_options(fnet_filter::CommitOptions::default()).await
1374    }
1375
1376    pub async fn commit_idempotent(&mut self) -> Result<(), CommitError> {
1377        self.commit_with_options(fnet_filter::CommitOptions {
1378            idempotent: Some(true),
1379            __source_breaking: SourceBreaking,
1380        })
1381        .await
1382    }
1383}
1384
1385pub(crate) fn handle_change_validation_result(
1386    change_validation_result: fnet_filter::ChangeValidationResult,
1387    changes: &Vec<Change>,
1388) -> Result<(), PushChangesError> {
1389    match change_validation_result {
1390        fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}) => Ok(()),
1391        fnet_filter::ChangeValidationResult::TooManyChanges(fnet_filter::Empty {}) => {
1392            Err(PushChangesError::TooManyChanges)
1393        }
1394        fnet_filter::ChangeValidationResult::ErrorOnChange(results) => {
1395            let errors: Result<_, PushChangesError> =
1396                changes.iter().zip(results).try_fold(Vec::new(), |mut errors, (change, result)| {
1397                    match result {
1398                        fnet_filter::ChangeValidationError::Ok
1399                        | fnet_filter::ChangeValidationError::NotReached => Ok(errors),
1400                        error @ (fnet_filter::ChangeValidationError::MissingRequiredField
1401                        | fnet_filter::ChangeValidationError::InvalidInterfaceMatcher
1402                        | fnet_filter::ChangeValidationError::InvalidAddressMatcher
1403                        | fnet_filter::ChangeValidationError::InvalidPortMatcher
1404                        | fnet_filter::ChangeValidationError::InvalidTransparentProxyAction
1405                        | fnet_filter::ChangeValidationError::InvalidNatAction
1406                        | fnet_filter::ChangeValidationError::InvalidPortRange) => {
1407                            let error = error
1408                                .try_into()
1409                                .expect("`Ok` and `NotReached` are handled in another arm");
1410                            errors.push((change.clone(), error));
1411                            Ok(errors)
1412                        }
1413                        fnet_filter::ChangeValidationError::__SourceBreaking { .. } => {
1414                            Err(FidlConversionError::UnknownUnionVariant(
1415                                type_names::CHANGE_VALIDATION_ERROR,
1416                            )
1417                            .into())
1418                        }
1419                    }
1420                });
1421            Err(PushChangesError::ErrorOnChange(errors?))
1422        }
1423        fnet_filter::ChangeValidationResult::__SourceBreaking { .. } => {
1424            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_RESULT)
1425                .into())
1426        }
1427    }
1428}
1429
1430pub(crate) fn handle_commit_result(
1431    commit_result: fnet_filter::CommitResult,
1432    committed_changes: Vec<Change>,
1433) -> Result<(), CommitError> {
1434    match commit_result {
1435        fnet_filter::CommitResult::Ok(fnet_filter::Empty {}) => Ok(()),
1436        fnet_filter::CommitResult::RuleWithInvalidMatcher(rule_id) => {
1437            Err(CommitError::RuleWithInvalidMatcher(rule_id.into()))
1438        }
1439        fnet_filter::CommitResult::RuleWithInvalidAction(rule_id) => {
1440            Err(CommitError::RuleWithInvalidAction(rule_id.into()))
1441        }
1442        fnet_filter::CommitResult::TransparentProxyWithInvalidMatcher(rule_id) => {
1443            Err(CommitError::TransparentProxyWithInvalidMatcher(rule_id.into()))
1444        }
1445        fnet_filter::CommitResult::RedirectWithInvalidMatcher(rule_id) => {
1446            Err(CommitError::RedirectWithInvalidMatcher(rule_id.into()))
1447        }
1448        fnet_filter::CommitResult::MasqueradeWithInvalidMatcher(rule_id) => {
1449            Err(CommitError::MasqueradeWithInvalidMatcher(rule_id.into()))
1450        }
1451        fnet_filter::CommitResult::CyclicalRoutineGraph(routine_id) => {
1452            Err(CommitError::CyclicalRoutineGraph(routine_id.into()))
1453        }
1454        fnet_filter::CommitResult::ErrorOnChange(results) => {
1455            let errors: Result<_, CommitError> = committed_changes
1456                .into_iter()
1457                .zip(results)
1458                .try_fold(Vec::new(), |mut errors, (change, result)| match result {
1459                    fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1460                        Ok(errors)
1461                    }
1462                    error @ (fnet_filter::CommitError::NamespaceNotFound
1463                    | fnet_filter::CommitError::RoutineNotFound
1464                    | fnet_filter::CommitError::RuleNotFound
1465                    | fnet_filter::CommitError::AlreadyExists
1466                    | fnet_filter::CommitError::TargetRoutineIsInstalled) => {
1467                        let error = error
1468                            .try_into()
1469                            .expect("`Ok` and `NotReached` are handled in another arm");
1470                        errors.push((change, error));
1471                        Ok(errors)
1472                    }
1473                    fnet_filter::CommitError::__SourceBreaking { .. } => {
1474                        Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR)
1475                            .into())
1476                    }
1477                });
1478            Err(CommitError::ErrorOnChange(errors?))
1479        }
1480        fnet_filter::CommitResult::__SourceBreaking { .. } => {
1481            Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_RESULT).into())
1482        }
1483    }
1484}
1485
1486#[cfg(test)]
1487mod tests {
1488
1489    use assert_matches::assert_matches;
1490    use fidl_fuchsia_net_matchers as fnet_matchers;
1491    use futures::channel::mpsc;
1492    use futures::task::Poll;
1493    use futures::{FutureExt as _, SinkExt as _};
1494    use test_case::test_case;
1495
1496    use {
1497        fidl_fuchsia_hardware_network as fhardware_network,
1498        fidl_fuchsia_net_interfaces as fnet_interfaces,
1499    };
1500
1501    use super::*;
1502
1503    #[test_case(
1504        fnet_filter::ResourceId::Namespace(String::from("namespace")),
1505        ResourceId::Namespace(NamespaceId(String::from("namespace")));
1506        "NamespaceId"
1507    )]
1508    #[test_case(fnet_filter::Domain::Ipv4, Domain::Ipv4; "Domain")]
1509    #[test_case(
1510        fnet_filter::Namespace {
1511            id: Some(String::from("namespace")),
1512            domain: Some(fnet_filter::Domain::Ipv4),
1513            ..Default::default()
1514        },
1515        Namespace { id: NamespaceId(String::from("namespace")), domain: Domain::Ipv4 };
1516        "Namespace"
1517    )]
1518    #[test_case(fnet_filter::IpInstallationHook::Egress, IpHook::Egress; "IpHook")]
1519    #[test_case(fnet_filter::NatInstallationHook::Egress, NatHook::Egress; "NatHook")]
1520    #[test_case(
1521        fnet_filter::InstalledIpRoutine {
1522            hook: Some(fnet_filter::IpInstallationHook::Egress),
1523            priority: Some(1),
1524            ..Default::default()
1525        },
1526        InstalledIpRoutine {
1527            hook: IpHook::Egress,
1528            priority: 1,
1529        };
1530        "InstalledIpRoutine"
1531    )]
1532    #[test_case(
1533        fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
1534            installation: Some(fnet_filter::InstalledIpRoutine {
1535                hook: Some(fnet_filter::IpInstallationHook::LocalEgress),
1536                priority: Some(1),
1537                ..Default::default()
1538            }),
1539            ..Default::default()
1540        }),
1541        RoutineType::Ip(Some(InstalledIpRoutine { hook: IpHook::LocalEgress, priority: 1 }));
1542        "RoutineType"
1543    )]
1544    #[test_case(
1545        fnet_filter::Routine {
1546            id: Some(fnet_filter::RoutineId {
1547                namespace: String::from("namespace"),
1548                name: String::from("routine"),
1549            }),
1550            type_: Some(fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine::default())),
1551            ..Default::default()
1552        },
1553        Routine {
1554            id: RoutineId {
1555                namespace: NamespaceId(String::from("namespace")),
1556                name: String::from("routine"),
1557            },
1558            routine_type: RoutineType::Nat(None),
1559        };
1560        "Routine"
1561    )]
1562    #[test_case(
1563        fnet_filter::Matchers {
1564            in_interface: Some(fnet_matchers::Interface::Name(String::from("wlan"))),
1565            transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
1566                src_port: None,
1567                dst_port: Some(fnet_matchers::Port { start: 22, end: 22, invert: false }),
1568                ..Default::default()
1569            })),
1570            ..Default::default()
1571        },
1572        Matchers {
1573            in_interface: Some(fnet_matchers_ext::Interface::Name(String::from("wlan"))),
1574            transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
1575                src_port: None,
1576                dst_port: Some(fnet_matchers_ext::Port::new(22, 22, false).unwrap()),
1577            }),
1578            ..Default::default()
1579        };
1580        "Matchers"
1581    )]
1582    #[test_case(
1583        fnet_filter::Action::Accept(fnet_filter::Empty {}),
1584        Action::Accept;
1585        "Action"
1586    )]
1587    #[test_case(
1588        fnet_filter::Rule {
1589            id: fnet_filter::RuleId {
1590                routine: fnet_filter::RoutineId {
1591                    namespace: String::from("namespace"),
1592                    name: String::from("routine"),
1593                },
1594                index: 1,
1595            },
1596            matchers: fnet_filter::Matchers {
1597                transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Icmp(
1598                    fnet_matchers::IcmpPacket::default()
1599                )),
1600                ..Default::default()
1601            },
1602            action: fnet_filter::Action::Drop(fnet_filter::Empty {}),
1603        },
1604        Rule {
1605            id: RuleId {
1606                routine: RoutineId {
1607                    namespace: NamespaceId(String::from("namespace")),
1608                    name: String::from("routine"),
1609                },
1610                index: 1,
1611            },
1612            matchers: Matchers {
1613                transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
1614                ..Default::default()
1615            },
1616            action: Action::Drop,
1617        };
1618        "Rule"
1619    )]
1620    #[test_case(
1621        fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1622            id: Some(String::from("namespace")),
1623            domain: Some(fnet_filter::Domain::Ipv4),
1624            ..Default::default()
1625        }),
1626        Resource::Namespace(Namespace {
1627            id: NamespaceId(String::from("namespace")),
1628            domain: Domain::Ipv4
1629        });
1630        "Resource"
1631    )]
1632    #[test_case(
1633        fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}),
1634        Event::EndOfUpdate;
1635        "Event"
1636    )]
1637    #[test_case(
1638        fnet_filter::Change::Remove(fnet_filter::ResourceId::Namespace(String::from("namespace"))),
1639        Change::Remove(ResourceId::Namespace(NamespaceId(String::from("namespace"))));
1640        "Change"
1641    )]
1642    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1643    where
1644        E: TryFrom<F> + Clone + Debug + PartialEq,
1645        <E as TryFrom<F>>::Error: Debug + PartialEq,
1646        F: From<E> + Clone + Debug + PartialEq,
1647    {
1648        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1649        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1650    }
1651
1652    #[test]
1653    fn resource_id_try_from_unknown_variant() {
1654        assert_eq!(
1655            ResourceId::try_from(fnet_filter::ResourceId::__SourceBreaking { unknown_ordinal: 0 }),
1656            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
1657        );
1658    }
1659
1660    #[test]
1661    fn domain_try_from_unknown_variant() {
1662        assert_eq!(
1663            Domain::try_from(fnet_filter::Domain::__SourceBreaking { unknown_ordinal: 0 }),
1664            Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
1665        );
1666    }
1667
1668    #[test]
1669    fn namespace_try_from_missing_properties() {
1670        assert_eq!(
1671            Namespace::try_from(fnet_filter::Namespace {
1672                id: None,
1673                domain: Some(fnet_filter::Domain::Ipv4),
1674                ..Default::default()
1675            }),
1676            Err(FidlConversionError::MissingNamespaceId)
1677        );
1678        assert_eq!(
1679            Namespace::try_from(fnet_filter::Namespace {
1680                id: Some(String::from("namespace")),
1681                domain: None,
1682                ..Default::default()
1683            }),
1684            Err(FidlConversionError::MissingNamespaceDomain)
1685        );
1686    }
1687
1688    #[test]
1689    fn ip_installation_hook_try_from_unknown_variant() {
1690        assert_eq!(
1691            IpHook::try_from(fnet_filter::IpInstallationHook::__SourceBreaking {
1692                unknown_ordinal: 0
1693            }),
1694            Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
1695        );
1696    }
1697
1698    #[test]
1699    fn nat_installation_hook_try_from_unknown_variant() {
1700        assert_eq!(
1701            NatHook::try_from(fnet_filter::NatInstallationHook::__SourceBreaking {
1702                unknown_ordinal: 0
1703            }),
1704            Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
1705        );
1706    }
1707
1708    #[test]
1709    fn installed_ip_routine_try_from_missing_hook() {
1710        assert_eq!(
1711            InstalledIpRoutine::try_from(fnet_filter::InstalledIpRoutine {
1712                hook: None,
1713                ..Default::default()
1714            }),
1715            Err(FidlConversionError::MissingIpInstallationHook)
1716        );
1717    }
1718
1719    #[test]
1720    fn installed_nat_routine_try_from_missing_hook() {
1721        assert_eq!(
1722            InstalledNatRoutine::try_from(fnet_filter::InstalledNatRoutine {
1723                hook: None,
1724                ..Default::default()
1725            }),
1726            Err(FidlConversionError::MissingNatInstallationHook)
1727        );
1728    }
1729
1730    #[test]
1731    fn routine_type_try_from_unknown_variant() {
1732        assert_eq!(
1733            RoutineType::try_from(fnet_filter::RoutineType::__SourceBreaking {
1734                unknown_ordinal: 0
1735            }),
1736            Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
1737        );
1738    }
1739
1740    #[test]
1741    fn routine_try_from_missing_properties() {
1742        assert_eq!(
1743            Routine::try_from(fnet_filter::Routine { id: None, ..Default::default() }),
1744            Err(FidlConversionError::MissingRoutineId)
1745        );
1746        assert_eq!(
1747            Routine::try_from(fnet_filter::Routine {
1748                id: Some(fnet_filter::RoutineId {
1749                    namespace: String::from("namespace"),
1750                    name: String::from("routine"),
1751                }),
1752                type_: None,
1753                ..Default::default()
1754            }),
1755            Err(FidlConversionError::MissingRoutineType)
1756        );
1757    }
1758
1759    #[test_case(
1760        fnet_matchers_ext::PortError::InvalidPortRange =>
1761        FidlConversionError::InvalidPortMatcherRange
1762    )]
1763    #[test_case(
1764        fnet_matchers_ext::InterfaceError::ZeroId =>
1765        FidlConversionError::ZeroInterfaceId
1766    )]
1767    #[test_case(
1768        fnet_matchers_ext::InterfaceError::UnknownUnionVariant =>
1769        FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
1770    )]
1771    #[test_case(
1772        {
1773            let invalid_port_class = fnet_interfaces::PortClass::__SourceBreaking {
1774                unknown_ordinal: 0
1775            };
1776            let error = fnet_interfaces_ext::PortClass::try_from(
1777                invalid_port_class
1778            ).unwrap_err();
1779            fnet_matchers_ext::InterfaceError::UnknownPortClass(error)
1780        } =>
1781        FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
1782    )]
1783    #[test_case(
1784        {
1785            let invalid_port_class = fhardware_network::PortClass::__SourceBreaking {
1786                unknown_ordinal: 0
1787            };
1788            let error = fnet_interfaces_ext::PortClass::try_from(
1789                invalid_port_class
1790            ).unwrap_err();
1791            fnet_matchers_ext::InterfaceError::UnknownPortClass(
1792                fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(error))
1793        } =>
1794        FidlConversionError::UnknownUnionVariant(type_names::HARDWARE_NETWORK_PORT_CLASS)
1795    )]
1796    #[test_case(
1797        fnet_matchers_ext::SubnetError::PrefixTooLong =>
1798        FidlConversionError::SubnetPrefixTooLong
1799    )]
1800    #[test_case(
1801        fnet_matchers_ext::SubnetError::HostBitsSet =>
1802        FidlConversionError::SubnetHostBitsSet
1803    )]
1804    #[test_case(
1805        fnet_matchers_ext::AddressRangeError::Invalid =>
1806        FidlConversionError::InvalidAddressRange
1807    )]
1808    #[test_case(
1809        fnet_matchers_ext::AddressRangeError::FamilyMismatch =>
1810        FidlConversionError::AddressRangeFamilyMismatch
1811    )]
1812    #[test_case(
1813        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1814            fnet_matchers_ext::SubnetError::PrefixTooLong) =>
1815        FidlConversionError::SubnetPrefixTooLong
1816    )]
1817    #[test_case(
1818        fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1819            fnet_matchers_ext::SubnetError::HostBitsSet) =>
1820        FidlConversionError::SubnetHostBitsSet
1821    )]
1822    #[test_case(
1823        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1824            fnet_matchers_ext::AddressRangeError::Invalid) =>
1825        FidlConversionError::InvalidAddressRange
1826    )]
1827    #[test_case(
1828        fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1829            fnet_matchers_ext::AddressRangeError::FamilyMismatch) =>
1830        FidlConversionError::AddressRangeFamilyMismatch
1831    )]
1832    #[test_case(
1833        fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant =>
1834        FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1835    )]
1836    #[test_case(
1837        fnet_matchers_ext::AddressError::AddressMatcherType(
1838            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1839                fnet_matchers_ext::SubnetError::PrefixTooLong)) =>
1840        FidlConversionError::SubnetPrefixTooLong
1841    )]
1842    #[test_case(
1843        fnet_matchers_ext::AddressError::AddressMatcherType(
1844            fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1845                fnet_matchers_ext::SubnetError::HostBitsSet)) =>
1846        FidlConversionError::SubnetHostBitsSet
1847    )]
1848    #[test_case(
1849        fnet_matchers_ext::AddressError::AddressMatcherType(
1850            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1851                fnet_matchers_ext::AddressRangeError::Invalid)) =>
1852        FidlConversionError::InvalidAddressRange
1853    )]
1854    #[test_case(
1855        fnet_matchers_ext::AddressError::AddressMatcherType(
1856            fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1857                fnet_matchers_ext::AddressRangeError::FamilyMismatch)) =>
1858        FidlConversionError::AddressRangeFamilyMismatch
1859    )]
1860    #[test_case(
1861        fnet_matchers_ext::AddressError::AddressMatcherType(
1862            fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant) =>
1863            FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1864    )]
1865    #[test_case(
1866        fnet_matchers_ext::TransportProtocolError::Port(
1867            fnet_matchers_ext::PortError::InvalidPortRange) =>
1868        FidlConversionError::InvalidPortMatcherRange
1869    )]
1870    #[test_case(
1871        fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant =>
1872            FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
1873    )]
1874    fn fidl_error_from_matcher_error<E: Into<FidlConversionError>>(
1875        error: E,
1876    ) -> FidlConversionError {
1877        error.into()
1878    }
1879
1880    #[test]
1881    fn action_try_from_unknown_variant() {
1882        assert_eq!(
1883            Action::try_from(fnet_filter::Action::__SourceBreaking { unknown_ordinal: 0 }),
1884            Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
1885        );
1886    }
1887
1888    #[test]
1889    fn resource_try_from_unknown_variant() {
1890        assert_eq!(
1891            Resource::try_from(fnet_filter::Resource::__SourceBreaking { unknown_ordinal: 0 }),
1892            Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
1893        );
1894    }
1895
1896    #[test]
1897    fn event_try_from_unknown_variant() {
1898        assert_eq!(
1899            Event::try_from(fnet_filter::Event::__SourceBreaking { unknown_ordinal: 0 }),
1900            Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
1901        );
1902    }
1903
1904    #[test]
1905    fn change_try_from_unknown_variant() {
1906        assert_eq!(
1907            Change::try_from(fnet_filter::Change::__SourceBreaking { unknown_ordinal: 0 }),
1908            Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1909        );
1910    }
1911
1912    fn test_controller_a() -> ControllerId {
1913        ControllerId(String::from("test-controller-a"))
1914    }
1915
1916    fn test_controller_b() -> ControllerId {
1917        ControllerId(String::from("test-controller-b"))
1918    }
1919
1920    pub(crate) fn test_resource_id() -> ResourceId {
1921        ResourceId::Namespace(NamespaceId(String::from("test-namespace")))
1922    }
1923
1924    pub(crate) fn test_resource() -> Resource {
1925        Resource::Namespace(Namespace {
1926            id: NamespaceId(String::from("test-namespace")),
1927            domain: Domain::AllIp,
1928        })
1929    }
1930
1931    // We can't easily create an invalid resource, so we just pretend and fake
1932    // the server response in tests.
1933    pub(crate) fn pretend_invalid_resource() -> Resource {
1934        Resource::Namespace(Namespace {
1935            id: NamespaceId(String::from("pretend-invalid-namespace")),
1936            domain: Domain::AllIp,
1937        })
1938    }
1939
1940    pub(crate) fn unknown_resource_id() -> ResourceId {
1941        ResourceId::Namespace(NamespaceId(String::from("does-not-exist")))
1942    }
1943
1944    #[fuchsia_async::run_singlethreaded(test)]
1945    async fn event_stream_from_state_conversion_error() {
1946        let (proxy, mut request_stream) =
1947            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
1948        let stream = event_stream_from_state(proxy).expect("get event stream");
1949        futures::pin_mut!(stream);
1950
1951        let send_invalid_event = async {
1952            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
1953                request_stream
1954                    .next()
1955                    .await
1956                    .expect("client should call state")
1957                    .expect("request should not error");
1958            let fnet_filter::WatcherRequest::Watch { responder } = request
1959                .into_stream()
1960                .next()
1961                .await
1962                .expect("client should call watch")
1963                .expect("request should not error");
1964            responder
1965                .send(&[fnet_filter::Event::Added(fnet_filter::AddedResource {
1966                    controller: String::from("controller"),
1967                    resource: fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1968                        id: None,
1969                        domain: None,
1970                        ..Default::default()
1971                    }),
1972                })])
1973                .expect("send batch with invalid event");
1974        };
1975        let ((), result) = futures::future::join(send_invalid_event, stream.next()).await;
1976        assert_matches!(
1977            result,
1978            Some(Err(WatchError::Conversion(FidlConversionError::MissingNamespaceId)))
1979        );
1980    }
1981
1982    #[fuchsia_async::run_singlethreaded(test)]
1983    async fn event_stream_from_state_empty_event_batch() {
1984        let (proxy, mut request_stream) =
1985            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
1986        let stream = event_stream_from_state(proxy).expect("get event stream");
1987        futures::pin_mut!(stream);
1988
1989        let send_empty_batch = async {
1990            let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
1991                request_stream
1992                    .next()
1993                    .await
1994                    .expect("client should call state")
1995                    .expect("request should not error");
1996            let fnet_filter::WatcherRequest::Watch { responder } = request
1997                .into_stream()
1998                .next()
1999                .await
2000                .expect("client should call watch")
2001                .expect("request should not error");
2002            responder.send(&[]).expect("send empty batch");
2003        };
2004        let ((), result) = futures::future::join(send_empty_batch, stream.next()).await;
2005        assert_matches!(result, Some(Err(WatchError::EmptyEventBatch)));
2006    }
2007
2008    #[fuchsia_async::run_singlethreaded(test)]
2009    async fn get_existing_resources_success() {
2010        let event_stream = futures::stream::iter([
2011            Ok(Event::Existing(test_controller_a(), test_resource())),
2012            Ok(Event::Existing(test_controller_b(), test_resource())),
2013            Ok(Event::Idle),
2014            Ok(Event::Removed(test_controller_a(), test_resource_id())),
2015        ]);
2016        futures::pin_mut!(event_stream);
2017
2018        let existing = get_existing_resources::<HashMap<_, _>>(event_stream.by_ref())
2019            .await
2020            .expect("get existing resources");
2021        assert_eq!(
2022            existing,
2023            HashMap::from([
2024                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2025                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2026            ])
2027        );
2028
2029        let trailing_events = event_stream.collect::<Vec<_>>().await;
2030        assert_matches!(
2031            &trailing_events[..],
2032            [Ok(Event::Removed(controller, resource))] if controller == &test_controller_a() &&
2033                                                           resource == &test_resource_id()
2034        );
2035    }
2036
2037    #[fuchsia_async::run_singlethreaded(test)]
2038    async fn get_existing_resources_error_in_stream() {
2039        let event_stream =
2040            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2041        futures::pin_mut!(event_stream);
2042        assert_matches!(
2043            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2044            Err(GetExistingResourcesError::ErrorInStream(WatchError::EmptyEventBatch))
2045        )
2046    }
2047
2048    #[fuchsia_async::run_singlethreaded(test)]
2049    async fn get_existing_resources_unexpected_event() {
2050        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::EndOfUpdate)));
2051        futures::pin_mut!(event_stream);
2052        assert_matches!(
2053            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2054            Err(GetExistingResourcesError::UnexpectedEvent(Event::EndOfUpdate))
2055        )
2056    }
2057
2058    #[fuchsia_async::run_singlethreaded(test)]
2059    async fn get_existing_resources_duplicate_resource() {
2060        let event_stream = futures::stream::iter([
2061            Ok(Event::Existing(test_controller_a(), test_resource())),
2062            Ok(Event::Existing(test_controller_a(), test_resource())),
2063        ]);
2064        futures::pin_mut!(event_stream);
2065        assert_matches!(
2066            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2067            Err(GetExistingResourcesError::DuplicateResource(resource))
2068                if resource == test_resource()
2069        )
2070    }
2071
2072    #[fuchsia_async::run_singlethreaded(test)]
2073    async fn get_existing_resources_stream_ended() {
2074        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::Existing(
2075            test_controller_a(),
2076            test_resource(),
2077        ))));
2078        futures::pin_mut!(event_stream);
2079        assert_matches!(
2080            get_existing_resources::<HashMap<_, _>>(event_stream).await,
2081            Err(GetExistingResourcesError::StreamEnded)
2082        )
2083    }
2084
2085    #[fuchsia_async::run_singlethreaded(test)]
2086    async fn wait_for_condition_add_remove() {
2087        let mut state = HashMap::new();
2088
2089        // Verify that checking for the presence of a resource blocks until the
2090        // resource is added.
2091        let has_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2092            resources.get(&test_controller_a()).map_or(false, |controller| {
2093                controller
2094                    .get(&test_resource_id())
2095                    .map_or(false, |resource| resource == &test_resource())
2096            })
2097        };
2098        assert_matches!(
2099            wait_for_condition(futures::stream::pending(), &mut state, has_resource).now_or_never(),
2100            None
2101        );
2102        assert!(state.is_empty());
2103        assert_matches!(
2104            wait_for_condition(
2105                futures::stream::iter([
2106                    Ok(Event::Added(test_controller_b(), test_resource())),
2107                    Ok(Event::EndOfUpdate),
2108                    Ok(Event::Added(test_controller_a(), test_resource())),
2109                    Ok(Event::EndOfUpdate),
2110                ]),
2111                &mut state,
2112                has_resource
2113            )
2114            .now_or_never(),
2115            Some(Ok(()))
2116        );
2117        assert_eq!(
2118            state,
2119            HashMap::from([
2120                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2121                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2122            ])
2123        );
2124
2125        // Re-add the resource and observe an error.
2126        assert_matches!(
2127            wait_for_condition(
2128                futures::stream::iter([
2129                    Ok(Event::Added(test_controller_a(), test_resource())),
2130                    Ok(Event::EndOfUpdate),
2131                ]),
2132                &mut state,
2133                has_resource
2134            )
2135            .now_or_never(),
2136            Some(Err(WaitForConditionError::AddedAlreadyExisting(r))) if r == test_resource()
2137        );
2138        assert_eq!(
2139            state,
2140            HashMap::from([
2141                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2142                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2143            ])
2144        );
2145
2146        // Verify that checking for the absence of a resource blocks until the
2147        // resource is removed.
2148        let does_not_have_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2149            resources.get(&test_controller_a()).map_or(false, |controller| controller.is_empty())
2150        };
2151        assert_matches!(
2152            wait_for_condition(futures::stream::pending(), &mut state, does_not_have_resource)
2153                .now_or_never(),
2154            None
2155        );
2156        assert_eq!(
2157            state,
2158            HashMap::from([
2159                (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2160                (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2161            ])
2162        );
2163        assert_matches!(
2164            wait_for_condition(
2165                futures::stream::iter([
2166                    Ok(Event::Removed(test_controller_b(), test_resource_id())),
2167                    Ok(Event::EndOfUpdate),
2168                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2169                    Ok(Event::EndOfUpdate),
2170                ]),
2171                &mut state,
2172                does_not_have_resource
2173            )
2174            .now_or_never(),
2175            Some(Ok(()))
2176        );
2177        assert_eq!(
2178            state,
2179            HashMap::from([
2180                (test_controller_a(), HashMap::new()),
2181                (test_controller_b(), HashMap::new()),
2182            ])
2183        );
2184
2185        // Remove a non-existent resource and observe an error.
2186        assert_matches!(
2187            wait_for_condition(
2188                futures::stream::iter([
2189                    Ok(Event::Removed(test_controller_a(), test_resource_id())),
2190                    Ok(Event::EndOfUpdate),
2191                ]),
2192                &mut state,
2193                does_not_have_resource
2194            ).now_or_never(),
2195            Some(Err(WaitForConditionError::RemovedNonExistent(r))) if r == test_resource_id()
2196        );
2197        assert_eq!(
2198            state,
2199            HashMap::from([
2200                (test_controller_a(), HashMap::new()),
2201                (test_controller_b(), HashMap::new()),
2202            ])
2203        );
2204    }
2205
2206    #[test]
2207    fn predicate_not_tested_until_update_complete() {
2208        let mut state = HashMap::new();
2209        let (mut tx, rx) = mpsc::unbounded();
2210
2211        let wait = wait_for_condition(rx, &mut state, |state| !state.is_empty()).fuse();
2212        futures::pin_mut!(wait);
2213
2214        // Sending an `Added` event should *not* allow the wait operation to
2215        // complete, because the predicate should only be tested once the full
2216        // update has been observed.
2217        let mut exec = fuchsia_async::TestExecutor::new();
2218        exec.run_singlethreaded(async {
2219            tx.send(Ok(Event::Added(test_controller_a(), test_resource())))
2220                .await
2221                .expect("receiver should not be closed");
2222        });
2223        assert_matches!(exec.run_until_stalled(&mut wait), Poll::Pending);
2224
2225        exec.run_singlethreaded(async {
2226            tx.send(Ok(Event::EndOfUpdate)).await.expect("receiver should not be closed");
2227            wait.await.expect("condition should be satisfied once update is complete");
2228        });
2229    }
2230
2231    #[fuchsia_async::run_singlethreaded(test)]
2232    async fn wait_for_condition_error_in_stream() {
2233        let mut state = HashMap::new();
2234        let event_stream =
2235            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2236        assert_matches!(
2237            wait_for_condition(event_stream, &mut state, |_| true).await,
2238            Err(WaitForConditionError::ErrorInStream(WatchError::EmptyEventBatch))
2239        );
2240        assert!(state.is_empty());
2241    }
2242
2243    #[fuchsia_async::run_singlethreaded(test)]
2244    async fn wait_for_condition_stream_ended() {
2245        let mut state = HashMap::new();
2246        let event_stream = futures::stream::empty();
2247        assert_matches!(
2248            wait_for_condition(event_stream, &mut state, |_| true).await,
2249            Err(WaitForConditionError::StreamEnded)
2250        );
2251        assert!(state.is_empty());
2252    }
2253
2254    pub(crate) async fn handle_open_controller(
2255        mut request_stream: fnet_filter::ControlRequestStream,
2256    ) -> fnet_filter::NamespaceControllerRequestStream {
2257        let (id, request, _control_handle) = request_stream
2258            .next()
2259            .await
2260            .expect("client should open controller")
2261            .expect("request should not error")
2262            .into_open_controller()
2263            .expect("client should open controller");
2264        let (stream, control_handle) = request.into_stream_and_control_handle();
2265        control_handle.send_on_id_assigned(&id).expect("send assigned ID");
2266
2267        stream
2268    }
2269
2270    pub(crate) async fn handle_push_changes(
2271        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2272        push_changes_result: fnet_filter::ChangeValidationResult,
2273    ) {
2274        let (_changes, responder) = stream
2275            .next()
2276            .await
2277            .expect("client should push changes")
2278            .expect("request should not error")
2279            .into_push_changes()
2280            .expect("client should push changes");
2281        responder.send(push_changes_result).expect("send empty batch");
2282    }
2283
2284    pub(crate) async fn handle_commit(
2285        stream: &mut fnet_filter::NamespaceControllerRequestStream,
2286        commit_result: fnet_filter::CommitResult,
2287    ) {
2288        let (_options, responder) = stream
2289            .next()
2290            .await
2291            .expect("client should commit")
2292            .expect("request should not error")
2293            .into_commit()
2294            .expect("client should commit");
2295        responder.send(commit_result).expect("send commit result");
2296    }
2297
2298    #[fuchsia_async::run_singlethreaded(test)]
2299    async fn controller_push_changes_reports_invalid_change() {
2300        let (control, request_stream) =
2301            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2302        let push_invalid_change = async {
2303            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2304                .await
2305                .expect("create controller");
2306            let result = controller
2307                .push_changes(vec![
2308                    Change::Create(test_resource()),
2309                    // We fake the server response to say this is invalid even
2310                    // though it really isn't.
2311                    Change::Create(pretend_invalid_resource()),
2312                    Change::Remove(test_resource_id()),
2313                ])
2314                .await;
2315            assert_matches!(
2316                result,
2317                Err(PushChangesError::ErrorOnChange(errors)) if errors == vec![(
2318                    Change::Create(pretend_invalid_resource()),
2319                    ChangeValidationError::InvalidPortMatcher
2320                )]
2321            );
2322        };
2323
2324        let handle_controller = async {
2325            let mut stream = handle_open_controller(request_stream).await;
2326            handle_push_changes(
2327                &mut stream,
2328                fnet_filter::ChangeValidationResult::ErrorOnChange(vec![
2329                    fnet_filter::ChangeValidationError::Ok,
2330                    fnet_filter::ChangeValidationError::InvalidPortMatcher,
2331                    fnet_filter::ChangeValidationError::NotReached,
2332                ]),
2333            )
2334            .await;
2335        };
2336
2337        let ((), ()) = futures::future::join(push_invalid_change, handle_controller).await;
2338    }
2339
2340    #[fuchsia_async::run_singlethreaded(test)]
2341    async fn controller_commit_reports_invalid_change() {
2342        let (control, request_stream) =
2343            fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2344        let commit_invalid_change = async {
2345            let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2346                .await
2347                .expect("create controller");
2348            controller
2349                .push_changes(vec![
2350                    Change::Create(test_resource()),
2351                    Change::Remove(unknown_resource_id()),
2352                    Change::Remove(test_resource_id()),
2353                ])
2354                .await
2355                .expect("push changes");
2356            let result = controller.commit().await;
2357            assert_matches!(
2358                result,
2359                Err(CommitError::ErrorOnChange(errors)) if errors == vec![(
2360                    Change::Remove(unknown_resource_id()),
2361                    ChangeCommitError::NamespaceNotFound,
2362                )]
2363            );
2364        };
2365        let handle_controller = async {
2366            let mut stream = handle_open_controller(request_stream).await;
2367            handle_push_changes(
2368                &mut stream,
2369                fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}),
2370            )
2371            .await;
2372            handle_commit(
2373                &mut stream,
2374                fnet_filter::CommitResult::ErrorOnChange(vec![
2375                    fnet_filter::CommitError::Ok,
2376                    fnet_filter::CommitError::NamespaceNotFound,
2377                    fnet_filter::CommitError::Ok,
2378                ]),
2379            )
2380            .await;
2381        };
2382        let ((), ()) = futures::future::join(commit_invalid_change, handle_controller).await;
2383    }
2384}