use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use bytes::{Bytes, BytesMut};
use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf};
use crate::error::MqttResult;
use crate::protocol::ack::SimpleAck;
use crate::protocol::connect::{ConnectPacket, Will};
use crate::protocol::packet::Packet;
use crate::protocol::properties::Properties;
use crate::protocol::MqttVersion;
use crate::MqttError;
use super::inner::{deliver, finish_disconnected, resolve_pending, Inner};
use super::tls::Transport;
use super::types::{ConnectOptions, MqttMessage};
use crate::protocol::QoS;
pub(super) fn build_connect_packet(options: &ConnectOptions) -> ConnectPacket {
let will = options.will.as_ref().map(|w| Will {
topic: w.topic.clone(),
payload: Bytes::from(w.payload.clone()),
qos: w.qos,
retain: w.retain,
properties: Properties::new(),
delay_interval: 0,
});
ConnectPacket {
version: options.version,
client_id: options.client_id.clone(),
clean_start: options.clean_start,
keep_alive: options.keep_alive_secs,
username: options.username.clone(),
password: options.password.clone().map(Bytes::from),
will,
properties: Properties::new(),
}
}
pub(super) async fn send_packet(inner: &Arc<Inner>, packet: &Packet) -> MqttResult<()> {
let encoded = packet
.encode(inner.version)
.map_err(|e| MqttError::Protocol(e.to_string()))?;
let mut guard = inner.writer.lock().await;
match guard.as_mut() {
Some(writer) => {
writer.write_all(&encoded).await?;
Ok(())
}
None => Err(MqttError::NotConnected),
}
}
pub(super) async fn read_one_packet(
reader: &mut ReadHalf<Box<dyn Transport>>,
buf: &mut BytesMut,
version: MqttVersion,
timeout_secs: u32,
) -> MqttResult<Packet> {
let deadline = Duration::from_secs(timeout_secs.max(1) as u64);
tokio::time::timeout(deadline, async {
loop {
if let Some(packet) = Packet::decode(buf, version)? {
return Ok(packet);
}
let n = reader.read_buf(buf).await?;
if n == 0 {
return Err(MqttError::Io("connection closed".into()));
}
}
})
.await
.map_err(|_| MqttError::Timeout)?
}
pub(super) async fn read_loop(
inner: Arc<Inner>,
mut reader: ReadHalf<Box<dyn Transport>>,
mut buf: BytesMut,
) {
loop {
let packet = match Packet::decode(&mut buf, inner.version) {
Ok(Some(packet)) => packet,
Ok(None) => {
let mut read_buf = [0u8; 4096];
match reader.read(&mut read_buf).await {
Ok(0) => {
finish_disconnected(&inner, "connection closed by peer".into());
return;
}
Ok(n) => {
buf.extend_from_slice(&read_buf[..n]);
continue;
}
Err(e) => {
finish_disconnected(&inner, format!("read error: {e}"));
return;
}
}
}
Err(e) => {
finish_disconnected(&inner, format!("protocol error: {e}"));
return;
}
};
if !handle_incoming(&inner, packet).await {
return;
}
}
}
pub(super) async fn handle_incoming(inner: &Arc<Inner>, packet: Packet) -> bool {
match packet {
Packet::Publish(p) => {
let message = MqttMessage {
topic: p.topic.clone(),
payload: p.payload.to_vec(),
qos: p.qos,
retain: p.retain,
};
match p.qos {
QoS::AtMostOnce => deliver(inner, message),
QoS::AtLeastOnce => {
if let Some(id) = p.packet_id {
deliver(inner, message);
let _ = send_packet(inner, &Packet::PubAck(SimpleAck::success(id))).await;
}
}
QoS::ExactlyOnce => {
if let Some(id) = p.packet_id {
inner
.incoming_qos2
.lock()
.unwrap()
.messages
.insert(id, message);
let _ = send_packet(inner, &Packet::PubRec(SimpleAck::success(id))).await;
}
}
}
true
}
Packet::PubRel(ack) => {
let message = inner
.incoming_qos2
.lock()
.unwrap()
.messages
.remove(&ack.packet_id);
if let Some(message) = message {
deliver(inner, message);
}
let _ = send_packet(inner, &Packet::PubComp(SimpleAck::success(ack.packet_id))).await;
true
}
Packet::PubAck(ack) => {
resolve_pending(inner, ack.packet_id, Packet::PubAck(ack));
true
}
Packet::PubRec(ack) => {
resolve_pending(inner, ack.packet_id, Packet::PubRec(ack));
true
}
Packet::PubComp(ack) => {
resolve_pending(inner, ack.packet_id, Packet::PubComp(ack));
true
}
Packet::SubAck(ack) => {
resolve_pending(inner, ack.packet_id, Packet::SubAck(ack));
true
}
Packet::UnsubAck(ack) => {
resolve_pending(inner, ack.packet_id, Packet::UnsubAck(ack));
true
}
Packet::PingResp => true,
Packet::Disconnect(d) => {
finish_disconnected(
inner,
format!("server disconnected (reason 0x{:02X})", d.reason_code),
);
false
}
_ => true,
}
}
pub(super) async fn keepalive_loop(inner: Arc<Inner>, keep_alive_secs: u16) {
let interval = Duration::from_secs((keep_alive_secs as u64).max(1)).mul_f32(0.8);
let mut ticker = tokio::time::interval(interval);
ticker.tick().await;
loop {
ticker.tick().await;
if !inner.connected.load(Ordering::Relaxed) {
return;
}
if send_packet(&inner, &Packet::PingReq).await.is_err() {
finish_disconnected(&inner, "keep-alive ping failed".into());
return;
}
}
}