Skip to main content

dfir_rs/util/
mod.rs

1//! Helper utilities for the DFIR syntax.
2#![warn(missing_docs)]
3
4#[cfg(feature = "dfir_macro")]
5#[cfg_attr(docsrs, doc(cfg(feature = "dfir_macro")))]
6pub mod demux_enum;
7pub mod multiset;
8#[cfg(feature = "tokio")]
9pub mod unsync;
10
11mod monotonic;
12pub use monotonic::*;
13
14#[cfg(feature = "tokio")]
15mod udp;
16#[cfg(feature = "tokio")]
17#[cfg(not(target_arch = "wasm32"))]
18pub use udp::*;
19
20#[cfg(feature = "tokio")]
21mod tcp;
22#[cfg(feature = "tokio")]
23#[cfg(not(target_arch = "wasm32"))]
24pub use tcp::*;
25
26#[cfg(feature = "tokio")]
27#[cfg(unix)]
28mod socket;
29use std::net::SocketAddr;
30#[cfg(feature = "tokio")]
31use std::num::NonZeroUsize;
32use std::task::{Context, Poll};
33
34use futures::Stream;
35use serde::de::DeserializeOwned;
36use serde::ser::Serialize;
37#[cfg(feature = "tokio")]
38#[cfg(unix)]
39pub use socket::*;
40
41/// Returns a channel as a (1) unbounded sender and (2) unbounded receiver `Stream` for use in DFIR.
42#[cfg(feature = "tokio")]
43pub fn unbounded_channel<T>() -> (
44    tokio::sync::mpsc::UnboundedSender<T>,
45    tokio_stream::wrappers::UnboundedReceiverStream<T>,
46) {
47    let (send, recv) = tokio::sync::mpsc::unbounded_channel();
48    let recv = tokio_stream::wrappers::UnboundedReceiverStream::new(recv);
49    (send, recv)
50}
51
52/// Returns an unsync channel as a (1) sender and (2) receiver `Stream` for use in DFIR.
53#[cfg(feature = "tokio")]
54pub fn unsync_channel<T>(
55    capacity: Option<NonZeroUsize>,
56) -> (unsync::mpsc::Sender<T>, unsync::mpsc::Receiver<T>) {
57    unsync::mpsc::channel(capacity)
58}
59
60/// Returns an [`Iterator`] of any immediately available items from the [`Stream`].
61pub fn ready_iter<S>(stream: S) -> impl Iterator<Item = S::Item>
62where
63    S: Stream,
64{
65    let mut stream = Box::pin(stream);
66    std::iter::from_fn(move || {
67        match stream
68            .as_mut()
69            .poll_next(&mut Context::from_waker(futures::task::noop_waker_ref()))
70        {
71            Poll::Ready(opt) => opt,
72            Poll::Pending => None,
73        }
74    })
75}
76
77/// Collects the immediately available items from the `Stream` into a `FromIterator` collection.
78///
79/// This consumes the stream, use [`futures::StreamExt::by_ref()`] (or just `&mut ...`) if you want
80/// to retain ownership of your stream.
81#[cfg(feature = "tokio")]
82pub fn collect_ready<C, S>(stream: S) -> C
83where
84    C: FromIterator<S::Item>,
85    S: Stream,
86{
87    assert!(
88        tokio::runtime::Handle::try_current().is_err(),
89        "Calling `collect_ready` from an async runtime may cause incorrect results, use `collect_ready_async` instead."
90    );
91    ready_iter(stream).collect()
92}
93
94/// Collects the immediately available items from the `Stream` into a collection (`Default` + `Extend`).
95///
96/// This consumes the stream, use [`futures::StreamExt::by_ref()`] (or just `&mut ...`) if you want
97/// to retain ownership of your stream.
98#[cfg(feature = "tokio")]
99pub async fn collect_ready_async<C, S>(stream: S) -> C
100where
101    C: Default + Extend<S::Item>,
102    S: Stream,
103{
104    use std::sync::atomic::Ordering;
105
106    // Yield to let any background async tasks send to the stream.
107    tokio::task::yield_now().await;
108
109    let got_any_items = std::sync::atomic::AtomicBool::new(true);
110    let mut unfused_iter =
111        ready_iter(stream).inspect(|_| got_any_items.store(true, Ordering::Relaxed));
112    let mut out = C::default();
113    while got_any_items.swap(false, Ordering::Relaxed) {
114        out.extend(unfused_iter.by_ref());
115        // Tokio unbounded channel returns items in lenght-128 chunks, so we have to be careful
116        // that everything gets returned. That is why we yield here and loop.
117        tokio::task::yield_now().await;
118    }
119    out
120}
121
122/// Serialize a message to bytes using bincode.
123pub fn serialize_to_bytes<T>(msg: T) -> bytes::Bytes
124where
125    T: Serialize,
126{
127    bytes::Bytes::from(bincode::serialize(&msg).unwrap())
128}
129
130/// Serialize a message from bytes using bincode.
131pub fn deserialize_from_bytes<T>(msg: impl AsRef<[u8]>) -> bincode::Result<T>
132where
133    T: DeserializeOwned,
134{
135    bincode::deserialize(msg.as_ref())
136}
137
138/// Resolve the `ipv4` [`SocketAddr`] from an IP or hostname string.
139pub fn ipv4_resolve(addr: &str) -> Result<SocketAddr, std::io::Error> {
140    use std::net::ToSocketAddrs;
141    let mut addrs = addr.to_socket_addrs()?;
142    let result = addrs.find(|addr| addr.is_ipv4());
143    match result {
144        Some(addr) => Ok(addr),
145        None => Err(std::io::Error::other("Unable to resolve IPv4 address")),
146    }
147}
148
149/// Returns a length-delimited bytes `Sink`, `Stream`, and `SocketAddr` bound to the given address.
150/// The input `addr` may have a port of `0`, the returned `SocketAddr` will have the chosen port.
151#[cfg(feature = "tokio")]
152#[cfg(not(target_arch = "wasm32"))]
153pub async fn bind_udp_bytes(addr: SocketAddr) -> (UdpSink, UdpStream, SocketAddr) {
154    let socket = tokio::net::UdpSocket::bind(addr).await.unwrap();
155    udp_bytes(socket)
156}
157
158/// Returns a newline-delimited bytes `Sink`, `Stream`, and `SocketAddr` bound to the given address.
159/// The input `addr` may have a port of `0`, the returned `SocketAddr` will have the chosen port.
160#[cfg(feature = "tokio")]
161#[cfg(not(target_arch = "wasm32"))]
162pub async fn bind_udp_lines(addr: SocketAddr) -> (UdpLinesSink, UdpLinesStream, SocketAddr) {
163    let socket = tokio::net::UdpSocket::bind(addr).await.unwrap();
164    udp_lines(socket)
165}
166
167/// Returns a newline-delimited bytes `Sender`, `Receiver`, and `SocketAddr` bound to the given address.
168///
169/// The input `addr` may have a port of `0`, the returned `SocketAddr` will be the address of the newly bound endpoint.
170/// The inbound connections can be used in full duplex mode. When a `(T, SocketAddr)` pair is fed to the `Sender`
171/// returned by this function, the `SocketAddr` will be looked up against the currently existing connections.
172/// If a match is found then the data will be sent on that connection. If no match is found then the data is silently dropped.
173#[cfg(feature = "tokio")]
174#[cfg(not(target_arch = "wasm32"))]
175pub async fn bind_tcp_bytes(
176    addr: SocketAddr,
177) -> (
178    unsync::mpsc::Sender<(bytes::Bytes, SocketAddr)>,
179    unsync::mpsc::Receiver<Result<(bytes::BytesMut, SocketAddr), std::io::Error>>,
180    SocketAddr,
181) {
182    bind_tcp(addr, tokio_util::codec::LengthDelimitedCodec::new())
183        .await
184        .unwrap()
185}
186
187/// This is the same thing as `bind_tcp_bytes` except instead of using a length-delimited encoding scheme it uses new lines to separate frames.
188#[cfg(feature = "tokio")]
189#[cfg(not(target_arch = "wasm32"))]
190pub async fn bind_tcp_lines(
191    addr: SocketAddr,
192) -> (
193    unsync::mpsc::Sender<(String, SocketAddr)>,
194    unsync::mpsc::Receiver<Result<(String, SocketAddr), tokio_util::codec::LinesCodecError>>,
195    SocketAddr,
196) {
197    bind_tcp(addr, tokio_util::codec::LinesCodec::new())
198        .await
199        .unwrap()
200}
201
202/// The inverse of [`bind_tcp_bytes`].
203///
204/// `(Bytes, SocketAddr)` pairs fed to the returned `Sender` will initiate new tcp connections to the specified `SocketAddr`.
205/// These connections will be cached and reused, so that there will only be one connection per destination endpoint. When the endpoint sends data back it will be available via the returned `Receiver`
206#[cfg(feature = "tokio")]
207#[cfg(not(target_arch = "wasm32"))]
208pub fn connect_tcp_bytes() -> (
209    TcpFramedSink<bytes::Bytes>,
210    TcpFramedStream<tokio_util::codec::LengthDelimitedCodec>,
211) {
212    connect_tcp(tokio_util::codec::LengthDelimitedCodec::new())
213}
214
215/// This is the same thing as `connect_tcp_bytes` except instead of using a length-delimited encoding scheme it uses new lines to separate frames.
216#[cfg(feature = "tokio")]
217#[cfg(not(target_arch = "wasm32"))]
218pub fn connect_tcp_lines() -> (
219    TcpFramedSink<String>,
220    TcpFramedStream<tokio_util::codec::LinesCodec>,
221) {
222    connect_tcp(tokio_util::codec::LinesCodec::new())
223}
224
225/// Sort a slice using a key fn which returns references.
226///
227/// From addendum in
228/// <https://stackoverflow.com/questions/56105305/how-to-sort-a-vec-of-structs-by-a-string-field>
229pub fn sort_unstable_by_key_hrtb<T, F, K>(slice: &mut [T], f: F)
230where
231    F: for<'a> Fn(&'a T) -> &'a K,
232    K: Ord,
233{
234    slice.sort_unstable_by(|a, b| f(a).cmp(f(b)))
235}
236
237/// Converts an iterator into a stream that emits `n` items at a time, yielding between each batch.
238///
239/// This is useful for breaking up a large iterator across several ticks: `source_iter(...)` always
240/// releases all items in the first tick. However using `iter_batches_stream` with `source_stream(...)`
241/// will cause `n` items to be released each tick. (Although more than that may be emitted if there
242/// are loops in the stratum).
243pub fn iter_batches_stream<I>(
244    iter: I,
245    n: usize,
246) -> futures::stream::PollFn<impl FnMut(&mut Context<'_>) -> Poll<Option<I::Item>>>
247where
248    I: IntoIterator + Unpin,
249{
250    let mut count = 0;
251    let mut iter = iter.into_iter();
252    futures::stream::poll_fn(move |ctx| {
253        count += 1;
254        if n < count {
255            count = 0;
256            ctx.waker().wake_by_ref();
257            Poll::Pending
258        } else {
259            Poll::Ready(iter.next())
260        }
261    })
262}
263
264#[cfg(test)]
265mod test {
266    use super::*;
267
268    #[test]
269    pub fn test_collect_ready() {
270        let (send, mut recv) = unbounded_channel::<usize>();
271        for x in 0..1000 {
272            send.send(x).unwrap();
273        }
274        assert_eq!(1000, collect_ready::<Vec<_>, _>(&mut recv).len());
275    }
276
277    #[crate::test]
278    pub async fn test_collect_ready_async() {
279        // Tokio unbounded channel returns items in 128 item long chunks, so we have to be careful that everything gets returned.
280        let (send, mut recv) = unbounded_channel::<usize>();
281        for x in 0..1000 {
282            send.send(x).unwrap();
283        }
284        assert_eq!(
285            1000,
286            collect_ready_async::<Vec<_>, _>(&mut recv).await.len()
287        );
288    }
289}