use crate::commands::builder::CommandBuilder;
use crate::commands::hello::HelloCommand;
use crate::commands::Command;
use crate::network::protocol::Protocol;
use crate::network::timeout::Timeout;
use crate::network::{Client, CommandErrors};
use crate::subscription::messages::{DecodeError, Message as PushMessage, ToPushMessage};
use bytes::Bytes;
use embedded_io_async::{Read, Write};
use embedded_time::Clock;
use futures_util::future::{select, Either};
use futures_util::pin_mut;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Error {
CommandError(CommandErrors),
ClockError,
TcpError,
DecodeError,
Timeout,
}
#[derive(Debug, Clone)]
pub struct Message {
pub channel: Bytes,
pub payload: Bytes,
}
#[derive(Debug)]
pub struct Subscription<'a, T: Read + Write, C: Clock, P: Protocol, const L: usize>
where
HelloCommand: Command<<P as Protocol>::FrameType>,
<P as Protocol>::FrameType: From<CommandBuilder>,
<P as Protocol>::FrameType: ToPushMessage,
{
client: Client<'a, T, C, P>,
channels: [Bytes; L],
subscribed: bool,
}
impl<'a, T, C, P, const L: usize> Subscription<'a, T, C, P, L>
where
T: Read + Write,
C: Clock,
P: Protocol,
HelloCommand: Command<<P as Protocol>::FrameType>,
<P as Protocol>::FrameType: From<CommandBuilder>,
<P as Protocol>::FrameType: ToPushMessage,
{
pub fn new(client: Client<'a, T, C, P>, topics: [Bytes; L]) -> Self {
Self {
client,
channels: topics,
subscribed: false,
}
}
pub async fn receive(&mut self) -> Result<Option<Message>, Error> {
loop {
let message = self.receive_message().await?;
if message.is_none() {
continue;
}
if let PushMessage::Publish(channel, payload) = message.unwrap() {
return Ok(Some(Message { channel, payload }));
}
}
}
pub(crate) async fn subscribe(mut self) -> Result<Self, Error> {
let mut cmd = CommandBuilder::new("SUBSCRIBE");
for topic in &self.channels {
cmd = cmd.arg(topic);
}
self.client.network.send_frame(cmd.into()).await.map_err(Error::CommandError)?;
self.wait_for_confirmation(|message| message == PushMessage::SubConfirmation(self.channels.len()))
.await?;
self.subscribed = true;
Ok(self)
}
pub async fn unsubscribe(mut self) -> Result<(), Error> {
self.close().await
}
pub(crate) async fn close(&mut self) -> Result<(), Error> {
self.subscribed = false;
let cmd = CommandBuilder::new("UNSUBSCRIBE");
self.client.network.send_frame(cmd.into()).await.map_err(Error::CommandError)?;
self.wait_for_confirmation(|message| message == PushMessage::UnSubConfirmation(0))
.await?;
Ok(())
}
async fn wait_for_confirmation<F: Fn(PushMessage) -> bool>(
&self,
is_confirmation: F,
) -> Result<(), Error> {
let timeout =
Timeout::new(self.client.clock, self.client.timeout_duration).map_err(|_| Error::ClockError)?;
loop {
if timeout.is_enabled() {
let receive = self.receive_message();
let expired = timeout.wait();
pin_mut!(receive, expired);
match select(receive, expired).await {
Either::Left((message, _)) => {
if let Some(message) = message? {
if is_confirmation(message) {
return Ok(());
}
}
}
Either::Right((result, _)) => {
result.map_err(|_| Error::ClockError)?;
return Err(Error::Timeout);
}
}
} else if let Some(message) = self.receive_message().await? {
if is_confirmation(message) {
return Ok(());
}
}
}
}
async fn receive_message(&self) -> Result<Option<PushMessage>, Error> {
let mut frame = self.client.network.take_next_frame();
if frame.is_none() {
let received = self.client.network.receive_chunk().await.map_err(|_| Error::TcpError)?;
if received == 0 {
return Err(Error::TcpError);
}
frame = self.client.network.take_next_frame();
}
if frame.is_none() {
return Ok(None);
}
match frame.unwrap().decode_push() {
Ok(message) => Ok(Some(message)),
Err(error) => match error {
DecodeError::ProtocolViolation => Err(Error::DecodeError),
DecodeError::IntegerOverflow => Err(Error::DecodeError),
},
}
}
#[cfg(test)]
pub(crate) fn set_unsubscribed(&mut self) {
self.subscribed = false;
}
}