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