use std::sync::Arc;
use std::time::Duration;
use bytes::{Bytes, BytesMut};
use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf};
use crate::MqttError;
use crate::error::MqttResult;
use crate::protocol::MqttVersion;
use crate::protocol::ack::SimpleAck;
use crate::protocol::connect::{ConnectPacket, Will};
use crate::protocol::packet::Packet;
use crate::protocol::properties::Properties;
use super::inner::{
Inner, deliver, finish_disconnected, forget_pending, register_pending, resolve_pending,
wait_for,
};
use super::tls::Transport;
use super::types::{ConnectOptions, MqttMessage};
use crate::protocol::QoS;
use crate::support::LockExt;
const MAX_INCOMING_QOS2: usize = 1024;
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: match &options.auth_method {
Some(method) => {
Properties::with_auth(method, options.auth_data.clone().map(Bytes::from))
}
None => 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;
let Some(writer) = guard.as_mut() else {
return Err(MqttError::NotConnected);
};
match tokio::time::timeout(inner.operation_timeout, writer.write_all(&encoded)).await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => {
drop(guard);
finish_disconnected(inner, format!("write error: {e}"));
Err(e.into())
}
Err(_) => {
drop(guard);
finish_disconnected(inner, "write timed out".into());
Err(MqttError::Timeout)
}
}
}
pub(super) async fn exchange(
inner: &Arc<Inner>,
build: impl FnOnce(u16) -> Packet,
) -> MqttResult<Packet> {
let id = inner.alloc_packet_id();
let rx = register_pending(inner, id);
if let Err(e) = send_packet(inner, &build(id)).await {
forget_pending(inner, id);
return Err(e);
}
wait_for(inner, id, rx).await
}
pub(super) async fn exchange_with_retry(
inner: &Arc<Inner>,
id: u16,
max_retries: u32,
build: impl Fn(bool) -> Packet,
) -> MqttResult<Packet> {
let mut dup = false;
let mut attempts = 0;
loop {
let rx = register_pending(inner, id);
if let Err(e) = send_packet(inner, &build(dup)).await {
forget_pending(inner, id);
return Err(e);
}
match wait_for(inner, id, rx).await {
Err(MqttError::Timeout) if attempts < max_retries => {
attempts += 1;
dup = true;
}
other => return other,
}
}
}
pub(super) async fn read_one_packet(
reader: &mut ReadHalf<Box<dyn Transport>>,
buf: &mut BytesMut,
version: MqttVersion,
timeout: Duration,
max_packet_size: usize,
) -> MqttResult<Packet> {
tokio::time::timeout(timeout, async {
loop {
if let Some(packet) = Packet::decode_with_limit(buf, version, max_packet_size)? {
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,
max_packet_size: usize,
) {
loop {
match Packet::decode_with_limit(&mut buf, inner.version, max_packet_size) {
Ok(Some(packet)) => {
inner.touch();
inner.set_busy(true);
let keep_going = handle_incoming(&inner, packet).await;
inner.set_busy(false);
inner.touch(); if !keep_going {
return;
}
}
Ok(None) => match reader.read_buf(&mut buf).await {
Ok(0) => {
finish_disconnected(&inner, "connection closed by peer".into());
return;
}
Ok(_) => {}
Err(e) => {
finish_disconnected(&inner, format!("read error: {e}"));
return;
}
},
Err(e) => {
finish_disconnected(&inner, format!("protocol error: {e}"));
return;
}
}
}
}
pub(super) async fn handle_incoming(inner: &Arc<Inner>, packet: Packet) -> bool {
if let Some(id) = packet.response_id() {
resolve_pending(inner, id, packet);
return true;
}
match packet {
Packet::Publish(p) => {
let message = MqttMessage::from(&p);
match (p.qos, p.packet_id) {
(QoS::AtMostOnce, _) => deliver(inner, message),
(QoS::AtLeastOnce, Some(id)) => {
deliver(inner, message);
let _ = send_packet(inner, &Packet::PubAck(SimpleAck::success(id))).await;
}
(QoS::ExactlyOnce, Some(id)) => {
{
let mut held = inner.incoming_qos2.lock_safe();
if held.len() >= MAX_INCOMING_QOS2 && !held.contains_key(&id) {
drop(held);
finish_disconnected(
inner,
"broker exceeded the inbound QoS 2 limit".into(),
);
return false;
}
held.insert(id, message);
}
let _ = send_packet(inner, &Packet::PubRec(SimpleAck::success(id))).await;
}
(_, None) => {}
}
true
}
Packet::PubRel(ack) => {
let message = inner.incoming_qos2.lock_safe().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::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 keep_alive = Duration::from_secs(u64::from(keep_alive_secs).max(1));
let interval = keep_alive.mul_f32(0.8);
let dead_after = keep_alive.mul_f32(1.5);
let mut ticker = tokio::time::interval(interval);
ticker.tick().await;
loop {
ticker.tick().await;
if !inner.is_connected() {
return;
}
if !inner.is_busy() && inner.idle_for() > dead_after {
finish_disconnected(&inner, "keep-alive timeout: no data from broker".into());
if let Some(handle) = inner.read_task.lock().await.take() {
handle.abort();
}
return;
}
if send_packet(&inner, &Packet::PingReq).await.is_err() {
return;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::inner::ListenerSlot;
use crate::protocol::MqttVersion;
use crate::protocol::publish::PublishPacket;
fn inner() -> Arc<Inner> {
let (a, _b) = tokio::io::duplex(1 << 16);
let transport: Box<dyn Transport> = Box::new(a);
let (_r, w) = tokio::io::split(transport);
let slot: ListenerSlot = Arc::default();
Arc::new(Inner::new(MqttVersion::V5, w, slot, Duration::from_secs(1)))
}
fn qos2(id: u16) -> Packet {
Packet::Publish(PublishPacket {
dup: false,
qos: QoS::ExactlyOnce,
retain: false,
topic: "t".into(),
packet_id: Some(id),
payload: Bytes::from_static(b"x"),
properties: Properties::new(),
})
}
#[tokio::test]
async fn inbound_qos2_is_bounded() {
let inner = inner();
for id in 1..=MAX_INCOMING_QOS2 as u16 {
assert!(handle_incoming(&inner, qos2(id)).await);
}
assert!(handle_incoming(&inner, qos2(1)).await);
let over = MAX_INCOMING_QOS2 as u16 + 1;
assert!(!handle_incoming(&inner, qos2(over)).await);
assert!(!inner.is_connected());
}
#[tokio::test]
async fn pubrel_releases_a_held_qos2_slot() {
let inner = inner();
assert!(handle_incoming(&inner, qos2(5)).await);
assert_eq!(inner.incoming_qos2.lock_safe().len(), 1);
let rel = Packet::PubRel(SimpleAck::success(5));
assert!(handle_incoming(&inner, rel).await);
assert!(inner.incoming_qos2.lock_safe().is_empty());
}
}