use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use bytes::Bytes;
use hashbrown::HashMap;
use mqtt_proto::v5::{UserProperty, VarByteInt};
use mqtt_proto::TopicName;
pub trait MqttProtocolConfig: core::fmt::Debug + Clone {
fn protocol_version(&self) -> &'static str;
fn validate(&self) -> Result<(), crate::error::ConfigError>;
fn connection_properties(&self) -> ProtocolProperties;
}
#[derive(Debug, Clone, Default)]
pub struct ProtocolProperties {
pub session_expiry_interval: Option<u32>,
pub receive_maximum: Option<u16>,
pub maximum_packet_size: Option<u32>,
pub topic_alias_maximum: Option<u16>,
pub extended_properties: HashMap<String, Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct V3Config {
pub protocol_name: String,
pub protocol_version: u8,
pub strict_protocol_validation: bool,
}
impl Default for V3Config {
fn default() -> Self {
Self {
protocol_name: "MQTT".to_string(),
protocol_version: 4, strict_protocol_validation: true,
}
}
}
impl V3Config {
pub fn new() -> Self {
Self::default()
}
pub fn with_strict_validation(mut self, strict: bool) -> Self {
self.strict_protocol_validation = strict;
self
}
}
impl MqttProtocolConfig for V3Config {
fn protocol_version(&self) -> &'static str {
"3.1.1"
}
fn validate(&self) -> Result<(), crate::error::ConfigError> {
if self.protocol_name != "MQTT" && self.strict_protocol_validation {
return Err(crate::error::ConfigError::InvalidProtocol(
"MQTT v3.1.1 protocol name must be 'MQTT'".into(),
));
}
if self.protocol_version != 4 && self.strict_protocol_validation {
return Err(crate::error::ConfigError::InvalidProtocol(
"MQTT v3.1.1 protocol version must be 4".into(),
));
}
Ok(())
}
fn connection_properties(&self) -> ProtocolProperties {
ProtocolProperties::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct V5ConnectConfig {
pub session_expiry_interval: Option<u32>,
pub receive_maximum: Option<u16>,
pub maximum_packet_size: Option<u32>,
pub topic_alias_maximum: Option<u16>,
pub request_response_information: Option<bool>,
pub request_problem_information: Option<bool>,
pub authentication_method: Option<Arc<str>>,
pub authentication_data: Option<Bytes>,
pub user_properties: Vec<UserProperty>,
}
impl V5ConnectConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_session_expiry(mut self, interval_secs: u32) -> Self {
self.session_expiry_interval = Some(interval_secs);
self
}
pub fn with_receive_maximum(mut self, max: u16) -> Self {
self.receive_maximum = Some(max);
self
}
pub fn with_maximum_packet_size(mut self, size: u32) -> Self {
self.maximum_packet_size = Some(size);
self
}
pub fn with_topic_alias_maximum(mut self, max: u16) -> Self {
self.topic_alias_maximum = Some(max);
self
}
pub fn with_user_property(mut self, key: &str, value: &str) -> Self {
self.user_properties.push(UserProperty {
name: key.into(),
value: value.into(),
});
self
}
pub fn with_extended_auth(mut self, method: &str, data: impl Into<Bytes>) -> Self {
self.authentication_method = Some(method.into());
self.authentication_data = Some(data.into());
self
}
}
impl MqttProtocolConfig for V5ConnectConfig {
fn protocol_version(&self) -> &'static str {
"5.0"
}
fn validate(&self) -> Result<(), crate::error::ConfigError> {
if let Some(receive_max) = self.receive_maximum {
if receive_max == 0 {
return Err(crate::error::ConfigError::InvalidLimit(
"Receive maximum cannot be 0".into(),
));
}
}
if let Some(packet_size) = self.maximum_packet_size {
if packet_size == 0 {
return Err(crate::error::ConfigError::InvalidLimit(
"Maximum packet size cannot be 0".into(),
));
}
}
Ok(())
}
fn connection_properties(&self) -> ProtocolProperties {
ProtocolProperties {
session_expiry_interval: self.session_expiry_interval,
receive_maximum: self.receive_maximum,
maximum_packet_size: self.maximum_packet_size,
topic_alias_maximum: self.topic_alias_maximum,
extended_properties: HashMap::new(), }
}
}
#[derive(Debug, Clone, Default)]
pub struct V5PublishConfig {
pub payload_format_indicator: Option<bool>,
pub message_expiry_interval: Option<u32>,
pub topic_alias: Option<u16>,
pub response_topic: Option<TopicName>,
pub correlation_data: Option<Bytes>,
pub subscription_identifiers: Vec<VarByteInt>,
pub content_type: Option<Arc<str>>,
pub user_properties: Vec<UserProperty>,
}
impl V5PublishConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_payload_format_indicator(mut self, is_utf8: bool) -> Self {
self.payload_format_indicator = Some(is_utf8);
self
}
pub fn with_message_expiry(mut self, interval_secs: u32) -> Self {
self.message_expiry_interval = Some(interval_secs);
self
}
pub fn with_response_topic(mut self, topic: TopicName) -> Self {
self.response_topic = Some(topic);
self
}
pub fn with_correlation_data(mut self, data: impl Into<Bytes>) -> Self {
self.correlation_data = Some(data.into());
self
}
pub fn with_content_type(mut self, content_type: &str) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn with_user_property(mut self, key: &str, value: &str) -> Self {
self.user_properties.push(UserProperty {
name: key.into(),
value: value.into(),
});
self
}
}
#[derive(Debug, Clone, Default)]
pub struct V5SubscribeConfig {
pub subscription_identifier: Option<VarByteInt>,
pub user_properties: Vec<UserProperty>,
}
impl V5SubscribeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_subscription_identifier(mut self, id: u32) -> Self {
self.subscription_identifier = Some(VarByteInt::try_from(id).unwrap_or_default());
self
}
pub fn with_user_property(mut self, key: &str, value: &str) -> Self {
self.user_properties.push(UserProperty {
name: key.into(),
value: value.into(),
});
self
}
}
#[derive(Debug, Clone)]
pub enum ProtocolConfig {
V3(V3Config),
V5(V5ConnectConfig),
}
impl ProtocolConfig {
pub fn v3() -> Self {
Self::V3(V3Config::new())
}
pub fn v5() -> Self {
Self::V5(V5ConnectConfig::new())
}
pub fn protocol_version(&self) -> &'static str {
match self {
Self::V3(config) => config.protocol_version(),
Self::V5(config) => config.protocol_version(),
}
}
pub fn validate(&self) -> Result<(), crate::error::ConfigError> {
match self {
Self::V3(config) => config.validate(),
Self::V5(config) => config.validate(),
}
}
pub fn connection_properties(&self) -> ProtocolProperties {
match self {
Self::V3(config) => config.connection_properties(),
Self::V5(config) => config.connection_properties(),
}
}
}