1#[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#[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
166mod 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#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
193pub struct NamespaceId(pub String);
194
195#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 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#[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#[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 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#[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#[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
854pub struct ControllerId(pub String);
855
856#[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#[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#[derive(Debug, Error)]
931pub enum WatchError {
932 #[error("the call to `Watch()` failed: {0}")]
934 Fidl(fidl::Error),
935 #[error("failed to convert event returned by `Watch()`: {0}")]
937 Conversion(FidlConversionError),
938 #[error("the call to `Watch()` returned an empty batch of events")]
940 EmptyEventBatch,
941}
942
943pub 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#[derive(Debug, Error)]
975pub enum GetExistingResourcesError {
976 #[error("there was an error in the event stream: {0}")]
978 ErrorInStream(WatchError),
979 #[error("there was an unexpected event in the event stream: {0:?}")]
982 UnexpectedEvent(Event),
983 #[error("a duplicate existing resource was reported")]
985 DuplicateResource(Resource),
986 #[error("the event stream unexpectedly ended")]
988 StreamEnded,
989}
990
991pub trait Update {
994 fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource>;
999
1000 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
1016pub 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#[derive(Debug, Error)]
1054pub enum WaitForConditionError {
1055 #[error("there was an error in the event stream: {0}")]
1057 ErrorInStream(WatchError),
1058 #[error("observed an added event for an already existing resource: {0:?}")]
1060 AddedAlreadyExisting(Resource),
1061 #[error("observed a removed event for a non-existent resource: {0:?}")]
1063 RemovedNonExistent(ResourceId),
1064 #[error("the event stream unexpectedly ended")]
1066 StreamEnded,
1067}
1068
1069pub 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 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#[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#[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#[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#[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#[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#[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
1293pub struct Controller {
1295 controller: fnet_filter::NamespaceControllerProxy,
1296 id: ControllerId,
1300 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 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 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 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 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 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 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 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 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 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}