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