Skip to main content

rings_node/extension/protocols/
echo.rs

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