netmod_mem/
lib.rs

1//! `netmod-mem` is an in-memory `netmod` endpoint
2//!
3//! This aims to make testing any structure that binds against
4//! `netmod` easier and reproducable.
5
6#![doc(html_favicon_url = "https://qaul.org/favicon.ico")]
7#![doc(html_logo_url = "https://qaul.org/img/qaul_icon-128.png")]
8
9use async_std::{
10    sync::{Arc, RwLock},
11    task,
12};
13use async_trait::async_trait;
14use ratman_netmod::{Endpoint, Error as NetError, Frame, Result as NetResult, Target};
15
16/// An input/output pair of `mpsc::channel`s.
17///
18/// This is the actual mechanism by which data is moved around between `MemMod`s in
19/// different places.
20pub(crate) mod io;
21// Simulated transmission media.
22// pub mod media;
23
24/// Represents a single netmod endpoint that can connect to exactly one other, either
25/// as a 1-to-1 link between libqaul instances or as a link into a transmission
26/// medium of some kind.
27pub struct MemMod {
28    /// Internal memory access to send/receive
29    io: Arc<RwLock<Option<io::Io>>>,
30}
31
32impl MemMod {
33    /// Create a new, unpaired `MemMod`.
34    pub fn new() -> Arc<Self> {
35        Arc::new(Self {
36            io: Default::default(),
37        })
38    }
39
40    /// Create two already-paired `MemMod`s, ready for use.
41    pub fn make_pair() -> (Arc<Self>, Arc<Self>) {
42        let (a, b) = (MemMod::new(), MemMod::new());
43        a.link(&b);
44        (a, b)
45    }
46
47    /// Return `true` if the MemMod is linked to another one or
48    /// `false` otherwise.
49    pub fn linked(&self) -> bool {
50        task::block_on(async { self.io.read().await.is_some() })
51    }
52
53    /// Establish a 1-to-1 link between two `MemMod`s.
54    ///
55    /// # Panics
56    ///
57    /// Panics if this MemMod, or the other one, is already linked.
58    pub fn link(&self, pair: &MemMod) {
59        if self.linked() || pair.linked() {
60            panic!("Attempted to link an already linked MemMod.");
61        }
62        let (my_io, their_io) = io::Io::make_pair();
63
64        self.set_io_async(my_io);
65        pair.set_io_async(their_io);
66    }
67
68    /// Establish a link to an `Io` module
69    ///
70    /// # Panics
71    /// Panics if this MemMod is already linked.
72    pub(crate) fn link_raw(&mut self, io: io::Io) {
73        if self.linked() {
74            panic!("Attempted to link an already linked MemMod.");
75        }
76        self.set_io_async(io);
77    }
78
79    /// Remove the connection between MemMods.
80    pub fn split(&self) {
81        // The previous value in here will now be dropped,
82        // so future messages will fail.
83        self.set_io_async(None);
84    }
85
86    fn set_io_async<I: Into<Option<io::Io>>>(&self, val: I) {
87        task::block_on(async { *self.io.write().await = val.into() });
88    }
89}
90
91#[async_trait]
92impl Endpoint for MemMod {
93    /// Provides maximum frame-size information to `RATMAN`
94    fn size_hint(&self) -> usize {
95        ::std::u32::MAX as usize
96    }
97
98    /// Send a message to a specific endpoint (client)
99    ///
100    /// # Errors
101    ///
102    /// Returns `OperationNotSupported` if attempting to send through
103    /// a connection that is not yet connected.
104    async fn send(&self, frame: Frame, _: Target) -> NetResult<()> {
105        let io = self.io.read().await;
106        match *io {
107            None => Err(NetError::NotSupported),
108            Some(ref io) => Ok(io.out.send(frame).await.unwrap()),
109        }
110    }
111
112    async fn next(&self) -> NetResult<(Frame, Target)> {
113        let io = self.io.read().await;
114        match *io {
115            None => Err(NetError::NotSupported),
116            Some(ref io) => match io.inc.recv().await {
117                Ok(f) => Ok((f, Target::default())),
118                Err(_) => Err(NetError::ConnectionLost),
119            },
120        }
121    }
122}