Skip to main content

fidl_fuchsia_net_sockets_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.sockets FIDL library.
6
7#![warn(
8    missing_docs,
9    unreachable_patterns,
10    clippy::useless_conversion,
11    clippy::redundant_clone,
12    clippy::precedence
13)]
14
15use fidl_fuchsia_net as fnet;
16use fidl_fuchsia_net_ext::{IntoExt, Marks};
17use fidl_fuchsia_net_matchers as fnet_matchers;
18use fidl_fuchsia_net_matchers_ext as fnet_matchers_ext;
19use fidl_fuchsia_net_sockets as fnet_sockets;
20use fidl_fuchsia_net_tcp as fnet_tcp;
21use fidl_fuchsia_net_udp as fnet_udp;
22use futures::{Stream, TryStreamExt as _};
23use net_types::ip::{self, GenericOverIp, Ip, IpInvariant, Ipv4, Ipv6};
24use thiserror::Error;
25
26/// An extension type for [`fnet_sockets::IpSocketMatcher`].
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum IpSocketMatcher {
29    /// Matches against the IP version of the socket.
30    Family(ip::IpVersion),
31    /// Matches against the source address of the socket.
32    SrcAddr(fnet_matchers_ext::BoundAddress),
33    /// Matches against the destination address of the socket.
34    DstAddr(fnet_matchers_ext::BoundAddress),
35    /// Matches against transport protocol fields of the socket.
36    Proto(fnet_matchers_ext::SocketTransportProtocol),
37    /// Matches against the (bound, i.e. SO_BINDTODEVICE) interface of the
38    /// socket.
39    BoundInterface(fnet_matchers_ext::BoundInterface),
40    /// Matches against the cookie of the socket (i.e. SO_COOKIE)
41    Cookie(fnet_matchers::SocketCookie),
42    /// Matches against one mark of the socket.
43    Mark(fnet_matchers_ext::MarkInDomain),
44}
45
46/// Errors returned by the conversion from [`fnet_sockets::IpSocketMatcher`]
47/// to [`IpSocketMatcher`].
48#[derive(Debug, PartialEq, Error)]
49pub enum IpSocketMatcherError {
50    /// A union type was unknown.
51    #[error("got unexpected union variant: {0}")]
52    UnknownUnionVariant(u64),
53    /// An error was encountered when converting one of the address matchers.
54    #[error("address matcher conversion failure: {0}")]
55    Address(fnet_matchers_ext::BoundAddressError),
56    /// An error was encountered when converting the transport protocol
57    /// matcher.
58    #[error("protocol matcher conversion failure: {0}")]
59    TransportProtocol(fnet_matchers_ext::SocketTransportProtocolError),
60    /// An error was encountered while converting the interface matcher.
61    #[error("bound interface matcher conversion failure: {0}")]
62    BoundInterface(fnet_matchers_ext::BoundInterfaceError),
63    /// An error was encountered when converting one of the mark matchers.
64    #[error("mark matcher conversion failure: {0}")]
65    Mark(fnet_matchers_ext::MarkInDomainError),
66}
67
68impl TryFrom<fnet_sockets::IpSocketMatcher> for IpSocketMatcher {
69    type Error = IpSocketMatcherError;
70
71    fn try_from(matcher: fnet_sockets::IpSocketMatcher) -> Result<Self, Self::Error> {
72        match matcher {
73            fnet_sockets::IpSocketMatcher::Family(ip_version) => {
74                Ok(Self::Family(ip_version.into_ext()))
75            }
76            fnet_sockets::IpSocketMatcher::SrcAddr(addr) => {
77                Ok(Self::SrcAddr(addr.try_into().map_err(|e| IpSocketMatcherError::Address(e))?))
78            }
79            fnet_sockets::IpSocketMatcher::DstAddr(addr) => {
80                Ok(Self::DstAddr(addr.try_into().map_err(|e| IpSocketMatcherError::Address(e))?))
81            }
82            fnet_sockets::IpSocketMatcher::Proto(proto) => Ok(Self::Proto(
83                proto.try_into().map_err(|e| IpSocketMatcherError::TransportProtocol(e))?,
84            )),
85            fnet_sockets::IpSocketMatcher::BoundInterface(bound_interface) => {
86                Ok(Self::BoundInterface(
87                    bound_interface
88                        .try_into()
89                        .map_err(|e| IpSocketMatcherError::BoundInterface(e))?,
90                ))
91            }
92            fnet_sockets::IpSocketMatcher::Cookie(cookie) => Ok(Self::Cookie(cookie)),
93            fnet_sockets::IpSocketMatcher::Mark(mark) => {
94                Ok(Self::Mark(mark.try_into().map_err(|e| IpSocketMatcherError::Mark(e))?))
95            }
96            fnet_sockets::IpSocketMatcher::__SourceBreaking { unknown_ordinal } => {
97                Err(IpSocketMatcherError::UnknownUnionVariant(unknown_ordinal))
98            }
99        }
100    }
101}
102
103impl From<IpSocketMatcher> for fnet_sockets::IpSocketMatcher {
104    fn from(value: IpSocketMatcher) -> Self {
105        match value {
106            IpSocketMatcher::Family(ip_version) => {
107                fnet_sockets::IpSocketMatcher::Family(ip_version.into_ext())
108            }
109            IpSocketMatcher::SrcAddr(address) => {
110                fnet_sockets::IpSocketMatcher::SrcAddr(address.into())
111            }
112            IpSocketMatcher::DstAddr(address) => {
113                fnet_sockets::IpSocketMatcher::DstAddr(address.into())
114            }
115            IpSocketMatcher::Proto(socket_transport_protocol) => {
116                fnet_sockets::IpSocketMatcher::Proto(socket_transport_protocol.into())
117            }
118            IpSocketMatcher::BoundInterface(mark) => {
119                fnet_sockets::IpSocketMatcher::BoundInterface(mark.into())
120            }
121            IpSocketMatcher::Cookie(socket_cookie) => {
122                fnet_sockets::IpSocketMatcher::Cookie(socket_cookie)
123            }
124            IpSocketMatcher::Mark(mark) => fnet_sockets::IpSocketMatcher::Mark(mark.into()),
125        }
126    }
127}
128
129/// Extension type for [`fnet_sockets::IpSocketState`].
130#[derive(Debug, PartialEq, Eq, Clone)]
131pub enum IpSocketState {
132    /// IPv4 socket state.
133    V4(IpSocketStateSpecific<Ipv4>),
134    /// IPv6 socket state.
135    V6(IpSocketStateSpecific<Ipv6>),
136}
137
138/// Error type for [`IpSocketState`] conversion.
139#[derive(Debug, Error, PartialEq)]
140pub enum IpSocketStateError {
141    /// Missing a required field.
142    #[error("missing field: {0}")]
143    MissingField(&'static str),
144    /// The socket address version does not match the expected version.
145    #[error("version mismatch")]
146    VersionMismatch,
147    /// The transport state is invalid.
148    #[error("transport state error: {0}")]
149    Transport(IpSocketTransportStateError),
150}
151
152impl TryFrom<fnet_sockets::IpSocketState> for IpSocketState {
153    type Error = IpSocketStateError;
154
155    fn try_from(value: fnet_sockets::IpSocketState) -> Result<Self, Self::Error> {
156        fn convert_address<I: Ip>(addr: fnet::IpAddress) -> Result<I::Addr, IpSocketStateError> {
157            I::map_ip::<_, Option<I::Addr>>(
158                IpInvariant(addr.into_ext()),
159                |IpInvariant(addr)| match addr {
160                    net_types::ip::IpAddr::V4(addr) => Some(addr),
161                    _ => None,
162                },
163                |IpInvariant(addr)| match addr {
164                    net_types::ip::IpAddr::V6(addr) => Some(addr),
165                    _ => None,
166                },
167            )
168            .ok_or(IpSocketStateError::VersionMismatch)
169        }
170
171        fn to_ip_socket_specific<I: Ip>(
172            src_addr: Option<fnet::IpAddress>,
173            dst_addr: Option<fnet::IpAddress>,
174            cookie: u64,
175            marks: fnet::Marks,
176            transport: fnet_sockets::IpSocketTransportState,
177        ) -> Result<IpSocketStateSpecific<I>, IpSocketStateError> {
178            let src_addr: Option<I::Addr> = src_addr.map(convert_address::<I>).transpose()?;
179            let dst_addr: Option<I::Addr> = dst_addr.map(convert_address::<I>).transpose()?;
180
181            Ok(IpSocketStateSpecific {
182                src_addr,
183                dst_addr,
184                cookie,
185                marks: marks.into(),
186                transport: transport.try_into().map_err(IpSocketStateError::Transport)?,
187            })
188        }
189
190        let fnet_sockets::IpSocketState {
191            family,
192            src_addr,
193            dst_addr,
194            cookie,
195            marks,
196            transport,
197            __source_breaking,
198        } = value;
199
200        let family = family.ok_or(IpSocketStateError::MissingField("family"))?;
201        let cookie = cookie.ok_or(IpSocketStateError::MissingField("cookie"))?;
202        let marks = marks.ok_or(IpSocketStateError::MissingField("marks"))?;
203        let transport = transport.ok_or(IpSocketStateError::MissingField("transport"))?;
204
205        match family {
206            fnet::IpVersion::V4 => Ok(IpSocketState::V4(to_ip_socket_specific(
207                src_addr, dst_addr, cookie, marks, transport,
208            )?)),
209            fnet::IpVersion::V6 => Ok(IpSocketState::V6(to_ip_socket_specific(
210                src_addr, dst_addr, cookie, marks, transport,
211            )?)),
212        }
213    }
214}
215
216impl From<IpSocketState> for fnet_sockets::IpSocketState {
217    fn from(state: IpSocketState) -> Self {
218        match state {
219            IpSocketState::V4(state) => state.into(),
220            IpSocketState::V6(state) => state.into(),
221        }
222    }
223}
224
225/// Lowest-level socket state information that ensures all fields are for the
226/// same IP version.
227#[derive(Debug, PartialEq, Eq, Clone, GenericOverIp)]
228#[generic_over_ip(I, Ip)]
229pub struct IpSocketStateSpecific<I: Ip> {
230    /// The source address of the socket.
231    pub src_addr: Option<I::Addr>,
232    /// The destination address of the socket.
233    pub dst_addr: Option<I::Addr>,
234    /// The cookie of the socket.
235    pub cookie: u64,
236    /// The marks of the socket.
237    pub marks: Marks,
238    /// The transport state of the socket.
239    pub transport: IpSocketTransportState,
240}
241
242impl<I: Ip> From<IpSocketStateSpecific<I>> for fnet_sockets::IpSocketState {
243    fn from(value: IpSocketStateSpecific<I>) -> Self {
244        let IpSocketStateSpecific { src_addr, dst_addr, cookie, marks, transport } = value;
245
246        fnet_sockets::IpSocketState {
247            family: Some(I::VERSION.into_ext()),
248            src_addr: src_addr.map(|a| net_types::ip::IpAddr::from(a).into_ext()),
249            dst_addr: dst_addr.map(|a| net_types::ip::IpAddr::from(a).into_ext()),
250            cookie: Some(cookie),
251            marks: Some(marks.into()),
252            transport: Some(transport.into()),
253            __source_breaking: fidl::marker::SourceBreaking,
254        }
255    }
256}
257
258/// Extension type for [`fnet_sockets::IpSocketTransportState`].
259#[derive(Debug, PartialEq, Eq, Clone)]
260pub enum IpSocketTransportState {
261    /// TCP socket state.
262    Tcp(IpSocketTcpState),
263    /// UDP socket state.
264    Udp(IpSocketUdpState),
265}
266
267/// Error type for [`IpSocketTransportState`] conversion.
268#[derive(Debug, PartialEq, Error)]
269pub enum IpSocketTransportStateError {
270    /// Error converting a TCP socket state.
271    #[error("tcp validation error: {0}")]
272    Tcp(IpSocketTcpStateError),
273    /// Error converting a UDP socket state.
274    #[error("udp validation error: {0}")]
275    Udp(IpSocketUdpStateError),
276    /// A union type was unknown.
277    #[error("got unexpected union variant: {0}")]
278    UnknownUnionVariant(u64),
279}
280
281impl TryFrom<fnet_sockets::IpSocketTransportState> for IpSocketTransportState {
282    type Error = IpSocketTransportStateError;
283
284    fn try_from(value: fnet_sockets::IpSocketTransportState) -> Result<Self, Self::Error> {
285        match value {
286            fnet_sockets::IpSocketTransportState::Tcp(tcp) => Ok(IpSocketTransportState::Tcp(
287                tcp.try_into().map_err(IpSocketTransportStateError::Tcp)?,
288            )),
289            fnet_sockets::IpSocketTransportState::Udp(udp) => Ok(IpSocketTransportState::Udp(
290                udp.try_into().map_err(IpSocketTransportStateError::Udp)?,
291            )),
292            fnet_sockets::IpSocketTransportState::__SourceBreaking { unknown_ordinal } => {
293                Err(IpSocketTransportStateError::UnknownUnionVariant(unknown_ordinal))
294            }
295        }
296    }
297}
298
299impl From<IpSocketTransportState> for fnet_sockets::IpSocketTransportState {
300    fn from(state: IpSocketTransportState) -> Self {
301        match state {
302            IpSocketTransportState::Tcp(tcp) => {
303                fnet_sockets::IpSocketTransportState::Tcp(tcp.into())
304            }
305            IpSocketTransportState::Udp(udp) => {
306                fnet_sockets::IpSocketTransportState::Udp(udp.into())
307            }
308        }
309    }
310}
311
312/// Extension type for [`fnet_sockets::IpSocketTcpState`].
313#[derive(Debug, PartialEq, Eq, Clone)]
314pub struct IpSocketTcpState {
315    /// The source port of the socket.
316    pub src_port: Option<u16>,
317    /// The destination port of the socket.
318    pub dst_port: Option<u16>,
319    /// The TCP state machine state for the socket.
320    pub state: fnet_tcp::State,
321    /// Extended TCP information if the TCP_INFO extension was requested.
322    pub tcp_info: Option<TcpInfo>,
323}
324
325/// Error type for [`IpSocketTcpState`] conversion.
326#[derive(Debug, PartialEq, Error)]
327pub enum IpSocketTcpStateError {
328    /// Missing a required field.
329    #[error("missing field: {0}")]
330    MissingField(&'static str),
331    /// Error converting a [`TcpInfo`].
332    #[error("tcp info error: {0}")]
333    TcpInfo(TcpInfoError),
334}
335
336impl TryFrom<fnet_sockets::IpSocketTcpState> for IpSocketTcpState {
337    type Error = IpSocketTcpStateError;
338
339    fn try_from(value: fnet_sockets::IpSocketTcpState) -> Result<Self, Self::Error> {
340        let fnet_sockets::IpSocketTcpState {
341            src_port,
342            dst_port,
343            state,
344            tcp_info,
345            __source_breaking,
346        } = value;
347
348        let state = state.ok_or(IpSocketTcpStateError::MissingField("state"))?;
349
350        Ok(IpSocketTcpState {
351            src_port,
352            dst_port,
353            state,
354            tcp_info: tcp_info
355                .map(|t| t.try_into())
356                .transpose()
357                .map_err(|e| IpSocketTcpStateError::TcpInfo(e))?,
358        })
359    }
360}
361
362impl From<IpSocketTcpState> for fnet_sockets::IpSocketTcpState {
363    fn from(state: IpSocketTcpState) -> Self {
364        let IpSocketTcpState { src_port, dst_port, state, tcp_info } = state;
365        fnet_sockets::IpSocketTcpState {
366            src_port,
367            dst_port,
368            state: Some(state),
369            tcp_info: tcp_info.map(Into::into),
370            __source_breaking: fidl::marker::SourceBreaking,
371        }
372    }
373}
374
375/// Extension type for [`fnet_tcp::Info`].
376#[derive(Debug, PartialEq, Eq, Clone)]
377pub struct TcpInfo {
378    /// The state of the TCP connection.
379    pub state: fnet_tcp::State,
380    /// The congestion control state of the TCP connection.
381    pub ca_state: fnet_tcp::CongestionControlState,
382    /// The retransmission timeout of the TCP connection in microseconds.
383    pub rto_usec: Option<u32>,
384    /// The time since the most recent data was sent on the connection in milliseconds.
385    pub tcpi_last_data_sent_msec: Option<u32>,
386    /// The time since the most recent ACK was received in milliseconds.
387    pub tcpi_last_ack_recv_msec: Option<u32>,
388    /// The estimated smoothed roundtrip time in microseconds.
389    pub rtt_usec: Option<u32>,
390    /// The smoothed mean deviation of the roundtrip time in microseconds.
391    pub rtt_var_usec: Option<u32>,
392    /// The sending slow start threshold in segments.
393    pub snd_ssthresh: u32,
394    /// The current sending congestion window in segments.
395    pub snd_cwnd: u32,
396    /// The total number of retransmissions.
397    pub tcpi_total_retrans: u32,
398    /// The total number of segments sent.
399    pub tcpi_segs_out: u64,
400    /// The total number of segments received.
401    pub tcpi_segs_in: u64,
402    /// Whether reordering has been seen on the connection.
403    pub reorder_seen: bool,
404}
405
406/// Error type for [`TcpInfo`] conversion.
407#[derive(Debug, PartialEq, Error)]
408pub enum TcpInfoError {
409    /// Missing a required field.
410    #[error("missing field: {0}")]
411    MissingField(&'static str),
412}
413
414impl TryFrom<fnet_tcp::Info> for TcpInfo {
415    type Error = TcpInfoError;
416
417    fn try_from(value: fnet_tcp::Info) -> Result<Self, Self::Error> {
418        let fnet_tcp::Info {
419            state,
420            ca_state,
421            rto_usec,
422            tcpi_last_data_sent_msec,
423            tcpi_last_ack_recv_msec,
424            rtt_usec,
425            rtt_var_usec,
426            snd_ssthresh,
427            snd_cwnd,
428            tcpi_total_retrans,
429            tcpi_segs_out,
430            tcpi_segs_in,
431            reorder_seen,
432            __source_breaking,
433        } = value;
434
435        Ok(TcpInfo {
436            state: state.ok_or(TcpInfoError::MissingField("state"))?,
437            ca_state: ca_state.ok_or(TcpInfoError::MissingField("ca_state"))?,
438            rto_usec,
439            tcpi_last_data_sent_msec,
440            tcpi_last_ack_recv_msec,
441            rtt_usec,
442            rtt_var_usec,
443            snd_ssthresh: snd_ssthresh.ok_or(TcpInfoError::MissingField("snd_ssthresh"))?,
444            snd_cwnd: snd_cwnd.ok_or(TcpInfoError::MissingField("snd_cwnd"))?,
445            tcpi_total_retrans: tcpi_total_retrans
446                .ok_or(TcpInfoError::MissingField("tcpi_total_retrans"))?,
447            tcpi_segs_out: tcpi_segs_out.ok_or(TcpInfoError::MissingField("tcpi_segs_out"))?,
448            tcpi_segs_in: tcpi_segs_in.ok_or(TcpInfoError::MissingField("tcpi_segs_in"))?,
449            reorder_seen: reorder_seen.ok_or(TcpInfoError::MissingField("reorder_seen"))?,
450        })
451    }
452}
453
454impl From<TcpInfo> for fnet_tcp::Info {
455    fn from(info: TcpInfo) -> Self {
456        let TcpInfo {
457            state,
458            ca_state,
459            rto_usec,
460            tcpi_last_data_sent_msec,
461            tcpi_last_ack_recv_msec,
462            rtt_usec,
463            rtt_var_usec,
464            snd_ssthresh,
465            snd_cwnd,
466            tcpi_total_retrans,
467            tcpi_segs_out,
468            tcpi_segs_in,
469            reorder_seen,
470        } = info;
471        fnet_tcp::Info {
472            state: Some(state),
473            ca_state: Some(ca_state),
474            rto_usec: rto_usec,
475            tcpi_last_data_sent_msec,
476            tcpi_last_ack_recv_msec,
477            rtt_usec: rtt_usec,
478            rtt_var_usec: rtt_var_usec,
479            snd_ssthresh: Some(snd_ssthresh),
480            snd_cwnd: Some(snd_cwnd),
481            tcpi_total_retrans: Some(tcpi_total_retrans),
482            tcpi_segs_out: Some(tcpi_segs_out),
483            tcpi_segs_in: Some(tcpi_segs_in),
484            reorder_seen: Some(reorder_seen),
485            __source_breaking: fidl::marker::SourceBreaking,
486        }
487    }
488}
489
490/// Extension type for [`fnet_sockets::IpSocketUdpState`].
491#[derive(Debug, PartialEq, Eq, Clone)]
492pub struct IpSocketUdpState {
493    /// The source port of the socket.
494    pub src_port: Option<u16>,
495    /// The destination port of the socket.
496    pub dst_port: Option<u16>,
497    /// The UDP pseudo-state machine state for the socket.
498    pub state: fnet_udp::State,
499}
500
501/// Error type for [`IpSocketUdpState`] conversion.
502#[derive(Debug, PartialEq, Error)]
503pub enum IpSocketUdpStateError {
504    /// Missing a required field.
505    #[error("missing field: {0}")]
506    MissingField(&'static str),
507}
508
509impl TryFrom<fnet_sockets::IpSocketUdpState> for IpSocketUdpState {
510    type Error = IpSocketUdpStateError;
511
512    fn try_from(value: fnet_sockets::IpSocketUdpState) -> Result<Self, Self::Error> {
513        let fnet_sockets::IpSocketUdpState { src_port, dst_port, state, __source_breaking } = value;
514
515        let state = state.ok_or(IpSocketUdpStateError::MissingField("state"))?;
516
517        Ok(IpSocketUdpState { src_port, dst_port, state })
518    }
519}
520
521impl From<IpSocketUdpState> for fnet_sockets::IpSocketUdpState {
522    fn from(state: IpSocketUdpState) -> Self {
523        let IpSocketUdpState { src_port, dst_port, state } = state;
524        fnet_sockets::IpSocketUdpState {
525            src_port,
526            dst_port,
527            state: Some(state),
528            __source_breaking: fidl::marker::SourceBreaking,
529        }
530    }
531}
532
533/// Errors returned by [`iterate_ip`]
534#[derive(Debug, Error)]
535pub enum IterateIpError {
536    /// The specified matcher was the first invalid one.
537    #[error("invalid matcher at position {0}")]
538    InvalidMatcher(usize),
539    /// An unknown response was received on the call to `Diagnostics.IterateIp`
540    #[error("unknown ordinal on Diagnostics.IterateIp call: {0}")]
541    UnknownOrdinal(u64),
542    /// A low-level FIDL error was encountered on the call to
543    /// `Diagnostics.IterateIp`.
544    #[error("fidl error during Diagnostics.IterateIp call: {0}")]
545    Fidl(fidl::Error),
546}
547
548impl From<fidl::Error> for IterateIpError {
549    fn from(e: fidl::Error) -> Self {
550        IterateIpError::Fidl(e)
551    }
552}
553
554/// Errors returned by the stream returned from [`iterate_ip`].
555#[derive(Debug, Error)]
556pub enum IpIteratorError {
557    /// The netstack returned an empty batch of sockets
558    #[error("received empty batch of sockets")]
559    EmptyBatch,
560    /// A low-level FIDL error was encountered on the call to
561    /// `Diagnostics.IterateIp`.
562    #[error("fidl error during Diagnostics.IterateIp call: {0}")]
563    Fidl(fidl::Error),
564    /// An error was encountered while converting a socket state.
565    #[error("error converting socket state: {0}")]
566    Conversion(IpSocketStateError),
567}
568
569impl From<fidl::Error> for IpIteratorError {
570    fn from(e: fidl::Error) -> Self {
571        IpIteratorError::Fidl(e)
572    }
573}
574
575/// Send a request to `Diagnostics.IterateIp` and drive the resulting
576/// `IpIterator`.
577///
578/// `IpIterator` returns a series of batches of sockets matching the query, the
579/// returned stream flattens those batches into individual sockets. If an error
580/// is encuontered during iteration, it is returned and iteration halts.
581pub async fn iterate_ip<M, I>(
582    diagnostics: &fnet_sockets::DiagnosticsProxy,
583    extensions: fnet_sockets::Extensions,
584    matchers: M,
585) -> Result<impl Stream<Item = Result<IpSocketState, IpIteratorError>>, IterateIpError>
586where
587    M: IntoIterator<Item = I>,
588    I: Into<fnet_sockets::IpSocketMatcher>,
589{
590    let (proxy, server_end) = fidl::endpoints::create_proxy::<fnet_sockets::IpIteratorMarker>();
591    match diagnostics
592        .iterate_ip(
593            server_end,
594            extensions,
595            &matchers.into_iter().map(Into::into).collect::<Vec<_>>()[..],
596        )
597        .await?
598    {
599        fnet_sockets::IterateIpResult::Ok(_empty) => Ok(()),
600        fnet_sockets::IterateIpResult::InvalidMatcher(fnet_sockets::InvalidMatcher { index }) => {
601            Err(IterateIpError::InvalidMatcher(index as usize))
602        }
603        fnet_sockets::IterateIpResult::__SourceBreaking { unknown_ordinal } => {
604            Err(IterateIpError::UnknownOrdinal(unknown_ordinal))
605        }
606    }?;
607
608    Ok(futures::stream::try_unfold((proxy, true), |(proxy, has_more)| async move {
609        if !has_more {
610            return Ok(None);
611        }
612
613        let (batch, has_more) = proxy.next().await?;
614        if batch.is_empty() && has_more {
615            Err(IpIteratorError::EmptyBatch)
616        } else {
617            Ok(Some((
618                futures::stream::iter(
619                    batch
620                        .into_iter()
621                        .map(|s| s.try_into().map_err(|e| IpIteratorError::Conversion(e))),
622                ),
623                (proxy, has_more),
624            )))
625        }
626    })
627    .try_flatten())
628}
629
630/// Errors returned by [`disconnect_ip`]
631#[derive(Debug, Error)]
632pub enum DisconnectIpError {
633    /// The specified matcher was the first invalid one.
634    #[error("invalid matcher at position {0}")]
635    InvalidMatcher(usize),
636    /// Specified matchers would a priori match all sockets.
637    #[error("matchers were unconstrained")]
638    UnconstrainedMatchers,
639    /// An unknown response was received on the call to `Control.DisconnectIp`
640    #[error("unknown ordinal on Control.DisconnectIp call: {0}")]
641    UnknownOrdinal(u64),
642    /// A low-level FIDL error was encountered on the call to
643    /// `Control.DisconnectIp`.
644    #[error("fidl error during Control.DisconnectIp call: {0}")]
645    Fidl(fidl::Error),
646}
647
648/// Send a request to `Control.DisconnectIp` with the provided matchers.
649pub async fn disconnect_ip<M, I>(
650    control: &fnet_sockets::ControlProxy,
651    matchers: M,
652) -> Result<usize, DisconnectIpError>
653where
654    M: IntoIterator<Item = I>,
655    I: Into<fnet_sockets::IpSocketMatcher>,
656{
657    match control
658        .disconnect_ip(&fnet_sockets::ControlDisconnectIpRequest {
659            matchers: Some(matchers.into_iter().map(Into::into).collect()),
660            __source_breaking: fidl::marker::SourceBreaking,
661        })
662        .await
663    {
664        Ok(r) => match r {
665            fnet_sockets::DisconnectIpResult::Ok(fnet_sockets::DisconnectIpResponse {
666                disconnected,
667            }) => {
668                // Unwrap is safe because usize is always at least u32.
669                Ok(disconnected.try_into().unwrap())
670            }
671            fnet_sockets::DisconnectIpResult::InvalidMatcher(fnet_sockets::InvalidMatcher {
672                index,
673            }) => {
674                // Unwrap is safe because usize is always at least u32.
675                Err(DisconnectIpError::InvalidMatcher(index.try_into().unwrap()))
676            }
677            fnet_sockets::DisconnectIpResult::UnconstrainedMatchers(fnet_sockets::Empty) => {
678                Err(DisconnectIpError::UnconstrainedMatchers)
679            }
680            fnet_sockets::DisconnectIpResult::__SourceBreaking { unknown_ordinal } => {
681                Err(DisconnectIpError::UnknownOrdinal(unknown_ordinal))
682            }
683        },
684        Err(e) => Err(DisconnectIpError::Fidl(e)),
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    use std::num::NonZeroU64;
693
694    use assert_matches::assert_matches;
695    use fidl_fuchsia_net as fnet;
696    use fidl_fuchsia_net_tcp as fnet_tcp;
697    use futures::{FutureExt as _, StreamExt as _, future, pin_mut};
698    use net_declare::{fidl_ip, fidl_subnet, net_ip_v4, net_ip_v6};
699    use test_case::test_case;
700
701    #[test_case(
702        fnet_sockets::IpSocketMatcher::Family(fnet::IpVersion::V4),
703        IpSocketMatcher::Family(ip::IpVersion::V4);
704        "FamilyIpv4"
705    )]
706    #[test_case(
707        fnet_sockets::IpSocketMatcher::Family(fnet::IpVersion::V6),
708        IpSocketMatcher::Family(ip::IpVersion::V6);
709        "FamilyIpv6"
710    )]
711    #[test_case(
712        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Bound(
713            fnet_matchers::Address {
714                matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("192.0.2.0/24")),
715                invert: true,
716            }
717        )),
718        IpSocketMatcher::SrcAddr(fnet_matchers_ext::BoundAddress::Bound(
719            fnet_matchers_ext::Address {
720                matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
721                    fnet_matchers_ext::Subnet::try_from(fidl_subnet!("192.0.2.0/24")).unwrap()
722                ),
723                invert: true,
724            }
725        ));
726        "SrcAddr"
727    )]
728    #[test_case(
729        fnet_sockets::IpSocketMatcher::DstAddr(fnet_matchers::BoundAddress::Bound(
730            fnet_matchers::Address {
731                matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("2001:db8::/32")),
732                invert: false,
733            }
734        )),
735        IpSocketMatcher::DstAddr(fnet_matchers_ext::BoundAddress::Bound(
736            fnet_matchers_ext::Address {
737                matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
738                    fnet_matchers_ext::Subnet::try_from(fidl_subnet!("2001:db8::/32")).unwrap()
739                ),
740                invert: false,
741            }
742        ));
743        "DstAddr"
744    )]
745    #[test_case(
746        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
747            fnet_matchers::TcpSocket::Empty(fnet_matchers::Empty)
748        )),
749        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
750            fnet_matchers_ext::TcpSocket::Empty
751        ));
752        "ProtoTcp"
753    )]
754    #[test_case(
755        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
756            fnet_matchers::UdpSocket::Empty(fnet_matchers::Empty)
757        )),
758        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
759            fnet_matchers_ext::UdpSocket::Empty
760        ));
761        "ProtoUdp"
762    )]
763    #[test_case(
764        fnet_sockets::IpSocketMatcher::BoundInterface(fnet_matchers::BoundInterface::Unbound(
765            fnet_matchers::Empty
766        )),
767        IpSocketMatcher::BoundInterface(fnet_matchers_ext::BoundInterface::Unbound);
768        "BoundInterfaceUnbound"
769    )]
770    #[test_case(
771        fnet_sockets::IpSocketMatcher::BoundInterface(fnet_matchers::BoundInterface::Bound(
772            fnet_matchers::Interface::Id(1)
773        )),
774        IpSocketMatcher::BoundInterface(fnet_matchers_ext::BoundInterface::Bound(
775            fnet_matchers_ext::Interface::Id(NonZeroU64::new(1).unwrap())
776        ));
777        "BoundInterfaceBound"
778    )]
779    #[test_case(
780        fnet_sockets::IpSocketMatcher::Cookie(fnet_matchers::SocketCookie {
781            cookie: 12345,
782            invert: false,
783        }),
784        IpSocketMatcher::Cookie(fnet_matchers::SocketCookie {
785            cookie: 12345,
786            invert: false,
787        });
788        "Cookie"
789    )]
790    #[test_case(
791        fnet_sockets::IpSocketMatcher::Mark(fnet_matchers::MarkInDomain {
792            domain: fnet::MarkDomain::Mark1,
793            mark: fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked),
794        }),
795        IpSocketMatcher::Mark(fnet_matchers_ext::MarkInDomain {
796            domain: fnet::MarkDomain::Mark1,
797            mark: fnet_matchers_ext::Mark::Unmarked,
798        });
799        "Mark"
800    )]
801    #[test_case(
802        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty)),
803        IpSocketMatcher::SrcAddr(fnet_matchers_ext::BoundAddress::Unbound);
804        "SrcAddrUnbound"
805    )]
806    #[test_case(
807        fnet_sockets::IpSocketMatcher::DstAddr(fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty)),
808        IpSocketMatcher::DstAddr(fnet_matchers_ext::BoundAddress::Unbound);
809        "DstAddrUnbound"
810    )]
811    #[test_case(
812        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
813            fnet_matchers::TcpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
814        )),
815        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
816            fnet_matchers_ext::TcpSocket::SrcPort(fnet_matchers_ext::BoundPort::Unbound)
817        ));
818        "ProtoTcpSrcPortUnbound"
819    )]
820    #[test_case(
821        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
822            fnet_matchers::TcpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
823        )),
824        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
825            fnet_matchers_ext::TcpSocket::DstPort(fnet_matchers_ext::BoundPort::Unbound)
826        ));
827        "ProtoTcpDstPortUnbound"
828    )]
829    #[test_case(
830        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
831            fnet_matchers::UdpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
832        )),
833        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
834            fnet_matchers_ext::UdpSocket::SrcPort(fnet_matchers_ext::BoundPort::Unbound)
835        ));
836        "ProtoUdpSrcPortUnbound"
837    )]
838    #[test_case(
839        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
840            fnet_matchers::UdpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
841        )),
842        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
843            fnet_matchers_ext::UdpSocket::DstPort(fnet_matchers_ext::BoundPort::Unbound)
844        ));
845        "ProtoUdpDstPortUnbound"
846    )]
847    #[test_case(
848        fnet_tcp::Info {
849            state: Some(fnet_tcp::State::Established),
850            ca_state: Some(fnet_tcp::CongestionControlState::Open),
851            rto_usec: Some(1),
852            tcpi_last_data_sent_msec: Some(2),
853            tcpi_last_ack_recv_msec: Some(3),
854            rtt_usec: Some(4),
855            rtt_var_usec: Some(5),
856            snd_ssthresh: Some(6),
857            snd_cwnd: Some(7),
858            tcpi_total_retrans: Some(8),
859            tcpi_segs_out: Some(9),
860            tcpi_segs_in: Some(10),
861            reorder_seen: Some(true),
862            __source_breaking: fidl::marker::SourceBreaking,
863        },
864        TcpInfo {
865            state: fnet_tcp::State::Established,
866            ca_state: fnet_tcp::CongestionControlState::Open,
867            rto_usec: Some(1),
868            tcpi_last_data_sent_msec: Some(2),
869            tcpi_last_ack_recv_msec: Some(3),
870            rtt_usec: Some(4),
871            rtt_var_usec: Some(5),
872            snd_ssthresh: 6,
873            snd_cwnd: 7,
874            tcpi_total_retrans: 8,
875            tcpi_segs_out: 9,
876            tcpi_segs_in: 10,
877            reorder_seen: true,
878        };
879        "TcpInfo"
880    )]
881    #[test_case(
882        fnet_sockets::IpSocketState {
883            family: Some(fnet::IpVersion::V4),
884            src_addr: Some(fidl_ip!("192.168.1.1")),
885            dst_addr: Some(fidl_ip!("192.168.1.2")),
886            cookie: Some(1234),
887            marks: Some(fnet::Marks {
888                mark_1: Some(1111),
889                mark_2: None,
890                __source_breaking: fidl::marker::SourceBreaking,
891            }),
892            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
893                fnet_sockets::IpSocketTcpState {
894                    src_port: Some(1111),
895                    dst_port: Some(2222),
896                    state: Some(fnet_tcp::State::Established),
897                    tcp_info: None,
898                    __source_breaking: fidl::marker::SourceBreaking,
899                },
900            )),
901            __source_breaking: fidl::marker::SourceBreaking,
902        },
903        IpSocketState::V4(IpSocketStateSpecific {
904            src_addr: Some(net_ip_v4!("192.168.1.1")),
905            dst_addr: Some(net_ip_v4!("192.168.1.2")),
906            cookie: 1234,
907            marks: fnet::Marks {
908                mark_1: Some(1111),
909                mark_2: None,
910                __source_breaking: fidl::marker::SourceBreaking,
911            }.into(),
912            transport: IpSocketTransportState::Tcp(IpSocketTcpState {
913                src_port: Some(1111),
914                dst_port: Some(2222),
915                state: fnet_tcp::State::Established,
916                tcp_info: None,
917            }),
918        });
919        "IpSocketStateV4"
920    )]
921    #[test_case(
922        fnet_sockets::IpSocketState {
923            family: Some(fnet::IpVersion::V6),
924            src_addr: Some(fidl_ip!("2001:db8::1")),
925            dst_addr: Some(fidl_ip!("2001:db8::2")),
926            cookie: Some(1234),
927            marks: Some(fnet::Marks {
928                mark_1: Some(1111),
929                mark_2: None,
930                __source_breaking: fidl::marker::SourceBreaking,
931            }),
932            transport: Some(fnet_sockets::IpSocketTransportState::Udp(
933                fnet_sockets::IpSocketUdpState {
934                    src_port: Some(3333),
935                    dst_port: Some(4444),
936                    state: Some(fnet_udp::State::Connected),
937                    __source_breaking: fidl::marker::SourceBreaking,
938                },
939            )),
940            __source_breaking: fidl::marker::SourceBreaking,
941        },
942        IpSocketState::V6(IpSocketStateSpecific {
943            src_addr: Some(net_ip_v6!("2001:db8::1")),
944            dst_addr: Some(net_ip_v6!("2001:db8::2")),
945            cookie: 1234,
946            marks: fnet::Marks {
947                mark_1: Some(1111),
948                mark_2: None,
949                __source_breaking: fidl::marker::SourceBreaking,
950            }.into(),
951            transport: IpSocketTransportState::Udp(IpSocketUdpState {
952                src_port: Some(3333),
953                dst_port: Some(4444),
954                state: fnet_udp::State::Connected,
955            }),
956        });
957        "IpSocketStateV6"
958    )]
959    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
960    where
961        E: TryFrom<F> + Clone + std::fmt::Debug + PartialEq,
962        <E as TryFrom<F>>::Error: std::fmt::Debug + PartialEq,
963        F: From<E> + Clone + std::fmt::Debug + PartialEq,
964    {
965        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
966        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type);
967    }
968
969    #[test_case(
970        fnet_sockets::IpSocketMatcher::__SourceBreaking { unknown_ordinal: 100 } =>
971            Err(IpSocketMatcherError::UnknownUnionVariant(100));
972        "UnknownUnionVariant"
973    )]
974    #[test_case(
975        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Bound(
976            fnet_matchers::Address {
977                matcher: fnet_matchers::AddressMatcherType::__SourceBreaking { unknown_ordinal: 100 },
978                invert: false,
979            }
980        )) => Err(IpSocketMatcherError::Address(fnet_matchers_ext::BoundAddressError::Address(
981            fnet_matchers_ext::AddressError::AddressMatcherType(
982                fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant
983            )
984        )));
985        "AddressError"
986    )]
987    #[test_case(
988        fnet_sockets::IpSocketMatcher::Proto(
989            fnet_matchers::SocketTransportProtocol::__SourceBreaking { unknown_ordinal: 100 }
990        ) => Err(IpSocketMatcherError::TransportProtocol(
991            fnet_matchers_ext::SocketTransportProtocolError::UnknownUnionVariant(100)
992        ));
993        "TransportProtocolError"
994    )]
995    #[test_case(
996        fnet_sockets::IpSocketMatcher::BoundInterface(
997            fnet_matchers::BoundInterface::__SourceBreaking { unknown_ordinal: 100 }
998        ) => Err(IpSocketMatcherError::BoundInterface(
999            fnet_matchers_ext::BoundInterfaceError::UnknownUnionVariant(100)
1000        ));
1001        "BoundInterfaceError"
1002    )]
1003    #[test_case(
1004        fnet_sockets::IpSocketMatcher::Mark(fnet_matchers::MarkInDomain {
1005            domain: fnet::MarkDomain::Mark1,
1006            mark: fnet_matchers::Mark::__SourceBreaking { unknown_ordinal: 100 },
1007        }) => Err(IpSocketMatcherError::Mark(
1008            fnet_matchers_ext::MarkInDomainError::Mark(
1009                fnet_matchers_ext::MarkError::UnknownUnionVariant(100)
1010            )
1011        ));
1012        "MarkError"
1013    )]
1014    fn ip_socket_matcher_try_from_error(
1015        fidl: fnet_sockets::IpSocketMatcher,
1016    ) -> Result<IpSocketMatcher, IpSocketMatcherError> {
1017        IpSocketMatcher::try_from(fidl)
1018    }
1019
1020    #[test_case(
1021        fnet_sockets::IpSocketState {
1022            family: None,
1023            src_addr: Some(fidl_ip!("192.168.1.1")),
1024            dst_addr: Some(fidl_ip!("192.168.1.2")),
1025            cookie: Some(1234),
1026            marks: Some(fnet::Marks {
1027                mark_1: Some(1111),
1028                mark_2: None,
1029                __source_breaking: fidl::marker::SourceBreaking,
1030            }),
1031            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1032                fnet_sockets::IpSocketTcpState {
1033                    src_port: Some(1111),
1034                    dst_port: Some(2222),
1035                    state: Some(fnet_tcp::State::Established),
1036                    tcp_info: None,
1037                    __source_breaking: fidl::marker::SourceBreaking,
1038                },
1039            )),
1040            __source_breaking: fidl::marker::SourceBreaking,
1041        } => Err(IpSocketStateError::MissingField("family"));
1042        "MissingFamily"
1043    )]
1044    #[test_case(
1045        fnet_sockets::IpSocketState {
1046            family: Some(fnet::IpVersion::V4),
1047            src_addr: Some(fidl_ip!("192.168.1.1")),
1048            dst_addr: Some(fidl_ip!("192.168.1.2")),
1049            cookie: None,
1050            marks: Some(fnet::Marks {
1051                mark_1: Some(1111),
1052                mark_2: None,
1053                __source_breaking: fidl::marker::SourceBreaking,
1054            }),
1055            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1056                fnet_sockets::IpSocketTcpState {
1057                    src_port: Some(1111),
1058                    dst_port: Some(2222),
1059                    state: Some(fnet_tcp::State::Established),
1060                    tcp_info: None,
1061                    __source_breaking: fidl::marker::SourceBreaking,
1062                },
1063            )),
1064            __source_breaking: fidl::marker::SourceBreaking,
1065        } => Err(IpSocketStateError::MissingField("cookie"));
1066        "MissingCookie"
1067    )]
1068    #[test_case(
1069        fnet_sockets::IpSocketState {
1070            family: Some(fnet::IpVersion::V4),
1071            src_addr: Some(fidl_ip!("192.168.1.1")),
1072            dst_addr: Some(fidl_ip!("192.168.1.2")),
1073            cookie: Some(1234),
1074            marks: None,
1075            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1076                fnet_sockets::IpSocketTcpState {
1077                    src_port: Some(1111),
1078                    dst_port: Some(2222),
1079                    state: Some(fnet_tcp::State::Established),
1080                    tcp_info: None,
1081                    __source_breaking: fidl::marker::SourceBreaking,
1082                },
1083            )),
1084            __source_breaking: fidl::marker::SourceBreaking,
1085        } => Err(IpSocketStateError::MissingField("marks"));
1086        "MissingMarks"
1087    )]
1088    #[test_case(
1089        fnet_sockets::IpSocketState {
1090            family: Some(fnet::IpVersion::V4),
1091            src_addr: Some(fidl_ip!("192.168.1.1")),
1092            dst_addr: Some(fidl_ip!("192.168.1.2")),
1093            cookie: Some(1234),
1094            marks: Some(fnet::Marks {
1095                mark_1: Some(1111),
1096                mark_2: None,
1097                __source_breaking: fidl::marker::SourceBreaking,
1098            }),
1099            transport: None,
1100            __source_breaking: fidl::marker::SourceBreaking,
1101        } => Err(IpSocketStateError::MissingField("transport"));
1102        "MissingTransport"
1103    )]
1104    #[test_case(
1105        fnet_sockets::IpSocketState {
1106            family: Some(fnet::IpVersion::V4),
1107            src_addr: Some(fidl_ip!("192.168.1.1")),
1108            dst_addr: Some(fidl_ip!("2001:db8::2")),
1109            cookie: Some(1234),
1110            marks: Some(fnet::Marks {
1111                mark_1: Some(1111),
1112                mark_2: None,
1113                __source_breaking: fidl::marker::SourceBreaking,
1114            }),
1115            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1116                fnet_sockets::IpSocketTcpState {
1117                    src_port: Some(1111),
1118                    dst_port: Some(2222),
1119                    state: Some(fnet_tcp::State::Established),
1120                    tcp_info: None,
1121                    __source_breaking: fidl::marker::SourceBreaking,
1122                },
1123            )),
1124            __source_breaking: fidl::marker::SourceBreaking,
1125        } => Err(IpSocketStateError::VersionMismatch);
1126        "VersionMismatchV4"
1127    )]
1128    #[test_case(
1129        fnet_sockets::IpSocketState {
1130            family: Some(fnet::IpVersion::V6),
1131            src_addr: Some(fidl_ip!("192.168.1.1")),
1132            dst_addr: Some(fidl_ip!("2001:db8::2")),
1133            cookie: Some(1234),
1134            marks: Some(fnet::Marks {
1135                mark_1: Some(1111),
1136                mark_2: None,
1137                __source_breaking: fidl::marker::SourceBreaking,
1138            }),
1139            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1140                fnet_sockets::IpSocketTcpState {
1141                    src_port: Some(1111),
1142                    dst_port: Some(2222),
1143                    state: Some(fnet_tcp::State::Established),
1144                    tcp_info: None,
1145                    __source_breaking: fidl::marker::SourceBreaking,
1146                },
1147            )),
1148            __source_breaking: fidl::marker::SourceBreaking,
1149        } => Err(IpSocketStateError::VersionMismatch);
1150        "VersionMismatchV6"
1151    )]
1152    #[test_case(
1153        fnet_sockets::IpSocketState {
1154            family: Some(fnet::IpVersion::V4),
1155            src_addr: Some(fidl_ip!("192.168.1.1")),
1156            dst_addr: Some(fidl_ip!("192.168.1.2")),
1157            cookie: Some(1234),
1158            marks: Some(fnet::Marks {
1159                mark_1: Some(1111),
1160                mark_2: None,
1161                __source_breaking: fidl::marker::SourceBreaking,
1162            }),
1163            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1164                fnet_sockets::IpSocketTcpState {
1165                    src_port: Some(1111),
1166                    dst_port: Some(2222),
1167                    state: None,
1168                    tcp_info: None,
1169                    __source_breaking: fidl::marker::SourceBreaking,
1170                },
1171            )),
1172            __source_breaking: fidl::marker::SourceBreaking,
1173        } => Err(IpSocketStateError::Transport(IpSocketTransportStateError::Tcp(
1174                IpSocketTcpStateError::MissingField("state"),
1175        )));
1176        "MissingTcpState"
1177    )]
1178    #[test_case(
1179        fnet_sockets::IpSocketState {
1180            family: Some(fnet::IpVersion::V6),
1181            src_addr: Some(fidl_ip!("2001:db8::1")),
1182            dst_addr: Some(fidl_ip!("2001:db8::2")),
1183            cookie: Some(1234),
1184            marks: Some(fnet::Marks {
1185                mark_1: Some(1111),
1186                mark_2: None,
1187                __source_breaking: fidl::marker::SourceBreaking,
1188            }),
1189            transport: Some(fnet_sockets::IpSocketTransportState::Udp(
1190                fnet_sockets::IpSocketUdpState {
1191                    src_port: Some(3333),
1192                    dst_port: Some(4444),
1193                    state: None,
1194                    __source_breaking: fidl::marker::SourceBreaking,
1195                },
1196            )),
1197            __source_breaking: fidl::marker::SourceBreaking,
1198        } => Err(IpSocketStateError::Transport(IpSocketTransportStateError::Udp(
1199                IpSocketUdpStateError::MissingField("state"),
1200        )));
1201        "MissingUdpState"
1202    )]
1203    fn ip_socket_state_try_from_error(
1204        fidl: fnet_sockets::IpSocketState,
1205    ) -> Result<IpSocketState, IpSocketStateError> {
1206        IpSocketState::try_from(fidl)
1207    }
1208
1209    #[fuchsia_async::run_singlethreaded(test)]
1210    async fn iterate_ip_diagnostics_iterate_ip_error() {
1211        async fn serve_matcher_error(req: fnet_sockets::DiagnosticsRequest) {
1212            match req {
1213                fnet_sockets::DiagnosticsRequest::IterateIp {
1214                    s: _,
1215                    extensions: _,
1216                    matchers: _,
1217                    responder,
1218                } => responder
1219                    .send(&fnet_sockets::IterateIpResult::InvalidMatcher(
1220                        fnet_sockets::InvalidMatcher { index: 0 },
1221                    ))
1222                    .unwrap(),
1223            };
1224        }
1225
1226        let (diagnostics, diagnostics_server_end) =
1227            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1228
1229        let (mut diagnostics_request_stream, _control_handle) =
1230            diagnostics_server_end.into_stream_and_control_handle();
1231        let server_fut = diagnostics_request_stream
1232            .next()
1233            .then(|req| {
1234                serve_matcher_error(req.expect("Request stream ended unexpectedly").unwrap())
1235            })
1236            .fuse();
1237        let client_fut = iterate_ip::<[IpSocketMatcher; 0], _>(
1238            &diagnostics,
1239            fnet_sockets::Extensions::empty(),
1240            [],
1241        );
1242
1243        pin_mut!(server_fut);
1244        pin_mut!(client_fut);
1245
1246        let ((), resp) = future::join(server_fut, client_fut).await;
1247
1248        assert_matches!(
1249            // Discard the stream because it can't be formatted.
1250            resp.map(|_| ()),
1251            Err(IterateIpError::InvalidMatcher(0))
1252        );
1253    }
1254
1255    #[fuchsia_async::run_singlethreaded(test)]
1256    async fn iterate_ip_next_error() {
1257        async fn serve_matcher(req: fnet_sockets::DiagnosticsRequest) {
1258            match req {
1259                fnet_sockets::DiagnosticsRequest::IterateIp {
1260                    s,
1261                    extensions: _,
1262                    matchers: _,
1263                    responder,
1264                } => {
1265                    s.close_with_epitaph(zx_status::Status::PEER_CLOSED).unwrap();
1266                    responder.send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty)).unwrap()
1267                }
1268            }
1269        }
1270
1271        let (diagnostics, diagnostics_server_end) =
1272            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1273
1274        let (mut diagnostics_request_stream, _control_handle) =
1275            diagnostics_server_end.into_stream_and_control_handle();
1276        let server_fut = diagnostics_request_stream
1277            .next()
1278            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1279            .fuse();
1280
1281        let client_fut = iterate_ip::<[IpSocketMatcher; 0], _>(
1282            &diagnostics,
1283            fnet_sockets::Extensions::empty(),
1284            [],
1285        );
1286
1287        let ((), resp) = future::join(server_fut, client_fut).await;
1288        let stream = resp.unwrap();
1289        pin_mut!(stream);
1290
1291        assert_matches!(
1292            stream.try_next().await,
1293            Err(IpIteratorError::Fidl(fidl::Error::ClientChannelClosed { .. }))
1294        );
1295    }
1296
1297    #[fuchsia_async::run_singlethreaded(test)]
1298    async fn iterate_ip_empty_batch() {
1299        async fn serve_matcher(req: fnet_sockets::DiagnosticsRequest) {
1300            match req {
1301                fnet_sockets::DiagnosticsRequest::IterateIp {
1302                    s,
1303                    extensions: _,
1304                    matchers: _,
1305                    responder,
1306                } => {
1307                    responder
1308                        .send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty))
1309                        .unwrap();
1310
1311                    let (mut stream, _control) = s.into_stream_and_control_handle();
1312                    match stream.next().await.unwrap().unwrap() {
1313                        fidl_fuchsia_net_sockets::IpIteratorRequest::Next { responder } => {
1314                            // Send an empty batch but indicate there's more to come.
1315                            responder.send(&[], true).unwrap();
1316                        }
1317                        fidl_fuchsia_net_sockets::IpIteratorRequest::_UnknownMethod { .. } => {
1318                            unreachable!()
1319                        }
1320                    }
1321                }
1322            }
1323        }
1324
1325        let (diagnostics, diagnostics_server_end) =
1326            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1327
1328        let (mut diagnostics_request_stream, _control_handle) =
1329            diagnostics_server_end.into_stream_and_control_handle();
1330        let server_fut = diagnostics_request_stream
1331            .next()
1332            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1333            .fuse();
1334
1335        let client_fut = async {
1336            let stream = iterate_ip::<[IpSocketMatcher; 0], _>(
1337                &diagnostics,
1338                fnet_sockets::Extensions::empty(),
1339                [],
1340            )
1341            .await
1342            .unwrap();
1343            pin_mut!(stream);
1344            stream.try_next().await
1345        };
1346
1347        let ((), resp) = future::join(server_fut, client_fut).await;
1348        assert_matches!(resp, Err(IpIteratorError::EmptyBatch));
1349    }
1350
1351    #[fuchsia_async::run_singlethreaded(test)]
1352    async fn iterate_ip_success() {
1353        let socket_1 = fnet_sockets::IpSocketState {
1354            family: Some(fnet::IpVersion::V4),
1355            src_addr: Some(fidl_ip!("192.168.1.1")),
1356            dst_addr: Some(fidl_ip!("192.168.1.2")),
1357            cookie: Some(1234),
1358            marks: Some(fnet::Marks {
1359                mark_1: Some(1111),
1360                mark_2: None,
1361                __source_breaking: fidl::marker::SourceBreaking,
1362            }),
1363            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1364                fnet_sockets::IpSocketTcpState {
1365                    src_port: Some(1111),
1366                    dst_port: Some(2222),
1367                    state: Some(fnet_tcp::State::Established),
1368                    tcp_info: None,
1369                    __source_breaking: fidl::marker::SourceBreaking,
1370                },
1371            )),
1372            __source_breaking: fidl::marker::SourceBreaking,
1373        };
1374
1375        let socket_2 = fnet_sockets::IpSocketState {
1376            family: Some(fnet::IpVersion::V4),
1377            src_addr: Some(fidl_ip!("192.168.8.1")),
1378            dst_addr: Some(fidl_ip!("192.168.8.2")),
1379            cookie: Some(9876),
1380            marks: Some(fnet::Marks {
1381                mark_1: None,
1382                mark_2: Some(2222),
1383                __source_breaking: fidl::marker::SourceBreaking,
1384            }),
1385            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1386                fnet_sockets::IpSocketTcpState {
1387                    src_port: Some(3333),
1388                    dst_port: Some(4444),
1389                    state: Some(fnet_tcp::State::TimeWait),
1390                    tcp_info: None,
1391                    __source_breaking: fidl::marker::SourceBreaking,
1392                },
1393            )),
1394            __source_breaking: fidl::marker::SourceBreaking,
1395        };
1396
1397        let socket_3 = fnet_sockets::IpSocketState {
1398            family: Some(fnet::IpVersion::V6),
1399            src_addr: Some(fidl_ip!("2001:db8::1")),
1400            dst_addr: Some(fidl_ip!("2001:db8::2")),
1401            cookie: Some(5678),
1402            marks: Some(fnet::Marks {
1403                mark_1: None,
1404                mark_2: None,
1405                __source_breaking: fidl::marker::SourceBreaking,
1406            }),
1407            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1408                fnet_sockets::IpSocketTcpState {
1409                    src_port: Some(5555),
1410                    dst_port: Some(6666),
1411                    state: Some(fnet_tcp::State::TimeWait),
1412                    tcp_info: None,
1413                    __source_breaking: fidl::marker::SourceBreaking,
1414                },
1415            )),
1416            __source_breaking: fidl::marker::SourceBreaking,
1417        };
1418
1419        let (diagnostics, diagnostics_server_end) =
1420            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1421
1422        let serve_matcher = async |req: fnet_sockets::DiagnosticsRequest| {
1423            let responses = &[vec![socket_1.clone()], vec![socket_2.clone(), socket_3.clone()]];
1424
1425            match req {
1426                fnet_sockets::DiagnosticsRequest::IterateIp {
1427                    s,
1428                    extensions: _,
1429                    matchers: _,
1430                    responder,
1431                } => {
1432                    responder
1433                        .send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty))
1434                        .unwrap();
1435
1436                    let (mut stream, _control) = s.into_stream_and_control_handle();
1437                    for (i, resp) in responses.iter().enumerate() {
1438                        match stream.next().await.unwrap().unwrap() {
1439                            fidl_fuchsia_net_sockets::IpIteratorRequest::Next { responder } => {
1440                                let has_more = i < responses.len() - 1;
1441                                responder.send(&resp, has_more).unwrap();
1442                            }
1443                            fidl_fuchsia_net_sockets::IpIteratorRequest::_UnknownMethod {
1444                                ..
1445                            } => {
1446                                unreachable!()
1447                            }
1448                        }
1449                    }
1450                }
1451            };
1452        };
1453
1454        let (mut diagnostics_request_stream, _control_handle) =
1455            diagnostics_server_end.into_stream_and_control_handle();
1456
1457        let server_fut = diagnostics_request_stream
1458            .next()
1459            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1460            .fuse();
1461
1462        let client_fut = async {
1463            iterate_ip::<[IpSocketMatcher; 0], _>(
1464                &diagnostics,
1465                fnet_sockets::Extensions::empty(),
1466                [],
1467            )
1468            .await
1469            .unwrap()
1470            .try_collect::<Vec<_>>()
1471            .await
1472            .unwrap()
1473        };
1474
1475        let ((), sockets) = future::join(server_fut, client_fut).await;
1476        assert_eq!(
1477            sockets,
1478            vec![
1479                socket_1.clone().try_into().unwrap(),
1480                socket_2.clone().try_into().unwrap(),
1481                socket_3.clone().try_into().unwrap()
1482            ]
1483        );
1484    }
1485}