fidl_fuchsia_net_matchers_ext/
lib.rs

1// Copyright 2025 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.matchers 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.matchers` 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
14use std::fmt::Debug;
15use std::num::NonZeroU64;
16use std::ops::RangeInclusive;
17
18use fidl::marker::SourceBreaking;
19use fidl_fuchsia_net_ext::{FromExt, IntoExt};
20use net_types::ip::Ip;
21use thiserror::Error;
22use {
23    fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
24    fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
25    fidl_fuchsia_net_matchers as fnet_matchers,
26};
27
28/// Extension type for [`fnet_matchers::Interface`].
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum Interface {
31    Id(NonZeroU64),
32    Name(fnet_interfaces::Name),
33    PortClass(fnet_interfaces_ext::PortClass),
34}
35
36/// Errors when creating an [`Interface`].
37#[derive(Debug, Error, PartialEq, Eq)]
38pub enum InterfaceError {
39    #[error("interface matcher specified an invalid ID of 0")]
40    ZeroId,
41    #[error(transparent)]
42    UnknownPortClass(fnet_interfaces_ext::UnknownPortClassError),
43    #[error("interface union is of an unknown variant")]
44    UnknownUnionVariant,
45}
46
47impl From<Interface> for fnet_matchers::Interface {
48    fn from(matcher: Interface) -> Self {
49        match matcher {
50            Interface::Id(id) => Self::Id(id.get()),
51            Interface::Name(name) => Self::Name(name),
52            Interface::PortClass(port_class) => Self::PortClass(port_class.into()),
53        }
54    }
55}
56
57impl TryFrom<fnet_matchers::Interface> for Interface {
58    type Error = InterfaceError;
59
60    fn try_from(matcher: fnet_matchers::Interface) -> Result<Self, Self::Error> {
61        match matcher {
62            fnet_matchers::Interface::Id(id) => {
63                let id = NonZeroU64::new(id).ok_or(InterfaceError::ZeroId)?;
64                Ok(Self::Id(id))
65            }
66            fnet_matchers::Interface::Name(name) => Ok(Self::Name(name)),
67            fnet_matchers::Interface::PortClass(port_class) => {
68                port_class.try_into().map(Self::PortClass).map_err(InterfaceError::UnknownPortClass)
69            }
70            fnet_matchers::Interface::__SourceBreaking { .. } => {
71                Err(InterfaceError::UnknownUnionVariant)
72            }
73        }
74    }
75}
76
77/// Extension type for [`fnet_matchers::BoundInterface`].
78#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub enum BoundInterface {
80    Unbound,
81    Bound(Interface),
82}
83
84/// Errors when creating an [`BoundInterface`].
85#[derive(Debug, Error, PartialEq)]
86pub enum BoundInterfaceError {
87    #[error(transparent)]
88    Interface(InterfaceError),
89    #[error("interface union is of an unknown variant")]
90    UnknownUnionVariant(u64),
91}
92
93impl From<BoundInterface> for fnet_matchers::BoundInterface {
94    fn from(matcher: BoundInterface) -> Self {
95        match matcher {
96            BoundInterface::Unbound => fnet_matchers::BoundInterface::Unbound(fnet_matchers::Empty),
97            BoundInterface::Bound(interface) => {
98                fnet_matchers::BoundInterface::Bound(interface.into())
99            }
100        }
101    }
102}
103
104impl TryFrom<fnet_matchers::BoundInterface> for BoundInterface {
105    type Error = BoundInterfaceError;
106
107    fn try_from(matcher: fnet_matchers::BoundInterface) -> Result<Self, Self::Error> {
108        match matcher {
109            fnet_matchers::BoundInterface::Unbound(fnet_matchers::Empty) => {
110                Ok(BoundInterface::Unbound)
111            }
112            fnet_matchers::BoundInterface::Bound(interface) => Ok(BoundInterface::Bound(
113                interface.try_into().map_err(|e| BoundInterfaceError::Interface(e))?,
114            )),
115            fnet_matchers::BoundInterface::__SourceBreaking { unknown_ordinal } => {
116                Err(BoundInterfaceError::UnknownUnionVariant(unknown_ordinal))
117            }
118        }
119    }
120}
121
122/// Extension type for the `Subnet` variant of [`fnet_matchers::Address`].
123///
124/// This type witnesses to the invariant that the prefix length of the subnet is
125/// no greater than the number of bits in the IP address, and that no host bits
126/// in the network address are set.
127#[derive(Clone, Copy, Eq, Hash, PartialEq)]
128pub struct Subnet(net_types::ip::SubnetEither);
129
130/// Errors when creating a [`Subnet`].
131#[derive(Debug, Error, PartialEq)]
132pub enum SubnetError {
133    #[error("prefix length of subnet is longer than number of bits in IP address")]
134    PrefixTooLong,
135    #[error("host bits are set in subnet network")]
136    HostBitsSet,
137}
138
139impl From<Subnet> for net_types::ip::SubnetEither {
140    fn from(subnet: Subnet) -> Self {
141        let Subnet(subnet) = subnet;
142        subnet
143    }
144}
145
146impl From<Subnet> for fnet::Subnet {
147    fn from(subnet: Subnet) -> Self {
148        let Subnet(subnet) = subnet;
149        let (addr, prefix_len) = subnet.net_prefix();
150
151        Self { addr: fnet::IpAddress::from_ext(addr), prefix_len }
152    }
153}
154
155impl TryFrom<fnet::Subnet> for Subnet {
156    type Error = SubnetError;
157
158    fn try_from(subnet: fnet::Subnet) -> Result<Self, Self::Error> {
159        let fnet::Subnet { addr, prefix_len } = subnet;
160
161        match net_types::ip::SubnetEither::new(addr.into_ext(), prefix_len) {
162            Ok(inner) => Ok(Self(inner)),
163            Err(err) => Err(match err {
164                net_types::ip::SubnetError::PrefixTooLong => SubnetError::PrefixTooLong,
165                net_types::ip::SubnetError::HostBitsSet => SubnetError::HostBitsSet,
166            }),
167        }
168    }
169}
170
171impl Debug for Subnet {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        let Self(subnet) = self;
174        match subnet {
175            net_types::ip::SubnetEither::V4(subnet) => subnet.fmt(f),
176            net_types::ip::SubnetEither::V6(subnet) => subnet.fmt(f),
177        }
178    }
179}
180
181/// Extension type for [`fnet_matchers::AddressRange`].
182///
183/// This type witnesses that `start <= end`. (Comparisons are performed on the
184/// numerical big-endian representation of the IP address.)
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum AddressRange {
187    V4(RangeInclusive<fnet::Ipv4Address>),
188    V6(RangeInclusive<fnet::Ipv6Address>),
189}
190
191/// Errors when creating an [`AddressRange`].
192#[derive(Debug, Error, PartialEq)]
193pub enum AddressRangeError {
194    #[error("invalid address range (start must be <= end)")]
195    Invalid,
196    #[error("address range start and end addresses are not the same IP family")]
197    FamilyMismatch,
198}
199
200impl From<AddressRange> for fnet_matchers::AddressRange {
201    fn from(range: AddressRange) -> Self {
202        let (start, end) = match range {
203            AddressRange::V4(range) => ((*range.start()).into_ext(), (*range.end()).into_ext()),
204            AddressRange::V6(range) => ((*range.start()).into_ext(), (*range.end()).into_ext()),
205        };
206
207        Self { start, end }
208    }
209}
210
211impl TryFrom<fnet_matchers::AddressRange> for AddressRange {
212    type Error = AddressRangeError;
213
214    fn try_from(range: fnet_matchers::AddressRange) -> Result<Self, Self::Error> {
215        let fnet_matchers::AddressRange { start, end } = range;
216        match (start, end) {
217            (fnet::IpAddress::Ipv4(start), fnet::IpAddress::Ipv4(end)) => {
218                if u32::from_be_bytes(start.addr) > u32::from_be_bytes(end.addr) {
219                    Err(AddressRangeError::Invalid)
220                } else {
221                    Ok(Self::V4(start..=end))
222                }
223            }
224            (fnet::IpAddress::Ipv6(start), fnet::IpAddress::Ipv6(end)) => {
225                if u128::from_be_bytes(start.addr) > u128::from_be_bytes(end.addr) {
226                    Err(AddressRangeError::Invalid)
227                } else {
228                    Ok(Self::V6(start..=end))
229                }
230            }
231            _ => Err(AddressRangeError::FamilyMismatch),
232        }
233    }
234}
235
236impl AddressRange {
237    /// Create a new [`AddressRange`] for a single address.
238    pub fn new_single<I: Ip>(ip: I::Addr) -> Self {
239        I::map_ip_in(
240            ip,
241            |ip| {
242                let ip = ip.into_ext();
243                Self::V4(ip..=ip)
244            },
245            |ip| {
246                let ip = ip.into_ext();
247                Self::V6(ip..=ip)
248            },
249        )
250    }
251}
252
253/// Extension type for [`fnet_matchers::Address`].
254#[derive(Clone, PartialEq, Eq)]
255pub enum AddressMatcherType {
256    Subnet(Subnet),
257    Range(AddressRange),
258}
259
260/// Errors when creating an [`AddressMatcherType`].
261#[derive(Debug, Error, PartialEq)]
262pub enum AddressMatcherTypeError {
263    #[error("AddressMatcher is of an unknown variant")]
264    UnknownUnionVariant,
265    #[error("subnet conversion error: {0}")]
266    Subnet(SubnetError),
267    #[error("address range conversion error: {0}")]
268    AddressRange(AddressRangeError),
269}
270
271impl From<SubnetError> for AddressMatcherTypeError {
272    fn from(value: SubnetError) -> Self {
273        AddressMatcherTypeError::Subnet(value)
274    }
275}
276impl From<AddressRangeError> for AddressMatcherTypeError {
277    fn from(value: AddressRangeError) -> Self {
278        AddressMatcherTypeError::AddressRange(value)
279    }
280}
281
282impl From<AddressMatcherType> for fnet_matchers::AddressMatcherType {
283    fn from(matcher: AddressMatcherType) -> Self {
284        match matcher {
285            AddressMatcherType::Subnet(subnet) => Self::Subnet(subnet.into()),
286            AddressMatcherType::Range(range) => Self::Range(range.into()),
287        }
288    }
289}
290
291impl TryFrom<fnet_matchers::AddressMatcherType> for AddressMatcherType {
292    type Error = AddressMatcherTypeError;
293
294    fn try_from(matcher: fnet_matchers::AddressMatcherType) -> Result<Self, Self::Error> {
295        match matcher {
296            fnet_matchers::AddressMatcherType::Subnet(subnet) => {
297                Ok(Self::Subnet(subnet.try_into()?))
298            }
299            fnet_matchers::AddressMatcherType::Range(range) => Ok(Self::Range(range.try_into()?)),
300            fnet_matchers::AddressMatcherType::__SourceBreaking { .. } => {
301                Err(AddressMatcherTypeError::UnknownUnionVariant)
302            }
303        }
304    }
305}
306
307impl Debug for AddressMatcherType {
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        match self {
310            AddressMatcherType::Subnet(subnet) => subnet.fmt(f),
311            AddressMatcherType::Range(address_range) => address_range.fmt(f),
312        }
313    }
314}
315
316/// Extension type for [`fnet_matchers::Address`].
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct Address {
319    pub matcher: AddressMatcherType,
320    pub invert: bool,
321}
322
323/// Errors when creating an [`Address`].
324#[derive(Debug, Error, PartialEq)]
325pub enum AddressError {
326    #[error("address matcher conversion failure: {0}")]
327    AddressMatcherType(AddressMatcherTypeError),
328}
329
330impl From<AddressMatcherTypeError> for AddressError {
331    fn from(value: AddressMatcherTypeError) -> Self {
332        Self::AddressMatcherType(value)
333    }
334}
335
336impl From<Address> for fnet_matchers::Address {
337    fn from(matcher: Address) -> Self {
338        let Address { matcher, invert } = matcher;
339        Self { matcher: matcher.into(), invert }
340    }
341}
342
343impl TryFrom<fnet_matchers::Address> for Address {
344    type Error = AddressError;
345
346    fn try_from(matcher: fnet_matchers::Address) -> Result<Self, Self::Error> {
347        let fnet_matchers::Address { matcher, invert } = matcher;
348        Ok(Self { matcher: matcher.try_into()?, invert })
349    }
350}
351
352/// Extension type for [`fnet_matchers::BoundAddress`].
353#[derive(Debug, Clone, PartialEq, Eq)]
354pub enum BoundAddress {
355    Unbound,
356    Bound(Address),
357}
358
359/// Errors when creating an [`BoundAddress`].
360#[derive(Debug, Error, PartialEq)]
361pub enum BoundAddressError {
362    #[error(transparent)]
363    Address(AddressError),
364    #[error("bound address union is of an unknown variant")]
365    UnknownUnionVariant(u64),
366}
367
368impl From<BoundAddress> for fnet_matchers::BoundAddress {
369    fn from(matcher: BoundAddress) -> Self {
370        match matcher {
371            BoundAddress::Unbound => fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty),
372            BoundAddress::Bound(address) => fnet_matchers::BoundAddress::Bound(address.into()),
373        }
374    }
375}
376
377impl TryFrom<fnet_matchers::BoundAddress> for BoundAddress {
378    type Error = BoundAddressError;
379
380    fn try_from(matcher: fnet_matchers::BoundAddress) -> Result<Self, Self::Error> {
381        match matcher {
382            fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty) => Ok(BoundAddress::Unbound),
383            fnet_matchers::BoundAddress::Bound(address) => {
384                Ok(BoundAddress::Bound(address.try_into().map_err(BoundAddressError::Address)?))
385            }
386            fnet_matchers::BoundAddress::__SourceBreaking { unknown_ordinal } => {
387                Err(BoundAddressError::UnknownUnionVariant(unknown_ordinal))
388            }
389        }
390    }
391}
392
393/// Extension type for [`fnet_matchers::Port`].
394///
395/// This type witnesses to the invariant that `start <= end`.
396#[derive(Debug, Clone, PartialEq, Eq, Hash)]
397pub struct Port {
398    range: RangeInclusive<u16>,
399    invert: bool,
400}
401
402/// Extension type for [`fnet_matchers::BoundPort`].
403#[derive(Debug, Clone, PartialEq, Eq, Hash)]
404pub enum BoundPort {
405    Unbound,
406    Bound(Port),
407}
408
409/// Errors when creating an [`BoundPort`].
410#[derive(Debug, Error, PartialEq, Eq)]
411pub enum BoundPortError {
412    #[error(transparent)]
413    Port(PortError),
414    #[error("bound port union is of an unknown variant")]
415    UnknownUnionVariant(u64),
416}
417
418impl From<BoundPort> for fnet_matchers::BoundPort {
419    fn from(matcher: BoundPort) -> Self {
420        match matcher {
421            BoundPort::Unbound => fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty),
422            BoundPort::Bound(port) => fnet_matchers::BoundPort::Bound(port.into()),
423        }
424    }
425}
426
427impl TryFrom<fnet_matchers::BoundPort> for BoundPort {
428    type Error = BoundPortError;
429
430    fn try_from(matcher: fnet_matchers::BoundPort) -> Result<Self, Self::Error> {
431        match matcher {
432            fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty) => Ok(BoundPort::Unbound),
433            fnet_matchers::BoundPort::Bound(port) => {
434                Ok(BoundPort::Bound(port.try_into().map_err(BoundPortError::Port)?))
435            }
436            fnet_matchers::BoundPort::__SourceBreaking { unknown_ordinal } => {
437                Err(BoundPortError::UnknownUnionVariant(unknown_ordinal))
438            }
439        }
440    }
441}
442
443/// Errors when creating a `Port`.
444#[derive(Debug, Error, PartialEq, Eq)]
445pub enum PortError {
446    #[error("invalid port range (start must be <= end)")]
447    InvalidPortRange,
448}
449
450impl Port {
451    pub fn new(start: u16, end: u16, invert: bool) -> Result<Self, PortError> {
452        if start > end {
453            return Err(PortError::InvalidPortRange);
454        }
455        Ok(Self { range: start..=end, invert })
456    }
457
458    /// Create a new [`Port`] for a single port.
459    pub fn new_single(port: u16, invert: bool) -> Self {
460        Self::new(port, port, invert).unwrap()
461    }
462
463    pub fn range(&self) -> &RangeInclusive<u16> {
464        &self.range
465    }
466
467    pub fn start(&self) -> u16 {
468        *self.range.start()
469    }
470
471    pub fn end(&self) -> u16 {
472        *self.range.end()
473    }
474
475    pub fn invert(&self) -> bool {
476        self.invert
477    }
478}
479
480impl From<Port> for fnet_matchers::Port {
481    fn from(matcher: Port) -> Self {
482        let Port { range, invert } = matcher;
483        Self { start: *range.start(), end: *range.end(), invert }
484    }
485}
486
487impl TryFrom<fnet_matchers::Port> for Port {
488    type Error = PortError;
489
490    fn try_from(matcher: fnet_matchers::Port) -> Result<Self, Self::Error> {
491        let fnet_matchers::Port { start, end, invert } = matcher;
492        if start > end {
493            return Err(PortError::InvalidPortRange);
494        }
495        Ok(Self { range: start..=end, invert })
496    }
497}
498
499/// Extension type for [`fnet_matchers::PacketTransportProtocol`].
500#[derive(Clone, PartialEq)]
501pub enum TransportProtocol {
502    Tcp { src_port: Option<Port>, dst_port: Option<Port> },
503    Udp { src_port: Option<Port>, dst_port: Option<Port> },
504    Icmp,
505    Icmpv6,
506}
507
508/// Errors when creating a [`TransportProtocol`].
509#[derive(Debug, Error, PartialEq)]
510pub enum TransportProtocolError {
511    #[error("invalid port: {0}")]
512    Port(PortError),
513    #[error("TransportProtocol is of an unknown variant")]
514    UnknownUnionVariant,
515}
516
517impl From<PortError> for TransportProtocolError {
518    fn from(value: PortError) -> Self {
519        TransportProtocolError::Port(value)
520    }
521}
522
523impl From<TransportProtocol> for fnet_matchers::PacketTransportProtocol {
524    fn from(matcher: TransportProtocol) -> Self {
525        match matcher {
526            TransportProtocol::Tcp { src_port, dst_port } => Self::Tcp(fnet_matchers::TcpPacket {
527                src_port: src_port.map(Into::into),
528                dst_port: dst_port.map(Into::into),
529                __source_breaking: SourceBreaking,
530            }),
531            TransportProtocol::Udp { src_port, dst_port } => Self::Udp(fnet_matchers::UdpPacket {
532                src_port: src_port.map(Into::into),
533                dst_port: dst_port.map(Into::into),
534                __source_breaking: SourceBreaking,
535            }),
536            TransportProtocol::Icmp => Self::Icmp(fnet_matchers::IcmpPacket::default()),
537            TransportProtocol::Icmpv6 => Self::Icmpv6(fnet_matchers::Icmpv6Packet::default()),
538        }
539    }
540}
541
542impl TryFrom<fnet_matchers::PacketTransportProtocol> for TransportProtocol {
543    type Error = TransportProtocolError;
544
545    fn try_from(matcher: fnet_matchers::PacketTransportProtocol) -> Result<Self, Self::Error> {
546        match matcher {
547            fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
548                src_port,
549                dst_port,
550                __source_breaking,
551            }) => Ok(Self::Tcp {
552                src_port: src_port.map(TryInto::try_into).transpose()?,
553                dst_port: dst_port.map(TryInto::try_into).transpose()?,
554            }),
555            fnet_matchers::PacketTransportProtocol::Udp(fnet_matchers::UdpPacket {
556                src_port,
557                dst_port,
558                __source_breaking,
559            }) => Ok(Self::Udp {
560                src_port: src_port.map(TryInto::try_into).transpose()?,
561                dst_port: dst_port.map(TryInto::try_into).transpose()?,
562            }),
563            fnet_matchers::PacketTransportProtocol::Icmp(fnet_matchers::IcmpPacket {
564                __source_breaking,
565            }) => Ok(Self::Icmp),
566            fnet_matchers::PacketTransportProtocol::Icmpv6(fnet_matchers::Icmpv6Packet {
567                __source_breaking,
568            }) => Ok(Self::Icmpv6),
569            fnet_matchers::PacketTransportProtocol::__SourceBreaking { .. } => {
570                Err(TransportProtocolError::UnknownUnionVariant)
571            }
572        }
573    }
574}
575
576impl Debug for TransportProtocol {
577    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
578        // Omit empty fields.
579        match self {
580            TransportProtocol::Tcp { src_port, dst_port } => {
581                let mut debug_struct = f.debug_struct("Tcp");
582
583                // Omit empty fields.
584                if let Some(port) = &src_port {
585                    let _ = debug_struct.field("src_port", port);
586                }
587
588                if let Some(port) = &dst_port {
589                    let _ = debug_struct.field("dst_port", port);
590                }
591
592                debug_struct.finish()
593            }
594            TransportProtocol::Udp { src_port, dst_port } => {
595                let mut debug_struct = f.debug_struct("Udp");
596
597                // Omit empty fields.
598                if let Some(port) = &src_port {
599                    let _ = debug_struct.field("src_port", port);
600                }
601
602                if let Some(port) = &dst_port {
603                    let _ = debug_struct.field("dst_port", port);
604                }
605
606                debug_struct.finish()
607            }
608            TransportProtocol::Icmp => f.write_str("Icmp"),
609            TransportProtocol::Icmpv6 => f.write_str("Icmpv6"),
610        }
611    }
612}
613
614/// An extension type for [`fnet_matchers::Mark`]
615#[derive(Debug, Clone, PartialEq, Eq, Hash)]
616pub enum Mark {
617    Unmarked,
618    Marked { mask: u32, between: RangeInclusive<u32>, invert: bool },
619}
620
621#[derive(Debug, Clone, PartialEq, Error)]
622pub enum MarkError {
623    #[error("mark union is of an unknown variant")]
624    UnknownUnionVariant(u64),
625}
626
627impl TryFrom<fnet_matchers::Mark> for Mark {
628    type Error = MarkError;
629
630    fn try_from(matcher: fnet_matchers::Mark) -> Result<Self, Self::Error> {
631        match matcher {
632            fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked) => Ok(Mark::Unmarked),
633            fnet_matchers::Mark::Marked(fnet_matchers::Marked {
634                mask,
635                between: fnet_matchers::Between { start, end },
636                invert,
637            }) => Ok(Mark::Marked { mask, between: RangeInclusive::new(start, end), invert }),
638            fnet_matchers::Mark::__SourceBreaking { unknown_ordinal } => {
639                Err(MarkError::UnknownUnionVariant(unknown_ordinal))
640            }
641        }
642    }
643}
644
645impl From<Mark> for fnet_matchers::Mark {
646    fn from(matcher: Mark) -> Self {
647        match matcher {
648            Mark::Unmarked => fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked),
649            Mark::Marked { mask, between, invert } => {
650                let (start, end) = between.into_inner();
651                fnet_matchers::Mark::Marked(fnet_matchers::Marked {
652                    mask,
653                    between: fnet_matchers::Between { start, end },
654                    invert,
655                })
656            }
657        }
658    }
659}
660
661/// An extension type for [`fnet_matchers::MarkInDomain`].
662#[derive(Debug, Clone, PartialEq, Eq, Hash)]
663pub struct MarkInDomain {
664    pub domain: fnet::MarkDomain,
665    pub mark: Mark,
666}
667
668#[derive(Debug, Clone, PartialEq, Error)]
669pub enum MarkInDomainError {
670    #[error("mark conversion failed: {0}")]
671    Mark(MarkError),
672}
673
674impl TryFrom<fnet_matchers::MarkInDomain> for MarkInDomain {
675    type Error = MarkInDomainError;
676
677    fn try_from(matcher: fnet_matchers::MarkInDomain) -> Result<Self, Self::Error> {
678        let fnet_matchers::MarkInDomain { domain, mark } = matcher;
679        Ok(Self { domain, mark: mark.try_into().map_err(MarkInDomainError::Mark)? })
680    }
681}
682
683impl From<MarkInDomain> for fnet_matchers::MarkInDomain {
684    fn from(matcher: MarkInDomain) -> Self {
685        let MarkInDomain { domain, mark } = matcher;
686        Self { domain, mark: mark.into() }
687    }
688}
689
690/// An extension type for [`fnet_matchers::TcpSocket`]
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum TcpSocket {
693    Empty,
694    SrcPort(BoundPort),
695    DstPort(BoundPort),
696    States(fnet_matchers::TcpState),
697}
698
699#[derive(Debug, PartialEq, Eq, Error)]
700pub enum TcpSocketError {
701    #[error("bound port matcher conversion failed: {0}")]
702    BoundPort(BoundPortError),
703    #[error("tcp union is of an unknown variant")]
704    UnknownUnionVariant(u64),
705}
706
707impl TryFrom<fnet_matchers::TcpSocket> for TcpSocket {
708    type Error = TcpSocketError;
709
710    fn try_from(matcher: fnet_matchers::TcpSocket) -> Result<Self, Self::Error> {
711        match matcher {
712            fnet_matchers::TcpSocket::Empty(fnet_matchers::Empty) => Ok(Self::Empty),
713            fnet_matchers::TcpSocket::SrcPort(port) => {
714                Ok(Self::SrcPort(port.try_into().map_err(|e| TcpSocketError::BoundPort(e))?))
715            }
716            fnet_matchers::TcpSocket::DstPort(port) => {
717                Ok(Self::DstPort(port.try_into().map_err(|e| TcpSocketError::BoundPort(e))?))
718            }
719            fnet_matchers::TcpSocket::States(states) => Ok(Self::States(states)),
720            fnet_matchers::TcpSocket::__SourceBreaking { unknown_ordinal } => {
721                Err(TcpSocketError::UnknownUnionVariant(unknown_ordinal))
722            }
723        }
724    }
725}
726
727impl From<TcpSocket> for fnet_matchers::TcpSocket {
728    fn from(matcher: TcpSocket) -> Self {
729        match matcher {
730            TcpSocket::Empty => Self::Empty(fnet_matchers::Empty),
731            TcpSocket::SrcPort(port) => Self::SrcPort(port.into()),
732            TcpSocket::DstPort(port) => Self::DstPort(port.into()),
733            TcpSocket::States(states) => Self::States(states),
734        }
735    }
736}
737
738/// An extension type for [`fnet_matchers::UdpSocket`]
739#[derive(Debug, Clone, PartialEq, Eq)]
740pub enum UdpSocket {
741    Empty,
742    SrcPort(BoundPort),
743    DstPort(BoundPort),
744    States(fnet_matchers::UdpState),
745}
746
747#[derive(Debug, PartialEq, Eq, Error)]
748pub enum UdpSocketError {
749    #[error("bound port matcher conversion failed: {0}")]
750    BoundPort(BoundPortError),
751    #[error("udp union is of an unknown variant")]
752    UnknownUnionVariant(u64),
753}
754
755impl TryFrom<fnet_matchers::UdpSocket> for UdpSocket {
756    type Error = UdpSocketError;
757
758    fn try_from(matcher: fnet_matchers::UdpSocket) -> Result<Self, Self::Error> {
759        match matcher {
760            fnet_matchers::UdpSocket::Empty(fnet_matchers::Empty) => Ok(Self::Empty),
761            fnet_matchers::UdpSocket::SrcPort(port) => {
762                Ok(Self::SrcPort(port.try_into().map_err(|e| UdpSocketError::BoundPort(e))?))
763            }
764            fnet_matchers::UdpSocket::DstPort(port) => {
765                Ok(Self::DstPort(port.try_into().map_err(|e| UdpSocketError::BoundPort(e))?))
766            }
767            fnet_matchers::UdpSocket::States(states) => Ok(Self::States(states)),
768            fnet_matchers::UdpSocket::__SourceBreaking { unknown_ordinal } => {
769                Err(UdpSocketError::UnknownUnionVariant(unknown_ordinal))
770            }
771        }
772    }
773}
774
775impl From<UdpSocket> for fnet_matchers::UdpSocket {
776    fn from(matcher: UdpSocket) -> Self {
777        match matcher {
778            UdpSocket::Empty => Self::Empty(fnet_matchers::Empty),
779            UdpSocket::SrcPort(port) => Self::SrcPort(port.into()),
780            UdpSocket::DstPort(port) => Self::DstPort(port.into()),
781            UdpSocket::States(states) => Self::States(states),
782        }
783    }
784}
785
786/// An extension type for [`fnet_matchers::SocketTransportProtocol`].
787#[derive(Debug, Clone, PartialEq, Eq)]
788pub enum SocketTransportProtocol {
789    Tcp(TcpSocket),
790    Udp(UdpSocket),
791}
792
793#[derive(Debug, PartialEq, Eq, Error)]
794pub enum SocketTransportProtocolError {
795    #[error("invalid tcp matcher: {0}")]
796    Tcp(TcpSocketError),
797    #[error("invalid udp matcher: {0}")]
798    Udp(UdpSocketError),
799    #[error("socket transport protocol union is of an unknown variant")]
800    UnknownUnionVariant(u64),
801}
802
803impl TryFrom<fnet_matchers::SocketTransportProtocol> for SocketTransportProtocol {
804    type Error = SocketTransportProtocolError;
805
806    fn try_from(matcher: fnet_matchers::SocketTransportProtocol) -> Result<Self, Self::Error> {
807        match matcher {
808            fnet_matchers::SocketTransportProtocol::Tcp(tcp) => {
809                Ok(Self::Tcp(tcp.try_into().map_err(|e| SocketTransportProtocolError::Tcp(e))?))
810            }
811            fnet_matchers::SocketTransportProtocol::Udp(udp) => {
812                Ok(Self::Udp(udp.try_into().map_err(|e| SocketTransportProtocolError::Udp(e))?))
813            }
814            fnet_matchers::SocketTransportProtocol::__SourceBreaking { unknown_ordinal } => {
815                Err(SocketTransportProtocolError::UnknownUnionVariant(unknown_ordinal))
816            }
817        }
818    }
819}
820
821impl From<SocketTransportProtocol> for fnet_matchers::SocketTransportProtocol {
822    fn from(matcher: SocketTransportProtocol) -> Self {
823        match matcher {
824            SocketTransportProtocol::Tcp(tcp) => Self::Tcp(tcp.into()),
825            SocketTransportProtocol::Udp(udp) => Self::Udp(udp.into()),
826        }
827    }
828}
829
830#[cfg(test)]
831mod tests {
832    use net_declare::{fidl_ip, fidl_ip_v4, fidl_ip_v6, fidl_subnet, net_subnet_v4, net_subnet_v6};
833    use test_case::test_case;
834
835    use super::*;
836
837    #[test_case(
838        fnet_matchers::Interface::Id(1),
839        Interface::Id(NonZeroU64::new(1).unwrap());
840        "Interface"
841    )]
842    #[test_case(
843        fnet_matchers::BoundInterface::Unbound(fnet_matchers::Empty),
844        BoundInterface::Unbound;
845        "BoundInterface Unbound"
846    )]
847    #[test_case(
848        fnet_matchers::BoundInterface::Bound(fnet_matchers::Interface::Id(1)),
849        BoundInterface::Bound(Interface::Id(NonZeroU64::new(1).unwrap()));
850        "BoundInterface Bound"
851    )]
852    #[test_case(
853        fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked),
854        Mark::Unmarked;
855        "Unmarked"
856    )]
857    #[test_case(
858        fnet_matchers::Mark::Marked(fnet_matchers::Marked {
859            mask: 0xFF,
860            between: fnet_matchers::Between { start: 10, end: 20 },
861            invert: true,
862        }),
863        Mark::Marked { mask: 0xFF, between: 10..=20, invert: true };
864        "Marked"
865    )]
866    #[test_case(
867        fnet_matchers::MarkInDomain {
868            domain: fnet::MarkDomain::Mark1,
869            mark: fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked),
870        },
871        MarkInDomain {
872            domain: fnet::MarkDomain::Mark1,
873            mark: Mark::Unmarked,
874        };
875        "MarkInDomain"
876    )]
877    #[test_case(
878        fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("192.0.2.0/24")),
879        AddressMatcherType::Subnet(Subnet(net_subnet_v4!("192.0.2.0/24").into()));
880        "AddressMatcherTypeV4"
881    )]
882    #[test_case(
883        fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("2001:db8::/64")),
884        AddressMatcherType::Subnet(Subnet(net_subnet_v6!("2001:db8::/64").into()));
885        "AddressMatcherTypeV6"
886    )]
887    #[test_case(
888        fnet_matchers::Address {
889            matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("2001:db8::/64")),
890            invert: true,
891        },
892        Address {
893            matcher: AddressMatcherType::Subnet(Subnet(net_subnet_v6!("2001:db8::/64").into())),
894            invert: true,
895        };
896        "AddressV6"
897    )]
898    #[test_case(
899        fnet_matchers::AddressRange {
900            start: fidl_ip!("192.0.2.0"),
901            end: fidl_ip!("192.0.2.1"),
902        },
903        AddressRange::V4(
904            fidl_ip_v4!("192.0.2.0")..=fidl_ip_v4!("192.0.2.1"),
905        );
906        "AddressRangeV4"
907    )]
908    #[test_case(
909        fnet_matchers::AddressRange {
910            start: fidl_ip!("2001:db8::0"),
911            end: fidl_ip!("2001:db8::8"),
912        },
913        AddressRange::V6(
914            fidl_ip_v6!("2001:db8::0")..=fidl_ip_v6!("2001:db8::8"),
915        );
916        "AddressRangeV6"
917    )]
918    #[test_case(
919        fnet_matchers::PacketTransportProtocol::Udp(fnet_matchers::UdpPacket {
920            src_port: Some(fnet_matchers::Port { start: 1024, end: u16::MAX, invert: false }),
921            dst_port: None,
922            ..Default::default()
923        }),
924        TransportProtocol::Udp {
925            src_port: Some(Port { range: 1024..=u16::MAX, invert: false }),
926            dst_port: None,
927        };
928        "TransportProtocol"
929    )]
930    #[test_case(
931        fnet_matchers::TcpSocket::Empty(fnet_matchers::Empty),
932        TcpSocket::Empty;
933        "TcpSocketEmpty"
934    )]
935    #[test_case(
936        fnet_matchers::TcpSocket::SrcPort(
937            fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 1024, end: u16::MAX, invert: false })
938        ),
939        TcpSocket::SrcPort(BoundPort::Bound(Port { range: 1024..=u16::MAX, invert: false }));
940        "TcpSocketSrcPort"
941    )]
942    #[test_case(
943        fnet_matchers::TcpSocket::DstPort(
944            fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 80, end: 80, invert: true })
945        ),
946        TcpSocket::DstPort(BoundPort::Bound(Port { range: 80..=80, invert: true }));
947        "TcpSocketDstPort"
948    )]
949    #[test_case(
950        fnet_matchers::TcpSocket::States(fnet_matchers::TcpState::ESTABLISHED),
951        TcpSocket::States(fnet_matchers::TcpState::ESTABLISHED);
952        "TcpSocketStates"
953    )]
954    #[test_case(
955        fnet_matchers::UdpSocket::Empty(fnet_matchers::Empty),
956        UdpSocket::Empty;
957        "UdpSocketEmpty"
958    )]
959    #[test_case(
960        fnet_matchers::UdpSocket::SrcPort(
961            fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 1024, end: u16::MAX, invert: false })
962        ),
963        UdpSocket::SrcPort(BoundPort::Bound(Port { range: 1024..=u16::MAX, invert: false }));
964        "UdpSocketSrcPort"
965    )]
966    #[test_case(
967        fnet_matchers::UdpSocket::DstPort(
968            fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 53, end: 53, invert: true })
969        ),
970        UdpSocket::DstPort(BoundPort::Bound(Port { range: 53..=53, invert: true }));
971        "UdpSocketDstPort"
972    )]
973    #[test_case(
974        fnet_matchers::UdpSocket::States(fnet_matchers::UdpState::BOUND),
975        UdpSocket::States(fnet_matchers::UdpState::BOUND);
976        "UdpSocketStates"
977    )]
978    #[test_case(
979        fnet_matchers::SocketTransportProtocol::Tcp(
980            fnet_matchers::TcpSocket::SrcPort(
981                fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 123, end: 123, invert: false })
982            )
983        ),
984        SocketTransportProtocol::Tcp(TcpSocket::SrcPort(BoundPort::Bound(Port { range: 123..=123, invert: false })));
985        "SocketTransportProtocolTcp"
986    )]
987    #[test_case(
988        fnet_matchers::SocketTransportProtocol::Udp(
989            fnet_matchers::UdpSocket::SrcPort(
990                fnet_matchers::BoundPort::Bound(fnet_matchers::Port { start: 123, end: 123, invert: false })
991            )
992        ),
993        SocketTransportProtocol::Udp(UdpSocket::SrcPort(BoundPort::Bound(Port { range: 123..=123, invert: false })));
994        "SocketTransportProtocolUdp"
995    )]
996    #[test_case(
997        fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty),
998        BoundPort::Unbound;
999        "BoundPort Unbound"
1000    )]
1001    #[test_case(
1002        fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty),
1003        BoundAddress::Unbound;
1004        "BoundAddress Unbound"
1005    )]
1006    #[test_case(
1007        fnet_matchers::TcpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty)),
1008        TcpSocket::SrcPort(BoundPort::Unbound);
1009        "TcpSocket SrcPort Unbound"
1010    )]
1011    #[test_case(
1012        fnet_matchers::TcpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty)),
1013        TcpSocket::DstPort(BoundPort::Unbound);
1014        "TcpSocket DstPort Unbound"
1015    )]
1016    #[test_case(
1017        fnet_matchers::UdpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty)),
1018        UdpSocket::SrcPort(BoundPort::Unbound);
1019        "UdpSocket SrcPort Unbound"
1020    )]
1021    #[test_case(
1022        fnet_matchers::UdpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty)),
1023        UdpSocket::DstPort(BoundPort::Unbound);
1024        "UdpSocket DstPort Unbound"
1025    )]
1026    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1027    where
1028        E: TryFrom<F> + Clone + Debug + PartialEq,
1029        <E as TryFrom<F>>::Error: Debug + PartialEq,
1030        F: From<E> + Clone + Debug + PartialEq,
1031    {
1032        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1033        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1034    }
1035
1036    #[test_case(
1037        fnet_matchers::BoundInterface::__SourceBreaking { unknown_ordinal: 0 } =>
1038            Err(BoundInterfaceError::UnknownUnionVariant(0));
1039        "UnknownUnionVariant"
1040    )]
1041    #[test_case(
1042        fnet_matchers::BoundInterface::Bound(fnet_matchers::Interface::Id(0)) =>
1043            Err(BoundInterfaceError::Interface(InterfaceError::ZeroId));
1044        "InterfaceError"
1045    )]
1046    fn bound_interface_try_from_error(
1047        fidl: fnet_matchers::BoundInterface,
1048    ) -> Result<BoundInterface, BoundInterfaceError> {
1049        BoundInterface::try_from(fidl)
1050    }
1051
1052    #[test_case(
1053        fnet_matchers::Mark::__SourceBreaking { unknown_ordinal: 0 } =>
1054            Err(MarkError::UnknownUnionVariant(0));
1055        "UnknownUnionVariant"
1056    )]
1057    fn mark_try_from_error(fidl: fnet_matchers::Mark) -> Result<Mark, MarkError> {
1058        Mark::try_from(fidl)
1059    }
1060
1061    #[test_case(
1062        fnet_matchers::MarkInDomain {
1063            domain: fnet::MarkDomain::Mark1,
1064            mark: fnet_matchers::Mark::__SourceBreaking { unknown_ordinal: 0 },
1065        } => Err(MarkInDomainError::Mark(MarkError::UnknownUnionVariant(0)));
1066        "MarkInDomain Mark Error"
1067    )]
1068    fn mark_and_domain_try_from_error(
1069        fidl: fnet_matchers::MarkInDomain,
1070    ) -> Result<MarkInDomain, MarkInDomainError> {
1071        MarkInDomain::try_from(fidl)
1072    }
1073
1074    #[test]
1075    fn address_matcher_type_try_from_unknown_variant() {
1076        assert_eq!(
1077            AddressMatcherType::try_from(fnet_matchers::AddressMatcherType::__SourceBreaking {
1078                unknown_ordinal: 0
1079            }),
1080            Err(AddressMatcherTypeError::UnknownUnionVariant)
1081        );
1082    }
1083
1084    #[test]
1085    fn subnet_try_from_invalid() {
1086        assert_eq!(
1087            Subnet::try_from(fnet::Subnet { addr: fidl_ip!("192.0.2.1"), prefix_len: 33 }),
1088            Err(SubnetError::PrefixTooLong)
1089        );
1090        assert_eq!(Subnet::try_from(fidl_subnet!("192.0.2.1/24")), Err(SubnetError::HostBitsSet));
1091    }
1092
1093    #[test]
1094    fn address_range_try_from_invalid() {
1095        assert_eq!(
1096            AddressRange::try_from(fnet_matchers::AddressRange {
1097                start: fidl_ip!("192.0.2.1"),
1098                end: fidl_ip!("192.0.2.0"),
1099            }),
1100            Err(AddressRangeError::Invalid)
1101        );
1102        assert_eq!(
1103            AddressRange::try_from(fnet_matchers::AddressRange {
1104                start: fidl_ip!("2001:db8::1"),
1105                end: fidl_ip!("2001:db8::"),
1106            }),
1107            Err(AddressRangeError::Invalid)
1108        );
1109    }
1110
1111    #[test]
1112    fn address_range_try_from_family_mismatch() {
1113        assert_eq!(
1114            AddressRange::try_from(fnet_matchers::AddressRange {
1115                start: fidl_ip!("192.0.2.0"),
1116                end: fidl_ip!("2001:db8::"),
1117            }),
1118            Err(AddressRangeError::FamilyMismatch)
1119        );
1120    }
1121
1122    #[test]
1123    fn port_matcher_try_from_invalid() {
1124        assert_eq!(
1125            Port::try_from(fnet_matchers::Port { start: 1, end: 0, invert: false }),
1126            Err(PortError::InvalidPortRange)
1127        );
1128    }
1129
1130    #[test]
1131    fn transport_protocol_try_from_unknown_variant() {
1132        assert_eq!(
1133            TransportProtocol::try_from(fnet_matchers::PacketTransportProtocol::__SourceBreaking {
1134                unknown_ordinal: 0
1135            }),
1136            Err(TransportProtocolError::UnknownUnionVariant)
1137        );
1138    }
1139
1140    #[test_case(
1141        fnet_matchers::TcpSocket::__SourceBreaking { unknown_ordinal: 100 } =>
1142            Err(TcpSocketError::UnknownUnionVariant(100));
1143        "TcpSocket UnknownUnionVariant"
1144    )]
1145    #[test_case(
1146        fnet_matchers::TcpSocket::SrcPort(fnet_matchers::BoundPort::Bound(fnet_matchers::Port {
1147            start: 1,
1148            end: 0,
1149            invert: false,
1150        })) => Err(TcpSocketError::BoundPort(BoundPortError::Port(PortError::InvalidPortRange)));
1151        "TcpSocket SrcPort Error"
1152    )]
1153    #[test_case(
1154        fnet_matchers::TcpSocket::DstPort(fnet_matchers::BoundPort::Bound(fnet_matchers::Port {
1155            start: 1,
1156            end: 0,
1157            invert: false,
1158        })) => Err(TcpSocketError::BoundPort(BoundPortError::Port(PortError::InvalidPortRange)));
1159        "TcpSocket DstPort Error"
1160    )]
1161    fn tcp_socket_try_from_error(
1162        fidl: fnet_matchers::TcpSocket,
1163    ) -> Result<TcpSocket, TcpSocketError> {
1164        TcpSocket::try_from(fidl)
1165    }
1166
1167    #[test_case(
1168        fnet_matchers::UdpSocket::__SourceBreaking { unknown_ordinal: 100 } =>
1169            Err(UdpSocketError::UnknownUnionVariant(100));
1170        "UdpSocket UnknownUnionVariant"
1171    )]
1172    #[test_case(
1173        fnet_matchers::UdpSocket::SrcPort(fnet_matchers::BoundPort::Bound(fnet_matchers::Port {
1174            start: 1,
1175            end: 0,
1176            invert: false,
1177        })) => Err(UdpSocketError::BoundPort(BoundPortError::Port(PortError::InvalidPortRange)));
1178        "UdpSocket SrcPort Error"
1179    )]
1180    #[test_case(
1181        fnet_matchers::UdpSocket::DstPort(fnet_matchers::BoundPort::Bound(fnet_matchers::Port {
1182            start: 1,
1183            end: 0,
1184            invert: false,
1185        })) => Err(UdpSocketError::BoundPort(BoundPortError::Port(PortError::InvalidPortRange)));
1186        "UdpSocket DstPort Error"
1187    )]
1188    fn udp_socket_try_from_error(
1189        fidl: fnet_matchers::UdpSocket,
1190    ) -> Result<UdpSocket, UdpSocketError> {
1191        UdpSocket::try_from(fidl)
1192    }
1193
1194    #[test_case(
1195        fnet_matchers::SocketTransportProtocol::__SourceBreaking {
1196            unknown_ordinal: 100
1197        } => Err(SocketTransportProtocolError::UnknownUnionVariant(100));
1198        "SocketTransportProtocol UnknownUnionVariant"
1199    )]
1200    #[test_case(
1201        fnet_matchers::SocketTransportProtocol::Tcp(
1202            fnet_matchers::TcpSocket::__SourceBreaking { unknown_ordinal: 100 }
1203        ) => Err(SocketTransportProtocolError::Tcp(TcpSocketError::UnknownUnionVariant(100)));
1204        "SocketTransportProtocol Tcp Error"
1205    )]
1206    #[test_case(
1207        fnet_matchers::SocketTransportProtocol::Udp(
1208            fnet_matchers::UdpSocket::__SourceBreaking { unknown_ordinal: 100 }
1209        ) => Err(SocketTransportProtocolError::Udp(UdpSocketError::UnknownUnionVariant(100)));
1210        "SocketTransportProtocol Udp Error"
1211    )]
1212    fn socket_transport_protocol_try_from_error(
1213        fidl: fnet_matchers::SocketTransportProtocol,
1214    ) -> Result<SocketTransportProtocol, SocketTransportProtocolError> {
1215        SocketTransportProtocol::try_from(fidl)
1216    }
1217}