Skip to main content

embedded_redis/subscription/
client.rs

1use crate::commands::builder::CommandBuilder;
2use crate::commands::hello::HelloCommand;
3use crate::commands::Command;
4use crate::network::protocol::Protocol;
5use crate::network::timeout::Timeout;
6use crate::network::{Client, CommandErrors};
7use crate::subscription::messages::{DecodeError, Message as PushMessage, ToPushMessage};
8use bytes::Bytes;
9use embedded_io_async::{Read, Write};
10use embedded_time::Clock;
11use futures_util::future::{select, Either};
12use futures_util::pin_mut;
13
14/// Subscription errors
15#[derive(Debug, Eq, PartialEq, Clone)]
16pub enum Error {
17    /// Error while sending SUBSCRIBE or UNSUBSCRIBE command
18    CommandError(CommandErrors),
19    /// Upstream time error
20    ClockError,
21    /// Network error receiving or sending data
22    TcpError,
23    /// Error while decoding a push message. Either Redis sent invalid data or there is a decoder bug.
24    DecodeError,
25    /// Subscription or unsubscription was not confirmed by Redis within the time limit.
26    Timeout,
27}
28
29/// A published subscription message
30#[derive(Debug, Clone)]
31pub struct Message {
32    /// The channel the message has been published to
33    pub channel: Bytes,
34
35    /// The actual payload
36    pub payload: Bytes,
37}
38
39/// Client for handling subscriptions
40///
41/// L: Number of subscribed topics
42#[derive(Debug)]
43pub struct Subscription<'a, T: Read + Write, C: Clock, P: Protocol, const L: usize>
44where
45    HelloCommand: Command<<P as Protocol>::FrameType>,
46    <P as Protocol>::FrameType: From<CommandBuilder>,
47    <P as Protocol>::FrameType: ToPushMessage,
48{
49    client: Client<'a, T, C, P>,
50
51    /// List of subscribed topics
52    channels: [Bytes; L],
53
54    /// Confirmed + active subscription
55    subscribed: bool,
56}
57
58impl<'a, T, C, P, const L: usize> Subscription<'a, T, C, P, L>
59where
60    T: Read + Write,
61    C: Clock,
62    P: Protocol,
63    HelloCommand: Command<<P as Protocol>::FrameType>,
64    <P as Protocol>::FrameType: From<CommandBuilder>,
65    <P as Protocol>::FrameType: ToPushMessage,
66{
67    pub fn new(client: Client<'a, T, C, P>, topics: [Bytes; L]) -> Self {
68        Self {
69            client,
70            channels: topics,
71            subscribed: false,
72        }
73    }
74
75    /// Waits for and receives the next published message.
76    pub async fn receive(&mut self) -> Result<Option<Message>, Error> {
77        loop {
78            let message = self.receive_message().await?;
79
80            if message.is_none() {
81                continue;
82            }
83
84            if let PushMessage::Publish(channel, payload) = message.unwrap() {
85                return Ok(Some(Message { channel, payload }));
86            }
87        }
88    }
89
90    /// Starts the subscription and waits for confirmation
91    pub(crate) async fn subscribe(mut self) -> Result<Self, Error> {
92        let mut cmd = CommandBuilder::new("SUBSCRIBE");
93        for topic in &self.channels {
94            cmd = cmd.arg(topic);
95        }
96
97        self.client.network.send_frame(cmd.into()).await.map_err(Error::CommandError)?;
98        self.wait_for_confirmation(|message| message == PushMessage::SubConfirmation(self.channels.len()))
99            .await?;
100
101        self.subscribed = true;
102        Ok(self)
103    }
104
105    /// Unsubscribes from all topics and waits for confirmation
106    ///
107    /// If this fails, the owned connection is dropped to avoid reusing an undefined state.
108    pub async fn unsubscribe(mut self) -> Result<(), Error> {
109        self.close().await
110    }
111
112    /// Unsubscribes from all topics and waits for confirmation
113    pub(crate) async fn close(&mut self) -> Result<(), Error> {
114        self.subscribed = false;
115        let cmd = CommandBuilder::new("UNSUBSCRIBE");
116
117        self.client.network.send_frame(cmd.into()).await.map_err(Error::CommandError)?;
118        self.wait_for_confirmation(|message| message == PushMessage::UnSubConfirmation(0))
119            .await?;
120
121        Ok(())
122    }
123
124    /// Waits for the confirmation of all topics
125    async fn wait_for_confirmation<F: Fn(PushMessage) -> bool>(
126        &self,
127        is_confirmation: F,
128    ) -> Result<(), Error> {
129        let timeout =
130            Timeout::new(self.client.clock, self.client.timeout_duration).map_err(|_| Error::ClockError)?;
131
132        loop {
133            if timeout.is_enabled() {
134                let receive = self.receive_message();
135                let expired = timeout.wait();
136                pin_mut!(receive, expired);
137
138                match select(receive, expired).await {
139                    Either::Left((message, _)) => {
140                        if let Some(message) = message? {
141                            if is_confirmation(message) {
142                                return Ok(());
143                            }
144                        }
145                    }
146                    Either::Right((result, _)) => {
147                        result.map_err(|_| Error::ClockError)?;
148                        return Err(Error::Timeout);
149                    }
150                }
151            } else if let Some(message) = self.receive_message().await? {
152                if is_confirmation(message) {
153                    return Ok(());
154                }
155            }
156        }
157    }
158
159    /// Receives and decodes the next message. Returns None in case no message is pending or not complete yet.
160    async fn receive_message(&self) -> Result<Option<PushMessage>, Error> {
161        let mut frame = self.client.network.take_next_frame();
162
163        if frame.is_none() {
164            let received = self.client.network.receive_chunk().await.map_err(|_| Error::TcpError)?;
165            if received == 0 {
166                return Err(Error::TcpError);
167            }
168            frame = self.client.network.take_next_frame();
169        }
170
171        if frame.is_none() {
172            return Ok(None);
173        }
174
175        match frame.unwrap().decode_push() {
176            Ok(message) => Ok(Some(message)),
177            Err(error) => match error {
178                DecodeError::ProtocolViolation => Err(Error::DecodeError),
179                DecodeError::IntegerOverflow => Err(Error::DecodeError),
180            },
181        }
182    }
183
184    /// Prevents the automatic unsubscription when client is dropped
185    #[cfg(test)]
186    pub(crate) fn set_unsubscribed(&mut self) {
187        self.subscribed = false;
188    }
189}