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    /// The send MSS for this endpoint.
405    pub tcpi_snd_mss: Option<u32>,
406    /// The receive MSS for this endpoint.
407    pub tcpi_rcv_mss: Option<u32>,
408}
409
410/// Error type for [`TcpInfo`] conversion.
411#[derive(Debug, PartialEq, Error)]
412pub enum TcpInfoError {
413    /// Missing a required field.
414    #[error("missing field: {0}")]
415    MissingField(&'static str),
416}
417
418impl TryFrom<fnet_tcp::Info> for TcpInfo {
419    type Error = TcpInfoError;
420
421    fn try_from(value: fnet_tcp::Info) -> Result<Self, Self::Error> {
422        let fnet_tcp::Info {
423            state,
424            ca_state,
425            rto_usec,
426            tcpi_last_data_sent_msec,
427            tcpi_last_ack_recv_msec,
428            rtt_usec,
429            rtt_var_usec,
430            snd_ssthresh,
431            snd_cwnd,
432            tcpi_total_retrans,
433            tcpi_segs_out,
434            tcpi_segs_in,
435            reorder_seen,
436            tcpi_snd_mss,
437            tcpi_rcv_mss,
438            __source_breaking,
439        } = value;
440
441        Ok(TcpInfo {
442            state: state.ok_or(TcpInfoError::MissingField("state"))?,
443            ca_state: ca_state.ok_or(TcpInfoError::MissingField("ca_state"))?,
444            rto_usec,
445            tcpi_last_data_sent_msec,
446            tcpi_last_ack_recv_msec,
447            rtt_usec,
448            rtt_var_usec,
449            snd_ssthresh: snd_ssthresh.ok_or(TcpInfoError::MissingField("snd_ssthresh"))?,
450            snd_cwnd: snd_cwnd.ok_or(TcpInfoError::MissingField("snd_cwnd"))?,
451            tcpi_total_retrans: tcpi_total_retrans
452                .ok_or(TcpInfoError::MissingField("tcpi_total_retrans"))?,
453            tcpi_segs_out: tcpi_segs_out.ok_or(TcpInfoError::MissingField("tcpi_segs_out"))?,
454            tcpi_segs_in: tcpi_segs_in.ok_or(TcpInfoError::MissingField("tcpi_segs_in"))?,
455            reorder_seen: reorder_seen.ok_or(TcpInfoError::MissingField("reorder_seen"))?,
456            tcpi_snd_mss,
457            tcpi_rcv_mss,
458        })
459    }
460}
461
462impl From<TcpInfo> for fnet_tcp::Info {
463    fn from(info: TcpInfo) -> Self {
464        let TcpInfo {
465            state,
466            ca_state,
467            rto_usec,
468            tcpi_last_data_sent_msec,
469            tcpi_last_ack_recv_msec,
470            rtt_usec,
471            rtt_var_usec,
472            snd_ssthresh,
473            snd_cwnd,
474            tcpi_total_retrans,
475            tcpi_segs_out,
476            tcpi_segs_in,
477            reorder_seen,
478            tcpi_snd_mss,
479            tcpi_rcv_mss,
480        } = info;
481        fnet_tcp::Info {
482            state: Some(state),
483            ca_state: Some(ca_state),
484            rto_usec: rto_usec,
485            tcpi_last_data_sent_msec,
486            tcpi_last_ack_recv_msec,
487            rtt_usec: rtt_usec,
488            rtt_var_usec: rtt_var_usec,
489            snd_ssthresh: Some(snd_ssthresh),
490            snd_cwnd: Some(snd_cwnd),
491            tcpi_total_retrans: Some(tcpi_total_retrans),
492            tcpi_segs_out: Some(tcpi_segs_out),
493            tcpi_segs_in: Some(tcpi_segs_in),
494            reorder_seen: Some(reorder_seen),
495            tcpi_snd_mss,
496            tcpi_rcv_mss,
497            __source_breaking: fidl::marker::SourceBreaking,
498        }
499    }
500}
501
502/// Extension type for [`fnet_sockets::IpSocketUdpState`].
503#[derive(Debug, PartialEq, Eq, Clone)]
504pub struct IpSocketUdpState {
505    /// The source port of the socket.
506    pub src_port: Option<u16>,
507    /// The destination port of the socket.
508    pub dst_port: Option<u16>,
509    /// The UDP pseudo-state machine state for the socket.
510    pub state: fnet_udp::State,
511}
512
513/// Error type for [`IpSocketUdpState`] conversion.
514#[derive(Debug, PartialEq, Error)]
515pub enum IpSocketUdpStateError {
516    /// Missing a required field.
517    #[error("missing field: {0}")]
518    MissingField(&'static str),
519}
520
521impl TryFrom<fnet_sockets::IpSocketUdpState> for IpSocketUdpState {
522    type Error = IpSocketUdpStateError;
523
524    fn try_from(value: fnet_sockets::IpSocketUdpState) -> Result<Self, Self::Error> {
525        let fnet_sockets::IpSocketUdpState { src_port, dst_port, state, __source_breaking } = value;
526
527        let state = state.ok_or(IpSocketUdpStateError::MissingField("state"))?;
528
529        Ok(IpSocketUdpState { src_port, dst_port, state })
530    }
531}
532
533impl From<IpSocketUdpState> for fnet_sockets::IpSocketUdpState {
534    fn from(state: IpSocketUdpState) -> Self {
535        let IpSocketUdpState { src_port, dst_port, state } = state;
536        fnet_sockets::IpSocketUdpState {
537            src_port,
538            dst_port,
539            state: Some(state),
540            __source_breaking: fidl::marker::SourceBreaking,
541        }
542    }
543}
544
545/// Errors returned by [`iterate_ip`]
546#[derive(Debug, Error)]
547pub enum IterateIpError {
548    /// The specified matcher was the first invalid one.
549    #[error("invalid matcher at position {0}")]
550    InvalidMatcher(usize),
551    /// An unknown response was received on the call to `Diagnostics.IterateIp`
552    #[error("unknown ordinal on Diagnostics.IterateIp call: {0}")]
553    UnknownOrdinal(u64),
554    /// A low-level FIDL error was encountered on the call to
555    /// `Diagnostics.IterateIp`.
556    #[error("fidl error during Diagnostics.IterateIp call: {0}")]
557    Fidl(fidl::Error),
558}
559
560impl From<fidl::Error> for IterateIpError {
561    fn from(e: fidl::Error) -> Self {
562        IterateIpError::Fidl(e)
563    }
564}
565
566/// Errors returned by the stream returned from [`iterate_ip`].
567#[derive(Debug, Error)]
568pub enum IpIteratorError {
569    /// The netstack returned an empty batch of sockets
570    #[error("received empty batch of sockets")]
571    EmptyBatch,
572    /// A low-level FIDL error was encountered on the call to
573    /// `Diagnostics.IterateIp`.
574    #[error("fidl error during Diagnostics.IterateIp call: {0}")]
575    Fidl(fidl::Error),
576    /// An error was encountered while converting a socket state.
577    #[error("error converting socket state: {0}")]
578    Conversion(IpSocketStateError),
579}
580
581impl From<fidl::Error> for IpIteratorError {
582    fn from(e: fidl::Error) -> Self {
583        IpIteratorError::Fidl(e)
584    }
585}
586
587/// Send a request to `Diagnostics.IterateIp` and drive the resulting
588/// `IpIterator`.
589///
590/// `IpIterator` returns a series of batches of sockets matching the query, the
591/// returned stream flattens those batches into individual sockets. If an error
592/// is encuontered during iteration, it is returned and iteration halts.
593//
594// TODO(https://github.com/rust-lang/rust/issues/130043): Remove types from the
595// precise capturing clause on the stream.
596pub async fn iterate_ip<M, I>(
597    diagnostics: &fnet_sockets::DiagnosticsProxy,
598    extensions: fnet_sockets::Extensions,
599    matchers: M,
600) -> Result<impl Stream<Item = Result<IpSocketState, IpIteratorError>> + use<M, I>, IterateIpError>
601where
602    M: IntoIterator<Item = I>,
603    I: Into<fnet_sockets::IpSocketMatcher>,
604{
605    let (proxy, server_end) = fidl::endpoints::create_proxy::<fnet_sockets::IpIteratorMarker>();
606    match diagnostics
607        .iterate_ip(
608            server_end,
609            extensions,
610            &matchers.into_iter().map(Into::into).collect::<Vec<_>>()[..],
611        )
612        .await?
613    {
614        fnet_sockets::IterateIpResult::Ok(_empty) => Ok(()),
615        fnet_sockets::IterateIpResult::InvalidMatcher(fnet_sockets::InvalidMatcher { index }) => {
616            Err(IterateIpError::InvalidMatcher(index as usize))
617        }
618        fnet_sockets::IterateIpResult::__SourceBreaking { unknown_ordinal } => {
619            Err(IterateIpError::UnknownOrdinal(unknown_ordinal))
620        }
621    }?;
622
623    Ok(futures::stream::try_unfold((proxy, true), |(proxy, has_more)| async move {
624        if !has_more {
625            return Ok(None);
626        }
627
628        let (batch, has_more) = proxy.next().await?;
629        if batch.is_empty() && has_more {
630            Err(IpIteratorError::EmptyBatch)
631        } else {
632            Ok(Some((
633                futures::stream::iter(
634                    batch
635                        .into_iter()
636                        .map(|s| s.try_into().map_err(|e| IpIteratorError::Conversion(e))),
637                ),
638                (proxy, has_more),
639            )))
640        }
641    })
642    .try_flatten())
643}
644
645/// Errors returned by [`disconnect_ip`]
646#[derive(Debug, Error)]
647pub enum DisconnectIpError {
648    /// The specified matcher was the first invalid one.
649    #[error("invalid matcher at position {0}")]
650    InvalidMatcher(usize),
651    /// Specified matchers would a priori match all sockets.
652    #[error("matchers were unconstrained")]
653    UnconstrainedMatchers,
654    /// An unknown response was received on the call to `Control.DisconnectIp`
655    #[error("unknown ordinal on Control.DisconnectIp call: {0}")]
656    UnknownOrdinal(u64),
657    /// A low-level FIDL error was encountered on the call to
658    /// `Control.DisconnectIp`.
659    #[error("fidl error during Control.DisconnectIp call: {0}")]
660    Fidl(fidl::Error),
661}
662
663/// Send a request to `Control.DisconnectIp` with the provided matchers.
664pub async fn disconnect_ip<M, I>(
665    control: &fnet_sockets::ControlProxy,
666    matchers: M,
667) -> Result<usize, DisconnectIpError>
668where
669    M: IntoIterator<Item = I>,
670    I: Into<fnet_sockets::IpSocketMatcher>,
671{
672    match control
673        .disconnect_ip(&fnet_sockets::ControlDisconnectIpRequest {
674            matchers: Some(matchers.into_iter().map(Into::into).collect()),
675            __source_breaking: fidl::marker::SourceBreaking,
676        })
677        .await
678    {
679        Ok(r) => match r {
680            fnet_sockets::DisconnectIpResult::Ok(fnet_sockets::DisconnectIpResponse {
681                disconnected,
682            }) => {
683                // Unwrap is safe because usize is always at least u32.
684                Ok(disconnected.try_into().unwrap())
685            }
686            fnet_sockets::DisconnectIpResult::InvalidMatcher(fnet_sockets::InvalidMatcher {
687                index,
688            }) => {
689                // Unwrap is safe because usize is always at least u32.
690                Err(DisconnectIpError::InvalidMatcher(index.try_into().unwrap()))
691            }
692            fnet_sockets::DisconnectIpResult::UnconstrainedMatchers(fnet_sockets::Empty) => {
693                Err(DisconnectIpError::UnconstrainedMatchers)
694            }
695            fnet_sockets::DisconnectIpResult::__SourceBreaking { unknown_ordinal } => {
696                Err(DisconnectIpError::UnknownOrdinal(unknown_ordinal))
697            }
698        },
699        Err(e) => Err(DisconnectIpError::Fidl(e)),
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706
707    use std::num::NonZeroU64;
708
709    use assert_matches::assert_matches;
710    use fidl_fuchsia_net as fnet;
711    use fidl_fuchsia_net_tcp as fnet_tcp;
712    use futures::{FutureExt as _, StreamExt as _, future, pin_mut};
713    use net_declare::{fidl_ip, fidl_subnet, net_ip_v4, net_ip_v6};
714    use test_case::test_case;
715
716    #[test_case(
717        fnet_sockets::IpSocketMatcher::Family(fnet::IpVersion::V4),
718        IpSocketMatcher::Family(ip::IpVersion::V4);
719        "FamilyIpv4"
720    )]
721    #[test_case(
722        fnet_sockets::IpSocketMatcher::Family(fnet::IpVersion::V6),
723        IpSocketMatcher::Family(ip::IpVersion::V6);
724        "FamilyIpv6"
725    )]
726    #[test_case(
727        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Bound(
728            fnet_matchers::Address {
729                matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("192.0.2.0/24")),
730                invert: true,
731            }
732        )),
733        IpSocketMatcher::SrcAddr(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!("192.0.2.0/24")).unwrap()
737                ),
738                invert: true,
739            }
740        ));
741        "SrcAddr"
742    )]
743    #[test_case(
744        fnet_sockets::IpSocketMatcher::DstAddr(fnet_matchers::BoundAddress::Bound(
745            fnet_matchers::Address {
746                matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("2001:db8::/32")),
747                invert: false,
748            }
749        )),
750        IpSocketMatcher::DstAddr(fnet_matchers_ext::BoundAddress::Bound(
751            fnet_matchers_ext::Address {
752                matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
753                    fnet_matchers_ext::Subnet::try_from(fidl_subnet!("2001:db8::/32")).unwrap()
754                ),
755                invert: false,
756            }
757        ));
758        "DstAddr"
759    )]
760    #[test_case(
761        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
762            fnet_matchers::TcpSocket::Empty(fnet_matchers::Empty)
763        )),
764        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
765            fnet_matchers_ext::TcpSocket::Empty
766        ));
767        "ProtoTcp"
768    )]
769    #[test_case(
770        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
771            fnet_matchers::UdpSocket::Empty(fnet_matchers::Empty)
772        )),
773        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
774            fnet_matchers_ext::UdpSocket::Empty
775        ));
776        "ProtoUdp"
777    )]
778    #[test_case(
779        fnet_sockets::IpSocketMatcher::BoundInterface(fnet_matchers::BoundInterface::Unbound(
780            fnet_matchers::Empty
781        )),
782        IpSocketMatcher::BoundInterface(fnet_matchers_ext::BoundInterface::Unbound);
783        "BoundInterfaceUnbound"
784    )]
785    #[test_case(
786        fnet_sockets::IpSocketMatcher::BoundInterface(fnet_matchers::BoundInterface::Bound(
787            fnet_matchers::Interface::Id(1)
788        )),
789        IpSocketMatcher::BoundInterface(fnet_matchers_ext::BoundInterface::Bound(
790            fnet_matchers_ext::Interface::Id(NonZeroU64::new(1).unwrap())
791        ));
792        "BoundInterfaceBound"
793    )]
794    #[test_case(
795        fnet_sockets::IpSocketMatcher::Cookie(fnet_matchers::SocketCookie {
796            cookie: 12345,
797            invert: false,
798        }),
799        IpSocketMatcher::Cookie(fnet_matchers::SocketCookie {
800            cookie: 12345,
801            invert: false,
802        });
803        "Cookie"
804    )]
805    #[test_case(
806        fnet_sockets::IpSocketMatcher::Mark(fnet_matchers::MarkInDomain {
807            domain: fnet::MarkDomain::Mark1,
808            mark: fnet_matchers::Mark::Unmarked(fnet_matchers::Unmarked),
809        }),
810        IpSocketMatcher::Mark(fnet_matchers_ext::MarkInDomain {
811            domain: fnet::MarkDomain::Mark1,
812            mark: fnet_matchers_ext::Mark::Unmarked,
813        });
814        "Mark"
815    )]
816    #[test_case(
817        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty)),
818        IpSocketMatcher::SrcAddr(fnet_matchers_ext::BoundAddress::Unbound);
819        "SrcAddrUnbound"
820    )]
821    #[test_case(
822        fnet_sockets::IpSocketMatcher::DstAddr(fnet_matchers::BoundAddress::Unbound(fnet_matchers::Empty)),
823        IpSocketMatcher::DstAddr(fnet_matchers_ext::BoundAddress::Unbound);
824        "DstAddrUnbound"
825    )]
826    #[test_case(
827        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
828            fnet_matchers::TcpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
829        )),
830        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
831            fnet_matchers_ext::TcpSocket::SrcPort(fnet_matchers_ext::BoundPort::Unbound)
832        ));
833        "ProtoTcpSrcPortUnbound"
834    )]
835    #[test_case(
836        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Tcp(
837            fnet_matchers::TcpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
838        )),
839        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Tcp(
840            fnet_matchers_ext::TcpSocket::DstPort(fnet_matchers_ext::BoundPort::Unbound)
841        ));
842        "ProtoTcpDstPortUnbound"
843    )]
844    #[test_case(
845        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
846            fnet_matchers::UdpSocket::SrcPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
847        )),
848        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
849            fnet_matchers_ext::UdpSocket::SrcPort(fnet_matchers_ext::BoundPort::Unbound)
850        ));
851        "ProtoUdpSrcPortUnbound"
852    )]
853    #[test_case(
854        fnet_sockets::IpSocketMatcher::Proto(fnet_matchers::SocketTransportProtocol::Udp(
855            fnet_matchers::UdpSocket::DstPort(fnet_matchers::BoundPort::Unbound(fnet_matchers::Empty))
856        )),
857        IpSocketMatcher::Proto(fnet_matchers_ext::SocketTransportProtocol::Udp(
858            fnet_matchers_ext::UdpSocket::DstPort(fnet_matchers_ext::BoundPort::Unbound)
859        ));
860        "ProtoUdpDstPortUnbound"
861    )]
862    #[test_case(
863        fnet_tcp::Info {
864            state: Some(fnet_tcp::State::Established),
865            ca_state: Some(fnet_tcp::CongestionControlState::Open),
866            rto_usec: Some(1),
867            tcpi_last_data_sent_msec: Some(2),
868            tcpi_last_ack_recv_msec: Some(3),
869            rtt_usec: Some(4),
870            rtt_var_usec: Some(5),
871            snd_ssthresh: Some(6),
872            snd_cwnd: Some(7),
873            tcpi_total_retrans: Some(8),
874            tcpi_segs_out: Some(9),
875            tcpi_segs_in: Some(10),
876            reorder_seen: Some(true),
877            tcpi_snd_mss: Some(11),
878            tcpi_rcv_mss: Some(12),
879            __source_breaking: fidl::marker::SourceBreaking,
880        },
881        TcpInfo {
882            state: fnet_tcp::State::Established,
883            ca_state: fnet_tcp::CongestionControlState::Open,
884            rto_usec: Some(1),
885            tcpi_last_data_sent_msec: Some(2),
886            tcpi_last_ack_recv_msec: Some(3),
887            rtt_usec: Some(4),
888            rtt_var_usec: Some(5),
889            snd_ssthresh: 6,
890            snd_cwnd: 7,
891            tcpi_total_retrans: 8,
892            tcpi_segs_out: 9,
893            tcpi_segs_in: 10,
894            reorder_seen: true,
895            tcpi_snd_mss: Some(11),
896            tcpi_rcv_mss: Some(12),
897        };
898        "TcpInfo"
899    )]
900    #[test_case(
901        fnet_sockets::IpSocketState {
902            family: Some(fnet::IpVersion::V4),
903            src_addr: Some(fidl_ip!("192.168.1.1")),
904            dst_addr: Some(fidl_ip!("192.168.1.2")),
905            cookie: Some(1234),
906            marks: Some(fnet::Marks {
907                mark_1: Some(1111),
908                mark_2: None,
909                __source_breaking: fidl::marker::SourceBreaking,
910            }),
911            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
912                fnet_sockets::IpSocketTcpState {
913                    src_port: Some(1111),
914                    dst_port: Some(2222),
915                    state: Some(fnet_tcp::State::Established),
916                    tcp_info: None,
917                    __source_breaking: fidl::marker::SourceBreaking,
918                },
919            )),
920            __source_breaking: fidl::marker::SourceBreaking,
921        },
922        IpSocketState::V4(IpSocketStateSpecific {
923            src_addr: Some(net_ip_v4!("192.168.1.1")),
924            dst_addr: Some(net_ip_v4!("192.168.1.2")),
925            cookie: 1234,
926            marks: fnet::Marks {
927                mark_1: Some(1111),
928                mark_2: None,
929                __source_breaking: fidl::marker::SourceBreaking,
930            }.into(),
931            transport: IpSocketTransportState::Tcp(IpSocketTcpState {
932                src_port: Some(1111),
933                dst_port: Some(2222),
934                state: fnet_tcp::State::Established,
935                tcp_info: None,
936            }),
937        });
938        "IpSocketStateV4"
939    )]
940    #[test_case(
941        fnet_sockets::IpSocketState {
942            family: Some(fnet::IpVersion::V6),
943            src_addr: Some(fidl_ip!("2001:db8::1")),
944            dst_addr: Some(fidl_ip!("2001:db8::2")),
945            cookie: Some(1234),
946            marks: Some(fnet::Marks {
947                mark_1: Some(1111),
948                mark_2: None,
949                __source_breaking: fidl::marker::SourceBreaking,
950            }),
951            transport: Some(fnet_sockets::IpSocketTransportState::Udp(
952                fnet_sockets::IpSocketUdpState {
953                    src_port: Some(3333),
954                    dst_port: Some(4444),
955                    state: Some(fnet_udp::State::Connected),
956                    __source_breaking: fidl::marker::SourceBreaking,
957                },
958            )),
959            __source_breaking: fidl::marker::SourceBreaking,
960        },
961        IpSocketState::V6(IpSocketStateSpecific {
962            src_addr: Some(net_ip_v6!("2001:db8::1")),
963            dst_addr: Some(net_ip_v6!("2001:db8::2")),
964            cookie: 1234,
965            marks: fnet::Marks {
966                mark_1: Some(1111),
967                mark_2: None,
968                __source_breaking: fidl::marker::SourceBreaking,
969            }.into(),
970            transport: IpSocketTransportState::Udp(IpSocketUdpState {
971                src_port: Some(3333),
972                dst_port: Some(4444),
973                state: fnet_udp::State::Connected,
974            }),
975        });
976        "IpSocketStateV6"
977    )]
978    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
979    where
980        E: TryFrom<F> + Clone + std::fmt::Debug + PartialEq,
981        <E as TryFrom<F>>::Error: std::fmt::Debug + PartialEq,
982        F: From<E> + Clone + std::fmt::Debug + PartialEq,
983    {
984        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
985        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type);
986    }
987
988    #[test_case(
989        fnet_sockets::IpSocketMatcher::__SourceBreaking { unknown_ordinal: 100 } =>
990            Err(IpSocketMatcherError::UnknownUnionVariant(100));
991        "UnknownUnionVariant"
992    )]
993    #[test_case(
994        fnet_sockets::IpSocketMatcher::SrcAddr(fnet_matchers::BoundAddress::Bound(
995            fnet_matchers::Address {
996                matcher: fnet_matchers::AddressMatcherType::__SourceBreaking { unknown_ordinal: 100 },
997                invert: false,
998            }
999        )) => Err(IpSocketMatcherError::Address(fnet_matchers_ext::BoundAddressError::Address(
1000            fnet_matchers_ext::AddressError::AddressMatcherType(
1001                fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant
1002            )
1003        )));
1004        "AddressError"
1005    )]
1006    #[test_case(
1007        fnet_sockets::IpSocketMatcher::Proto(
1008            fnet_matchers::SocketTransportProtocol::__SourceBreaking { unknown_ordinal: 100 }
1009        ) => Err(IpSocketMatcherError::TransportProtocol(
1010            fnet_matchers_ext::SocketTransportProtocolError::UnknownUnionVariant(100)
1011        ));
1012        "TransportProtocolError"
1013    )]
1014    #[test_case(
1015        fnet_sockets::IpSocketMatcher::BoundInterface(
1016            fnet_matchers::BoundInterface::__SourceBreaking { unknown_ordinal: 100 }
1017        ) => Err(IpSocketMatcherError::BoundInterface(
1018            fnet_matchers_ext::BoundInterfaceError::UnknownUnionVariant(100)
1019        ));
1020        "BoundInterfaceError"
1021    )]
1022    #[test_case(
1023        fnet_sockets::IpSocketMatcher::Mark(fnet_matchers::MarkInDomain {
1024            domain: fnet::MarkDomain::Mark1,
1025            mark: fnet_matchers::Mark::__SourceBreaking { unknown_ordinal: 100 },
1026        }) => Err(IpSocketMatcherError::Mark(
1027            fnet_matchers_ext::MarkInDomainError::Mark(
1028                fnet_matchers_ext::MarkError::UnknownUnionVariant(100)
1029            )
1030        ));
1031        "MarkError"
1032    )]
1033    fn ip_socket_matcher_try_from_error(
1034        fidl: fnet_sockets::IpSocketMatcher,
1035    ) -> Result<IpSocketMatcher, IpSocketMatcherError> {
1036        IpSocketMatcher::try_from(fidl)
1037    }
1038
1039    #[test_case(
1040        fnet_sockets::IpSocketState {
1041            family: None,
1042            src_addr: Some(fidl_ip!("192.168.1.1")),
1043            dst_addr: Some(fidl_ip!("192.168.1.2")),
1044            cookie: Some(1234),
1045            marks: Some(fnet::Marks {
1046                mark_1: Some(1111),
1047                mark_2: None,
1048                __source_breaking: fidl::marker::SourceBreaking,
1049            }),
1050            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1051                fnet_sockets::IpSocketTcpState {
1052                    src_port: Some(1111),
1053                    dst_port: Some(2222),
1054                    state: Some(fnet_tcp::State::Established),
1055                    tcp_info: None,
1056                    __source_breaking: fidl::marker::SourceBreaking,
1057                },
1058            )),
1059            __source_breaking: fidl::marker::SourceBreaking,
1060        } => Err(IpSocketStateError::MissingField("family"));
1061        "MissingFamily"
1062    )]
1063    #[test_case(
1064        fnet_sockets::IpSocketState {
1065            family: Some(fnet::IpVersion::V4),
1066            src_addr: Some(fidl_ip!("192.168.1.1")),
1067            dst_addr: Some(fidl_ip!("192.168.1.2")),
1068            cookie: None,
1069            marks: Some(fnet::Marks {
1070                mark_1: Some(1111),
1071                mark_2: None,
1072                __source_breaking: fidl::marker::SourceBreaking,
1073            }),
1074            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1075                fnet_sockets::IpSocketTcpState {
1076                    src_port: Some(1111),
1077                    dst_port: Some(2222),
1078                    state: Some(fnet_tcp::State::Established),
1079                    tcp_info: None,
1080                    __source_breaking: fidl::marker::SourceBreaking,
1081                },
1082            )),
1083            __source_breaking: fidl::marker::SourceBreaking,
1084        } => Err(IpSocketStateError::MissingField("cookie"));
1085        "MissingCookie"
1086    )]
1087    #[test_case(
1088        fnet_sockets::IpSocketState {
1089            family: Some(fnet::IpVersion::V4),
1090            src_addr: Some(fidl_ip!("192.168.1.1")),
1091            dst_addr: Some(fidl_ip!("192.168.1.2")),
1092            cookie: Some(1234),
1093            marks: None,
1094            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1095                fnet_sockets::IpSocketTcpState {
1096                    src_port: Some(1111),
1097                    dst_port: Some(2222),
1098                    state: Some(fnet_tcp::State::Established),
1099                    tcp_info: None,
1100                    __source_breaking: fidl::marker::SourceBreaking,
1101                },
1102            )),
1103            __source_breaking: fidl::marker::SourceBreaking,
1104        } => Err(IpSocketStateError::MissingField("marks"));
1105        "MissingMarks"
1106    )]
1107    #[test_case(
1108        fnet_sockets::IpSocketState {
1109            family: Some(fnet::IpVersion::V4),
1110            src_addr: Some(fidl_ip!("192.168.1.1")),
1111            dst_addr: Some(fidl_ip!("192.168.1.2")),
1112            cookie: Some(1234),
1113            marks: Some(fnet::Marks {
1114                mark_1: Some(1111),
1115                mark_2: None,
1116                __source_breaking: fidl::marker::SourceBreaking,
1117            }),
1118            transport: None,
1119            __source_breaking: fidl::marker::SourceBreaking,
1120        } => Err(IpSocketStateError::MissingField("transport"));
1121        "MissingTransport"
1122    )]
1123    #[test_case(
1124        fnet_sockets::IpSocketState {
1125            family: Some(fnet::IpVersion::V4),
1126            src_addr: Some(fidl_ip!("192.168.1.1")),
1127            dst_addr: Some(fidl_ip!("2001:db8::2")),
1128            cookie: Some(1234),
1129            marks: Some(fnet::Marks {
1130                mark_1: Some(1111),
1131                mark_2: None,
1132                __source_breaking: fidl::marker::SourceBreaking,
1133            }),
1134            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1135                fnet_sockets::IpSocketTcpState {
1136                    src_port: Some(1111),
1137                    dst_port: Some(2222),
1138                    state: Some(fnet_tcp::State::Established),
1139                    tcp_info: None,
1140                    __source_breaking: fidl::marker::SourceBreaking,
1141                },
1142            )),
1143            __source_breaking: fidl::marker::SourceBreaking,
1144        } => Err(IpSocketStateError::VersionMismatch);
1145        "VersionMismatchV4"
1146    )]
1147    #[test_case(
1148        fnet_sockets::IpSocketState {
1149            family: Some(fnet::IpVersion::V6),
1150            src_addr: Some(fidl_ip!("192.168.1.1")),
1151            dst_addr: Some(fidl_ip!("2001:db8::2")),
1152            cookie: Some(1234),
1153            marks: Some(fnet::Marks {
1154                mark_1: Some(1111),
1155                mark_2: None,
1156                __source_breaking: fidl::marker::SourceBreaking,
1157            }),
1158            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1159                fnet_sockets::IpSocketTcpState {
1160                    src_port: Some(1111),
1161                    dst_port: Some(2222),
1162                    state: Some(fnet_tcp::State::Established),
1163                    tcp_info: None,
1164                    __source_breaking: fidl::marker::SourceBreaking,
1165                },
1166            )),
1167            __source_breaking: fidl::marker::SourceBreaking,
1168        } => Err(IpSocketStateError::VersionMismatch);
1169        "VersionMismatchV6"
1170    )]
1171    #[test_case(
1172        fnet_sockets::IpSocketState {
1173            family: Some(fnet::IpVersion::V4),
1174            src_addr: Some(fidl_ip!("192.168.1.1")),
1175            dst_addr: Some(fidl_ip!("192.168.1.2")),
1176            cookie: Some(1234),
1177            marks: Some(fnet::Marks {
1178                mark_1: Some(1111),
1179                mark_2: None,
1180                __source_breaking: fidl::marker::SourceBreaking,
1181            }),
1182            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1183                fnet_sockets::IpSocketTcpState {
1184                    src_port: Some(1111),
1185                    dst_port: Some(2222),
1186                    state: None,
1187                    tcp_info: None,
1188                    __source_breaking: fidl::marker::SourceBreaking,
1189                },
1190            )),
1191            __source_breaking: fidl::marker::SourceBreaking,
1192        } => Err(IpSocketStateError::Transport(IpSocketTransportStateError::Tcp(
1193                IpSocketTcpStateError::MissingField("state"),
1194        )));
1195        "MissingTcpState"
1196    )]
1197    #[test_case(
1198        fnet_sockets::IpSocketState {
1199            family: Some(fnet::IpVersion::V6),
1200            src_addr: Some(fidl_ip!("2001:db8::1")),
1201            dst_addr: Some(fidl_ip!("2001:db8::2")),
1202            cookie: Some(1234),
1203            marks: Some(fnet::Marks {
1204                mark_1: Some(1111),
1205                mark_2: None,
1206                __source_breaking: fidl::marker::SourceBreaking,
1207            }),
1208            transport: Some(fnet_sockets::IpSocketTransportState::Udp(
1209                fnet_sockets::IpSocketUdpState {
1210                    src_port: Some(3333),
1211                    dst_port: Some(4444),
1212                    state: None,
1213                    __source_breaking: fidl::marker::SourceBreaking,
1214                },
1215            )),
1216            __source_breaking: fidl::marker::SourceBreaking,
1217        } => Err(IpSocketStateError::Transport(IpSocketTransportStateError::Udp(
1218                IpSocketUdpStateError::MissingField("state"),
1219        )));
1220        "MissingUdpState"
1221    )]
1222    fn ip_socket_state_try_from_error(
1223        fidl: fnet_sockets::IpSocketState,
1224    ) -> Result<IpSocketState, IpSocketStateError> {
1225        IpSocketState::try_from(fidl)
1226    }
1227
1228    #[fuchsia_async::run_singlethreaded(test)]
1229    async fn iterate_ip_diagnostics_iterate_ip_error() {
1230        async fn serve_matcher_error(req: fnet_sockets::DiagnosticsRequest) {
1231            match req {
1232                fnet_sockets::DiagnosticsRequest::IterateIp {
1233                    s: _,
1234                    extensions: _,
1235                    matchers: _,
1236                    responder,
1237                } => responder
1238                    .send(&fnet_sockets::IterateIpResult::InvalidMatcher(
1239                        fnet_sockets::InvalidMatcher { index: 0 },
1240                    ))
1241                    .unwrap(),
1242            };
1243        }
1244
1245        let (diagnostics, diagnostics_server_end) =
1246            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1247
1248        let (mut diagnostics_request_stream, _control_handle) =
1249            diagnostics_server_end.into_stream_and_control_handle();
1250        let server_fut = diagnostics_request_stream
1251            .next()
1252            .then(|req| {
1253                serve_matcher_error(req.expect("Request stream ended unexpectedly").unwrap())
1254            })
1255            .fuse();
1256        let client_fut = iterate_ip::<[IpSocketMatcher; 0], _>(
1257            &diagnostics,
1258            fnet_sockets::Extensions::empty(),
1259            [],
1260        );
1261
1262        pin_mut!(server_fut);
1263        pin_mut!(client_fut);
1264
1265        let ((), resp) = future::join(server_fut, client_fut).await;
1266
1267        assert_matches!(
1268            // Discard the stream because it can't be formatted.
1269            resp.map(|_| ()),
1270            Err(IterateIpError::InvalidMatcher(0))
1271        );
1272    }
1273
1274    #[fuchsia_async::run_singlethreaded(test)]
1275    async fn iterate_ip_next_error() {
1276        async fn serve_matcher(req: fnet_sockets::DiagnosticsRequest) {
1277            match req {
1278                fnet_sockets::DiagnosticsRequest::IterateIp {
1279                    s,
1280                    extensions: _,
1281                    matchers: _,
1282                    responder,
1283                } => {
1284                    s.close_with_epitaph(zx_status::Status::PEER_CLOSED).unwrap();
1285                    responder.send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty)).unwrap()
1286                }
1287            }
1288        }
1289
1290        let (diagnostics, diagnostics_server_end) =
1291            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1292
1293        let (mut diagnostics_request_stream, _control_handle) =
1294            diagnostics_server_end.into_stream_and_control_handle();
1295        let server_fut = diagnostics_request_stream
1296            .next()
1297            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1298            .fuse();
1299
1300        let client_fut = iterate_ip::<[IpSocketMatcher; 0], _>(
1301            &diagnostics,
1302            fnet_sockets::Extensions::empty(),
1303            [],
1304        );
1305
1306        let ((), resp) = future::join(server_fut, client_fut).await;
1307        let stream = resp.unwrap();
1308        pin_mut!(stream);
1309
1310        assert_matches!(
1311            stream.try_next().await,
1312            Err(IpIteratorError::Fidl(fidl::Error::ClientChannelClosed { .. }))
1313        );
1314    }
1315
1316    #[fuchsia_async::run_singlethreaded(test)]
1317    async fn iterate_ip_empty_batch() {
1318        async fn serve_matcher(req: fnet_sockets::DiagnosticsRequest) {
1319            match req {
1320                fnet_sockets::DiagnosticsRequest::IterateIp {
1321                    s,
1322                    extensions: _,
1323                    matchers: _,
1324                    responder,
1325                } => {
1326                    responder
1327                        .send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty))
1328                        .unwrap();
1329
1330                    let (mut stream, _control) = s.into_stream_and_control_handle();
1331                    match stream.next().await.unwrap().unwrap() {
1332                        fidl_fuchsia_net_sockets::IpIteratorRequest::Next { responder } => {
1333                            // Send an empty batch but indicate there's more to come.
1334                            responder.send(&[], true).unwrap();
1335                        }
1336                        fidl_fuchsia_net_sockets::IpIteratorRequest::_UnknownMethod { .. } => {
1337                            unreachable!()
1338                        }
1339                    }
1340                }
1341            }
1342        }
1343
1344        let (diagnostics, diagnostics_server_end) =
1345            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1346
1347        let (mut diagnostics_request_stream, _control_handle) =
1348            diagnostics_server_end.into_stream_and_control_handle();
1349        let server_fut = diagnostics_request_stream
1350            .next()
1351            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1352            .fuse();
1353
1354        let client_fut = async {
1355            let stream = iterate_ip::<[IpSocketMatcher; 0], _>(
1356                &diagnostics,
1357                fnet_sockets::Extensions::empty(),
1358                [],
1359            )
1360            .await
1361            .unwrap();
1362            pin_mut!(stream);
1363            stream.try_next().await
1364        };
1365
1366        let ((), resp) = future::join(server_fut, client_fut).await;
1367        assert_matches!(resp, Err(IpIteratorError::EmptyBatch));
1368    }
1369
1370    #[fuchsia_async::run_singlethreaded(test)]
1371    async fn iterate_ip_success() {
1372        let socket_1 = fnet_sockets::IpSocketState {
1373            family: Some(fnet::IpVersion::V4),
1374            src_addr: Some(fidl_ip!("192.168.1.1")),
1375            dst_addr: Some(fidl_ip!("192.168.1.2")),
1376            cookie: Some(1234),
1377            marks: Some(fnet::Marks {
1378                mark_1: Some(1111),
1379                mark_2: None,
1380                __source_breaking: fidl::marker::SourceBreaking,
1381            }),
1382            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1383                fnet_sockets::IpSocketTcpState {
1384                    src_port: Some(1111),
1385                    dst_port: Some(2222),
1386                    state: Some(fnet_tcp::State::Established),
1387                    tcp_info: None,
1388                    __source_breaking: fidl::marker::SourceBreaking,
1389                },
1390            )),
1391            __source_breaking: fidl::marker::SourceBreaking,
1392        };
1393
1394        let socket_2 = fnet_sockets::IpSocketState {
1395            family: Some(fnet::IpVersion::V4),
1396            src_addr: Some(fidl_ip!("192.168.8.1")),
1397            dst_addr: Some(fidl_ip!("192.168.8.2")),
1398            cookie: Some(9876),
1399            marks: Some(fnet::Marks {
1400                mark_1: None,
1401                mark_2: Some(2222),
1402                __source_breaking: fidl::marker::SourceBreaking,
1403            }),
1404            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1405                fnet_sockets::IpSocketTcpState {
1406                    src_port: Some(3333),
1407                    dst_port: Some(4444),
1408                    state: Some(fnet_tcp::State::TimeWait),
1409                    tcp_info: None,
1410                    __source_breaking: fidl::marker::SourceBreaking,
1411                },
1412            )),
1413            __source_breaking: fidl::marker::SourceBreaking,
1414        };
1415
1416        let socket_3 = fnet_sockets::IpSocketState {
1417            family: Some(fnet::IpVersion::V6),
1418            src_addr: Some(fidl_ip!("2001:db8::1")),
1419            dst_addr: Some(fidl_ip!("2001:db8::2")),
1420            cookie: Some(5678),
1421            marks: Some(fnet::Marks {
1422                mark_1: None,
1423                mark_2: None,
1424                __source_breaking: fidl::marker::SourceBreaking,
1425            }),
1426            transport: Some(fnet_sockets::IpSocketTransportState::Tcp(
1427                fnet_sockets::IpSocketTcpState {
1428                    src_port: Some(5555),
1429                    dst_port: Some(6666),
1430                    state: Some(fnet_tcp::State::TimeWait),
1431                    tcp_info: None,
1432                    __source_breaking: fidl::marker::SourceBreaking,
1433                },
1434            )),
1435            __source_breaking: fidl::marker::SourceBreaking,
1436        };
1437
1438        let (diagnostics, diagnostics_server_end) =
1439            fidl::endpoints::create_proxy::<fnet_sockets::DiagnosticsMarker>();
1440
1441        let serve_matcher = async |req: fnet_sockets::DiagnosticsRequest| {
1442            let responses = &[vec![socket_1.clone()], vec![socket_2.clone(), socket_3.clone()]];
1443
1444            match req {
1445                fnet_sockets::DiagnosticsRequest::IterateIp {
1446                    s,
1447                    extensions: _,
1448                    matchers: _,
1449                    responder,
1450                } => {
1451                    responder
1452                        .send(&fnet_sockets::IterateIpResult::Ok(fnet_sockets::Empty))
1453                        .unwrap();
1454
1455                    let (mut stream, _control) = s.into_stream_and_control_handle();
1456                    for (i, resp) in responses.iter().enumerate() {
1457                        match stream.next().await.unwrap().unwrap() {
1458                            fidl_fuchsia_net_sockets::IpIteratorRequest::Next { responder } => {
1459                                let has_more = i < responses.len() - 1;
1460                                responder.send(&resp, has_more).unwrap();
1461                            }
1462                            fidl_fuchsia_net_sockets::IpIteratorRequest::_UnknownMethod {
1463                                ..
1464                            } => {
1465                                unreachable!()
1466                            }
1467                        }
1468                    }
1469                }
1470            };
1471        };
1472
1473        let (mut diagnostics_request_stream, _control_handle) =
1474            diagnostics_server_end.into_stream_and_control_handle();
1475
1476        let server_fut = diagnostics_request_stream
1477            .next()
1478            .then(|req| serve_matcher(req.expect("Request stream ended unexpectedly").unwrap()))
1479            .fuse();
1480
1481        let client_fut = async {
1482            iterate_ip::<[IpSocketMatcher; 0], _>(
1483                &diagnostics,
1484                fnet_sockets::Extensions::empty(),
1485                [],
1486            )
1487            .await
1488            .unwrap()
1489            .try_collect::<Vec<_>>()
1490            .await
1491            .unwrap()
1492        };
1493
1494        let ((), sockets) = future::join(server_fut, client_fut).await;
1495        assert_eq!(
1496            sockets,
1497            vec![
1498                socket_1.clone().try_into().unwrap(),
1499                socket_2.clone().try_into().unwrap(),
1500                socket_3.clone().try_into().unwrap()
1501            ]
1502        );
1503    }
1504}