Skip to main content

rings_node/extension/protocols/
echo.rs

1#![warn(missing_docs)]
2//! Echo protocol — the reference extension (pure [`Protocol`] + its [`Interpret`] shell).
3//!
4//! Demonstrates the model end to end: **stateful** (counts the messages seen), **typed**
5//! (its own `Event`/`Effect`), and **effectful** (echoes the payload back) — yet `step` is
6//! pure and the only IO (an overlay `send`) lives in the interpreter.
7//!
8//! ```text
9//!   S = ℕ
10//!   step (Ctx n, Echoed{from, p}) = Transition (n+1) [Reply{to=from, p}]
11//! ```
12
13use bytes::Bytes;
14use rings_core::dht::Did;
15
16use crate::extension::ext::Ctx;
17use crate::extension::ext::Interpret;
18use crate::extension::ext::Protocol;
19use crate::extension::ext::Reject;
20use crate::extension::ext::Scope;
21use crate::extension::ext::Transition;
22use crate::extension::ext::Wire;
23
24/// Namespace for the echo protocol.
25pub const NAMESPACE: &str = "echo";
26
27/// A decoded echo message: who sent it and what bytes to echo back.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct Echoed {
30    /// Sender to echo back to.
31    pub from: Did,
32    /// Payload to echo.
33    pub payload: Bytes,
34}
35
36/// Echo's own effect: reply to `to` with `payload` over the overlay.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum EchoEffect {
39    /// Send `payload` back to `to` under the echo namespace.
40    Reply {
41        /// Destination.
42        to: Did,
43        /// Payload to send.
44        payload: Bytes,
45    },
46}
47
48/// Echo protocol: replies with the same payload and counts how many it has seen.
49#[derive(Default)]
50pub struct Echo;
51
52impl Protocol for Echo {
53    /// Number of messages seen so far.
54    type State = u64;
55    type Event = Echoed;
56    type Effect = EchoEffect;
57
58    fn namespace(&self) -> &str {
59        NAMESPACE
60    }
61
62    fn init(&self) -> u64 {
63        0
64    }
65
66    fn decode(&self, wire: Wire<'_>) -> Result<Echoed, Reject> {
67        Ok(Echoed {
68            from: wire.from,
69            payload: Bytes::copy_from_slice(wire.payload),
70        })
71    }
72
73    /// Pure. `step (Ctx n, Echoed{from,p}) = ((n+1), [Reply to=from p])`.
74    fn step(&self, ctx: Ctx<'_, u64>, event: Echoed) -> Transition<u64, EchoEffect> {
75        Transition::with(ctx.state + 1, vec![EchoEffect::Reply {
76            to: event.from,
77            payload: event.payload,
78        }])
79    }
80}
81
82/// Echo's interpreter: it owns no resources; a `Reply` is just an overlay `send`.
83#[derive(Default)]
84pub struct EchoShell;
85
86#[cfg_attr(feature = "browser", async_trait::async_trait(?Send))]
87#[cfg_attr(not(feature = "browser"), async_trait::async_trait)]
88impl Interpret for EchoShell {
89    type Effect = EchoEffect;
90
91    async fn run(&self, scope: &Scope, effect: EchoEffect) -> crate::error::Result<Vec<Bytes>> {
92        match effect {
93            EchoEffect::Reply { to, payload } => {
94                scope.send(to, payload).await?;
95                Ok(Vec::new())
96            }
97        }
98    }
99}