mqtt5_protocol/protocol/v5/
properties.rs

1use crate::encoding::{
2    decode_binary, decode_string, decode_variable_int, encode_binary, encode_string,
3    encode_variable_int,
4};
5use crate::error::{MqttError, Result};
6use bytes::{Buf, BufMut, Bytes};
7use std::collections::HashMap;
8
9/// MQTT v5.0 Property Identifiers
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum PropertyId {
12    // Byte properties
13    PayloadFormatIndicator = 0x01,
14    RequestProblemInformation = 0x17,
15    RequestResponseInformation = 0x19,
16    MaximumQoS = 0x24,
17    RetainAvailable = 0x25,
18    WildcardSubscriptionAvailable = 0x28,
19    SubscriptionIdentifierAvailable = 0x29,
20    SharedSubscriptionAvailable = 0x2A,
21
22    // Two Byte Integer properties
23    ServerKeepAlive = 0x13,
24    ReceiveMaximum = 0x21,
25    TopicAliasMaximum = 0x22,
26    TopicAlias = 0x23,
27
28    // Four Byte Integer properties
29    MessageExpiryInterval = 0x02,
30    SessionExpiryInterval = 0x11,
31    WillDelayInterval = 0x18,
32    MaximumPacketSize = 0x27,
33
34    // Variable Byte Integer properties
35    SubscriptionIdentifier = 0x0B,
36
37    // UTF-8 Encoded String properties
38    ContentType = 0x03,
39    ResponseTopic = 0x08,
40    AssignedClientIdentifier = 0x12,
41    AuthenticationMethod = 0x15,
42    ResponseInformation = 0x1A,
43    ServerReference = 0x1C,
44    ReasonString = 0x1F,
45
46    // Binary Data properties
47    CorrelationData = 0x09,
48    AuthenticationData = 0x16,
49
50    // UTF-8 String Pair properties
51    UserProperty = 0x26,
52}
53
54impl PropertyId {
55    /// Converts a u8 to `PropertyId`
56    #[must_use]
57    pub fn from_u8(value: u8) -> Option<Self> {
58        match value {
59            0x01 => Some(Self::PayloadFormatIndicator),
60            0x02 => Some(Self::MessageExpiryInterval),
61            0x03 => Some(Self::ContentType),
62            0x08 => Some(Self::ResponseTopic),
63            0x09 => Some(Self::CorrelationData),
64            0x0B => Some(Self::SubscriptionIdentifier),
65            0x11 => Some(Self::SessionExpiryInterval),
66            0x12 => Some(Self::AssignedClientIdentifier),
67            0x13 => Some(Self::ServerKeepAlive),
68            0x15 => Some(Self::AuthenticationMethod),
69            0x16 => Some(Self::AuthenticationData),
70            0x17 => Some(Self::RequestProblemInformation),
71            0x18 => Some(Self::WillDelayInterval),
72            0x19 => Some(Self::RequestResponseInformation),
73            0x1A => Some(Self::ResponseInformation),
74            0x1C => Some(Self::ServerReference),
75            0x1F => Some(Self::ReasonString),
76            0x21 => Some(Self::ReceiveMaximum),
77            0x22 => Some(Self::TopicAliasMaximum),
78            0x23 => Some(Self::TopicAlias),
79            0x24 => Some(Self::MaximumQoS),
80            0x25 => Some(Self::RetainAvailable),
81            0x26 => Some(Self::UserProperty),
82            0x27 => Some(Self::MaximumPacketSize),
83            0x28 => Some(Self::WildcardSubscriptionAvailable),
84            0x29 => Some(Self::SubscriptionIdentifierAvailable),
85            0x2A => Some(Self::SharedSubscriptionAvailable),
86            _ => None,
87        }
88    }
89
90    /// Checks if this property can appear multiple times in a packet
91    #[must_use]
92    pub fn allows_multiple(&self) -> bool {
93        matches!(self, Self::UserProperty | Self::SubscriptionIdentifier)
94    }
95
96    /// Gets the expected value type for this property
97    #[must_use]
98    pub fn value_type(&self) -> PropertyValueType {
99        match self {
100            Self::PayloadFormatIndicator
101            | Self::RequestProblemInformation
102            | Self::RequestResponseInformation
103            | Self::MaximumQoS
104            | Self::RetainAvailable
105            | Self::WildcardSubscriptionAvailable
106            | Self::SubscriptionIdentifierAvailable
107            | Self::SharedSubscriptionAvailable => PropertyValueType::Byte,
108
109            Self::ServerKeepAlive
110            | Self::ReceiveMaximum
111            | Self::TopicAliasMaximum
112            | Self::TopicAlias => PropertyValueType::TwoByteInteger,
113
114            Self::MessageExpiryInterval
115            | Self::SessionExpiryInterval
116            | Self::WillDelayInterval
117            | Self::MaximumPacketSize => PropertyValueType::FourByteInteger,
118
119            Self::SubscriptionIdentifier => PropertyValueType::VariableByteInteger,
120
121            Self::ContentType
122            | Self::ResponseTopic
123            | Self::AssignedClientIdentifier
124            | Self::AuthenticationMethod
125            | Self::ResponseInformation
126            | Self::ServerReference
127            | Self::ReasonString => PropertyValueType::Utf8String,
128
129            Self::CorrelationData | Self::AuthenticationData => PropertyValueType::BinaryData,
130
131            Self::UserProperty => PropertyValueType::Utf8StringPair,
132        }
133    }
134}
135
136/// Property value types
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum PropertyValueType {
139    Byte,
140    TwoByteInteger,
141    FourByteInteger,
142    VariableByteInteger,
143    BinaryData,
144    Utf8String,
145    Utf8StringPair,
146}
147
148/// Property value storage
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum PropertyValue {
151    Byte(u8),
152    TwoByteInteger(u16),
153    FourByteInteger(u32),
154    VariableByteInteger(u32),
155    BinaryData(Bytes),
156    Utf8String(String),
157    Utf8StringPair(String, String),
158}
159
160impl PropertyValue {
161    /// Gets the value type
162    #[must_use]
163    pub fn value_type(&self) -> PropertyValueType {
164        match self {
165            Self::Byte(_) => PropertyValueType::Byte,
166            Self::TwoByteInteger(_) => PropertyValueType::TwoByteInteger,
167            Self::FourByteInteger(_) => PropertyValueType::FourByteInteger,
168            Self::VariableByteInteger(_) => PropertyValueType::VariableByteInteger,
169            Self::BinaryData(_) => PropertyValueType::BinaryData,
170            Self::Utf8String(_) => PropertyValueType::Utf8String,
171            Self::Utf8StringPair(_, _) => PropertyValueType::Utf8StringPair,
172        }
173    }
174
175    /// Validates that this value matches the expected type for a property
176    #[must_use]
177    pub fn matches_type(&self, expected: PropertyValueType) -> bool {
178        self.value_type() == expected
179    }
180}
181
182/// Container for MQTT v5.0 properties
183#[derive(Debug, Clone, Default, PartialEq, Eq)]
184pub struct Properties {
185    properties: HashMap<PropertyId, Vec<PropertyValue>>,
186}
187
188impl Properties {
189    /// Creates a new empty properties container
190    #[must_use]
191    pub fn new() -> Self {
192        Self {
193            properties: HashMap::new(),
194        }
195    }
196
197    /// Adds a property value
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if:
202    /// - The value type doesn't match the property's expected type
203    /// - The property doesn't allow multiple values and already exists
204    ///
205    /// # Errors
206    ///
207    /// Returns an error if the operation fails
208    pub fn add(&mut self, id: PropertyId, value: PropertyValue) -> Result<()> {
209        // Validate value type
210        if !value.matches_type(id.value_type()) {
211            return Err(MqttError::ProtocolError(format!(
212                "Property {:?} expects type {:?}, got {:?}",
213                id,
214                id.value_type(),
215                value.value_type()
216            )));
217        }
218
219        // Check if property allows multiple values
220        if !id.allows_multiple() && self.properties.contains_key(&id) {
221            return Err(MqttError::DuplicatePropertyId(id as u8));
222        }
223
224        self.properties.entry(id).or_default().push(value);
225        Ok(())
226    }
227
228    /// Gets a single property value
229    #[must_use]
230    pub fn get(&self, id: PropertyId) -> Option<&PropertyValue> {
231        self.properties.get(&id).and_then(|v| v.first())
232    }
233
234    /// Gets all values for a property (for properties that allow multiple values)
235    #[must_use]
236    pub fn get_all(&self, id: PropertyId) -> Option<&[PropertyValue]> {
237        self.properties.get(&id).map(std::vec::Vec::as_slice)
238    }
239
240    /// Checks if a property is present
241    #[must_use]
242    pub fn contains(&self, id: PropertyId) -> bool {
243        self.properties.contains_key(&id)
244    }
245
246    /// Returns the number of properties (counting multi-value properties as one)
247    #[must_use]
248    pub fn len(&self) -> usize {
249        self.properties.len()
250    }
251
252    /// Checks if there are no properties
253    #[must_use]
254    pub fn is_empty(&self) -> bool {
255        self.properties.is_empty()
256    }
257
258    /// Iterates over all properties and their values
259    pub fn iter(&self) -> impl Iterator<Item = (PropertyId, &PropertyValue)> + '_ {
260        self.properties
261            .iter()
262            .flat_map(|(id, values)| values.iter().map(move |value| (*id, value)))
263    }
264
265    /// Encodes all properties to the buffer
266    ///
267    /// # Errors
268    ///
269    /// Returns an error if encoding fails
270    ///
271    /// # Errors
272    ///
273    /// Returns an error if the operation fails
274    pub fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
275        // First calculate the total properties length
276        let mut props_buf = Vec::new();
277        self.encode_properties(&mut props_buf)?;
278
279        // Write properties length as variable byte integer
280        encode_variable_int(
281            buf,
282            props_buf
283                .len()
284                .try_into()
285                .map_err(|_| MqttError::PacketTooLarge {
286                    size: props_buf.len(),
287                    max: u32::MAX as usize,
288                })?,
289        )?;
290
291        // Write properties data
292        buf.put_slice(&props_buf);
293        Ok(())
294    }
295
296    /// Encodes properties without the length prefix
297    ///
298    /// # Errors
299    ///
300    /// Returns an error if the operation fails
301    fn encode_properties<B: BufMut>(&self, buf: &mut B) -> Result<()> {
302        // Sort properties by ID for consistent encoding
303        let mut sorted_props: Vec<_> = self.properties.iter().collect();
304        sorted_props.sort_by_key(|(id, _)| **id as u8);
305
306        for (id, values) in sorted_props {
307            for value in values {
308                // Write property identifier as variable byte integer
309                encode_variable_int(buf, u32::from(*id as u8))?;
310
311                // Write property value
312                match value {
313                    PropertyValue::Byte(v) => buf.put_u8(*v),
314                    PropertyValue::TwoByteInteger(v) => buf.put_u16(*v),
315                    PropertyValue::FourByteInteger(v) => buf.put_u32(*v),
316                    PropertyValue::VariableByteInteger(v) => encode_variable_int(buf, *v)?,
317                    PropertyValue::BinaryData(v) => encode_binary(buf, v)?,
318                    PropertyValue::Utf8String(v) => encode_string(buf, v)?,
319                    PropertyValue::Utf8StringPair(k, v) => {
320                        encode_string(buf, k)?;
321                        encode_string(buf, v)?;
322                    }
323                }
324            }
325        }
326        Ok(())
327    }
328
329    /// Decodes properties from the buffer
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if:
334    /// - Decoding fails
335    /// - Invalid property ID
336    /// - Type mismatch
337    /// - Duplicate property that doesn't allow multiples
338    ///
339    /// # Errors
340    ///
341    /// Returns an error if the operation fails
342    pub fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
343        // Read properties length
344        let props_len = decode_variable_int(buf)? as usize;
345
346        // Create a sub-buffer for properties
347        if buf.remaining() < props_len {
348            return Err(MqttError::MalformedPacket(format!(
349                "Insufficient data for properties: expected {}, got {}",
350                props_len,
351                buf.remaining()
352            )));
353        }
354
355        let mut props_buf = buf.copy_to_bytes(props_len);
356        let mut properties = Self::new();
357
358        while props_buf.has_remaining() {
359            // Read property identifier
360            let id_val = decode_variable_int(&mut props_buf)?;
361            let id_byte = u8::try_from(id_val).map_err(|_| MqttError::InvalidPropertyId(255))?;
362
363            let id = PropertyId::from_u8(id_byte).ok_or(MqttError::InvalidPropertyId(id_byte))?;
364
365            // Read property value based on type
366            let value = match id.value_type() {
367                PropertyValueType::Byte => {
368                    if !props_buf.has_remaining() {
369                        return Err(MqttError::MalformedPacket(
370                            "Insufficient data for byte property".to_string(),
371                        ));
372                    }
373                    PropertyValue::Byte(props_buf.get_u8())
374                }
375                PropertyValueType::TwoByteInteger => {
376                    if props_buf.remaining() < 2 {
377                        return Err(MqttError::MalformedPacket(
378                            "Insufficient data for two-byte integer property".to_string(),
379                        ));
380                    }
381                    PropertyValue::TwoByteInteger(props_buf.get_u16())
382                }
383                PropertyValueType::FourByteInteger => {
384                    if props_buf.remaining() < 4 {
385                        return Err(MqttError::MalformedPacket(
386                            "Insufficient data for four-byte integer property".to_string(),
387                        ));
388                    }
389                    PropertyValue::FourByteInteger(props_buf.get_u32())
390                }
391                PropertyValueType::VariableByteInteger => {
392                    PropertyValue::VariableByteInteger(decode_variable_int(&mut props_buf)?)
393                }
394                PropertyValueType::BinaryData => {
395                    PropertyValue::BinaryData(decode_binary(&mut props_buf)?)
396                }
397                PropertyValueType::Utf8String => {
398                    PropertyValue::Utf8String(decode_string(&mut props_buf)?)
399                }
400                PropertyValueType::Utf8StringPair => {
401                    let key = decode_string(&mut props_buf)?;
402                    let value = decode_string(&mut props_buf)?;
403                    PropertyValue::Utf8StringPair(key, value)
404                }
405            };
406
407            properties.add(id, value)?;
408        }
409
410        Ok(properties)
411    }
412
413    /// Calculates the encoded length of all properties (including length prefix)
414    #[must_use]
415    pub fn encoded_len(&self) -> usize {
416        let props_len = self.properties_encoded_len();
417        crate::encoding::variable_int_len(props_len.try_into().unwrap_or(u32::MAX)) + props_len
418    }
419
420    /// Calculates the encoded length of properties without length prefix
421    #[must_use]
422    fn properties_encoded_len(&self) -> usize {
423        let mut len = 0;
424
425        for (id, values) in &self.properties {
426            for value in values {
427                // Property ID length
428                len += crate::encoding::variable_int_len(u32::from(*id as u8));
429
430                // Property value length
431                len += match value {
432                    PropertyValue::Byte(_) => 1,
433                    PropertyValue::TwoByteInteger(_) => 2,
434                    PropertyValue::FourByteInteger(_) => 4,
435                    PropertyValue::VariableByteInteger(v) => crate::encoding::variable_int_len(*v),
436                    PropertyValue::BinaryData(v) => crate::encoding::binary_len(v),
437                    PropertyValue::Utf8String(v) => crate::encoding::string_len(v),
438                    PropertyValue::Utf8StringPair(k, v) => {
439                        crate::encoding::string_len(k) + crate::encoding::string_len(v)
440                    }
441                };
442            }
443        }
444
445        len
446    }
447
448    // Type-safe property setters that cannot fail
449
450    /// Sets the payload format indicator (0 = unspecified bytes, 1 = UTF-8)
451    pub fn set_payload_format_indicator(&mut self, is_utf8: bool) {
452        self.properties
453            .entry(PropertyId::PayloadFormatIndicator)
454            .or_default()
455            .push(PropertyValue::Byte(u8::from(is_utf8)));
456    }
457
458    /// Sets the message expiry interval in seconds
459    pub fn set_message_expiry_interval(&mut self, seconds: u32) {
460        self.properties
461            .entry(PropertyId::MessageExpiryInterval)
462            .or_default()
463            .push(PropertyValue::FourByteInteger(seconds));
464    }
465
466    /// Gets the message expiry interval in seconds
467    pub fn get_message_expiry_interval(&self) -> Option<u32> {
468        self.properties
469            .get(&PropertyId::MessageExpiryInterval)
470            .and_then(|values| values.first())
471            .and_then(|value| {
472                if let PropertyValue::FourByteInteger(v) = value {
473                    Some(*v)
474                } else {
475                    None
476                }
477            })
478    }
479
480    /// Sets the topic alias
481    pub fn set_topic_alias(&mut self, alias: u16) {
482        self.properties
483            .entry(PropertyId::TopicAlias)
484            .or_default()
485            .push(PropertyValue::TwoByteInteger(alias));
486    }
487
488    /// Gets the topic alias
489    pub fn get_topic_alias(&self) -> Option<u16> {
490        self.properties
491            .get(&PropertyId::TopicAlias)
492            .and_then(|values| values.first())
493            .and_then(|value| {
494                if let PropertyValue::TwoByteInteger(v) = value {
495                    Some(*v)
496                } else {
497                    None
498                }
499            })
500    }
501
502    /// Sets the response topic
503    pub fn set_response_topic(&mut self, topic: String) {
504        self.properties
505            .entry(PropertyId::ResponseTopic)
506            .or_default()
507            .push(PropertyValue::Utf8String(topic));
508    }
509
510    /// Sets the correlation data
511    pub fn set_correlation_data(&mut self, data: Bytes) {
512        self.properties
513            .entry(PropertyId::CorrelationData)
514            .or_default()
515            .push(PropertyValue::BinaryData(data));
516    }
517
518    /// Adds a user property (can be called multiple times)
519    pub fn add_user_property(&mut self, key: String, value: String) {
520        self.properties
521            .entry(PropertyId::UserProperty)
522            .or_default()
523            .push(PropertyValue::Utf8StringPair(key, value));
524    }
525
526    /// Sets the subscription identifier
527    pub fn set_subscription_identifier(&mut self, id: u32) {
528        self.properties
529            .entry(PropertyId::SubscriptionIdentifier)
530            .or_default()
531            .push(PropertyValue::VariableByteInteger(id));
532    }
533
534    #[must_use]
535    pub fn get_subscription_identifier(&self) -> Option<u32> {
536        self.properties
537            .get(&PropertyId::SubscriptionIdentifier)
538            .and_then(|values| values.first())
539            .and_then(|value| {
540                if let PropertyValue::VariableByteInteger(v) = value {
541                    Some(*v)
542                } else {
543                    None
544                }
545            })
546    }
547
548    /// Sets the session expiry interval
549    pub fn set_session_expiry_interval(&mut self, seconds: u32) {
550        self.properties
551            .entry(PropertyId::SessionExpiryInterval)
552            .or_default()
553            .push(PropertyValue::FourByteInteger(seconds));
554    }
555
556    /// Gets the session expiry interval
557    pub fn get_session_expiry_interval(&self) -> Option<u32> {
558        self.properties
559            .get(&PropertyId::SessionExpiryInterval)
560            .and_then(|values| values.first())
561            .and_then(|value| {
562                if let PropertyValue::FourByteInteger(v) = value {
563                    Some(*v)
564                } else {
565                    None
566                }
567            })
568    }
569
570    /// Sets the assigned client identifier
571    pub fn set_assigned_client_identifier(&mut self, id: String) {
572        self.properties
573            .entry(PropertyId::AssignedClientIdentifier)
574            .or_default()
575            .push(PropertyValue::Utf8String(id));
576    }
577
578    /// Sets the server keep alive
579    pub fn set_server_keep_alive(&mut self, seconds: u16) {
580        self.properties
581            .entry(PropertyId::ServerKeepAlive)
582            .or_default()
583            .push(PropertyValue::TwoByteInteger(seconds));
584    }
585
586    /// Sets the authentication method
587    pub fn set_authentication_method(&mut self, method: String) {
588        self.properties
589            .entry(PropertyId::AuthenticationMethod)
590            .or_default()
591            .push(PropertyValue::Utf8String(method));
592    }
593
594    /// Sets the authentication data
595    pub fn set_authentication_data(&mut self, data: Bytes) {
596        self.properties
597            .entry(PropertyId::AuthenticationData)
598            .or_default()
599            .push(PropertyValue::BinaryData(data));
600    }
601
602    pub fn get_authentication_method(&self) -> Option<&String> {
603        self.properties
604            .get(&PropertyId::AuthenticationMethod)
605            .and_then(|values| values.first())
606            .and_then(|value| {
607                if let PropertyValue::Utf8String(s) = value {
608                    Some(s)
609                } else {
610                    None
611                }
612            })
613    }
614
615    pub fn get_authentication_data(&self) -> Option<&[u8]> {
616        self.properties
617            .get(&PropertyId::AuthenticationData)
618            .and_then(|values| values.first())
619            .and_then(|value| {
620                if let PropertyValue::BinaryData(b) = value {
621                    Some(b.as_ref())
622                } else {
623                    None
624                }
625            })
626    }
627
628    /// Sets request problem information
629    pub fn set_request_problem_information(&mut self, request: bool) {
630        self.properties
631            .entry(PropertyId::RequestProblemInformation)
632            .or_default()
633            .push(PropertyValue::Byte(u8::from(request)));
634    }
635
636    /// Gets request problem information
637    pub fn get_request_problem_information(&self) -> Option<bool> {
638        self.properties
639            .get(&PropertyId::RequestProblemInformation)
640            .and_then(|values| values.first())
641            .and_then(|value| {
642                if let PropertyValue::Byte(v) = value {
643                    Some(*v != 0)
644                } else {
645                    None
646                }
647            })
648    }
649
650    /// Sets the will delay interval
651    pub fn set_will_delay_interval(&mut self, seconds: u32) {
652        self.properties
653            .entry(PropertyId::WillDelayInterval)
654            .or_default()
655            .push(PropertyValue::FourByteInteger(seconds));
656    }
657
658    /// Sets request response information
659    pub fn set_request_response_information(&mut self, request: bool) {
660        self.properties
661            .entry(PropertyId::RequestResponseInformation)
662            .or_default()
663            .push(PropertyValue::Byte(u8::from(request)));
664    }
665
666    /// Gets request response information
667    pub fn get_request_response_information(&self) -> Option<bool> {
668        self.properties
669            .get(&PropertyId::RequestResponseInformation)
670            .and_then(|values| values.first())
671            .and_then(|value| {
672                if let PropertyValue::Byte(v) = value {
673                    Some(*v != 0)
674                } else {
675                    None
676                }
677            })
678    }
679
680    /// Sets the response information
681    pub fn set_response_information(&mut self, info: String) {
682        self.properties
683            .entry(PropertyId::ResponseInformation)
684            .or_default()
685            .push(PropertyValue::Utf8String(info));
686    }
687
688    /// Sets the server reference
689    pub fn set_server_reference(&mut self, reference: String) {
690        self.properties
691            .entry(PropertyId::ServerReference)
692            .or_default()
693            .push(PropertyValue::Utf8String(reference));
694    }
695
696    /// Sets the reason string
697    pub fn set_reason_string(&mut self, reason: String) {
698        self.properties
699            .entry(PropertyId::ReasonString)
700            .or_default()
701            .push(PropertyValue::Utf8String(reason));
702    }
703
704    /// Sets the receive maximum
705    pub fn set_receive_maximum(&mut self, max: u16) {
706        self.properties
707            .entry(PropertyId::ReceiveMaximum)
708            .or_default()
709            .push(PropertyValue::TwoByteInteger(max));
710    }
711
712    /// Gets the receive maximum
713    pub fn get_receive_maximum(&self) -> Option<u16> {
714        self.properties
715            .get(&PropertyId::ReceiveMaximum)
716            .and_then(|values| values.first())
717            .and_then(|value| {
718                if let PropertyValue::TwoByteInteger(v) = value {
719                    Some(*v)
720                } else {
721                    None
722                }
723            })
724    }
725
726    /// Sets the topic alias maximum
727    pub fn set_topic_alias_maximum(&mut self, max: u16) {
728        self.properties
729            .entry(PropertyId::TopicAliasMaximum)
730            .or_default()
731            .push(PropertyValue::TwoByteInteger(max));
732    }
733
734    /// Gets the topic alias maximum
735    pub fn get_topic_alias_maximum(&self) -> Option<u16> {
736        self.properties
737            .get(&PropertyId::TopicAliasMaximum)
738            .and_then(|values| values.first())
739            .and_then(|value| {
740                if let PropertyValue::TwoByteInteger(v) = value {
741                    Some(*v)
742                } else {
743                    None
744                }
745            })
746    }
747
748    /// Sets the maximum `QoS`
749    pub fn set_maximum_qos(&mut self, qos: u8) {
750        self.properties
751            .entry(PropertyId::MaximumQoS)
752            .or_default()
753            .push(PropertyValue::Byte(qos));
754    }
755
756    /// Sets retain available
757    pub fn set_retain_available(&mut self, available: bool) {
758        self.properties
759            .entry(PropertyId::RetainAvailable)
760            .or_default()
761            .push(PropertyValue::Byte(u8::from(available)));
762    }
763
764    /// Sets the maximum packet size
765    pub fn set_maximum_packet_size(&mut self, size: u32) {
766        self.properties
767            .entry(PropertyId::MaximumPacketSize)
768            .or_default()
769            .push(PropertyValue::FourByteInteger(size));
770    }
771
772    /// Sets wildcard subscription available
773    pub fn set_wildcard_subscription_available(&mut self, available: bool) {
774        self.properties
775            .entry(PropertyId::WildcardSubscriptionAvailable)
776            .or_default()
777            .push(PropertyValue::Byte(u8::from(available)));
778    }
779
780    /// Sets subscription identifier available
781    pub fn set_subscription_identifier_available(&mut self, available: bool) {
782        self.properties
783            .entry(PropertyId::SubscriptionIdentifierAvailable)
784            .or_default()
785            .push(PropertyValue::Byte(u8::from(available)));
786    }
787
788    /// Sets shared subscription available
789    pub fn set_shared_subscription_available(&mut self, available: bool) {
790        self.properties
791            .entry(PropertyId::SharedSubscriptionAvailable)
792            .or_default()
793            .push(PropertyValue::Byte(u8::from(available)));
794    }
795
796    /// Gets the maximum QoS
797    pub fn get_maximum_qos(&self) -> Option<u8> {
798        self.properties
799            .get(&PropertyId::MaximumQoS)
800            .and_then(|values| values.first())
801            .and_then(|value| {
802                if let PropertyValue::Byte(v) = value {
803                    Some(*v)
804                } else {
805                    None
806                }
807            })
808    }
809
810    /// Sets the content type
811    pub fn set_content_type(&mut self, content_type: String) {
812        self.properties
813            .entry(PropertyId::ContentType)
814            .or_default()
815            .push(PropertyValue::Utf8String(content_type));
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    use super::*;
822    use bytes::BytesMut;
823
824    #[test]
825    fn test_property_id_from_u8() {
826        assert_eq!(
827            PropertyId::from_u8(0x01),
828            Some(PropertyId::PayloadFormatIndicator)
829        );
830        assert_eq!(PropertyId::from_u8(0x26), Some(PropertyId::UserProperty));
831        assert_eq!(
832            PropertyId::from_u8(0x2A),
833            Some(PropertyId::SharedSubscriptionAvailable)
834        );
835        assert_eq!(PropertyId::from_u8(0xFF), None);
836        assert_eq!(PropertyId::from_u8(0x00), None);
837    }
838
839    #[test]
840    fn test_property_allows_multiple() {
841        assert!(PropertyId::UserProperty.allows_multiple());
842        assert!(PropertyId::SubscriptionIdentifier.allows_multiple());
843        assert!(!PropertyId::PayloadFormatIndicator.allows_multiple());
844        assert!(!PropertyId::SessionExpiryInterval.allows_multiple());
845    }
846
847    #[test]
848    fn test_property_value_type() {
849        assert_eq!(
850            PropertyId::PayloadFormatIndicator.value_type(),
851            PropertyValueType::Byte
852        );
853        assert_eq!(
854            PropertyId::TopicAlias.value_type(),
855            PropertyValueType::TwoByteInteger
856        );
857        assert_eq!(
858            PropertyId::SessionExpiryInterval.value_type(),
859            PropertyValueType::FourByteInteger
860        );
861        assert_eq!(
862            PropertyId::SubscriptionIdentifier.value_type(),
863            PropertyValueType::VariableByteInteger
864        );
865        assert_eq!(
866            PropertyId::ContentType.value_type(),
867            PropertyValueType::Utf8String
868        );
869        assert_eq!(
870            PropertyId::CorrelationData.value_type(),
871            PropertyValueType::BinaryData
872        );
873        assert_eq!(
874            PropertyId::UserProperty.value_type(),
875            PropertyValueType::Utf8StringPair
876        );
877    }
878
879    #[test]
880    fn test_property_value_matches_type() {
881        let byte_val = PropertyValue::Byte(1);
882        assert!(byte_val.matches_type(PropertyValueType::Byte));
883        assert!(!byte_val.matches_type(PropertyValueType::TwoByteInteger));
884
885        let string_val = PropertyValue::Utf8String("test".to_string());
886        assert!(string_val.matches_type(PropertyValueType::Utf8String));
887        assert!(!string_val.matches_type(PropertyValueType::BinaryData));
888    }
889
890    #[test]
891    fn test_properties_add_valid() {
892        let mut props = Properties::new();
893
894        // Add single-value properties
895        props
896            .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
897            .unwrap();
898        props
899            .add(
900                PropertyId::SessionExpiryInterval,
901                PropertyValue::FourByteInteger(3600),
902            )
903            .unwrap();
904        props
905            .add(
906                PropertyId::ContentType,
907                PropertyValue::Utf8String("text/plain".to_string()),
908            )
909            .unwrap();
910
911        // Add multi-value property
912        props
913            .add(
914                PropertyId::UserProperty,
915                PropertyValue::Utf8StringPair("key1".to_string(), "value1".to_string()),
916            )
917            .unwrap();
918        props
919            .add(
920                PropertyId::UserProperty,
921                PropertyValue::Utf8StringPair("key2".to_string(), "value2".to_string()),
922            )
923            .unwrap();
924
925        assert_eq!(props.len(), 4); // 4 unique property IDs
926    }
927
928    #[test]
929    fn test_properties_add_type_mismatch() {
930        let mut props = Properties::new();
931
932        // Try to add wrong type
933        let result = props.add(
934            PropertyId::PayloadFormatIndicator,
935            PropertyValue::FourByteInteger(100),
936        );
937        assert!(result.is_err());
938    }
939
940    #[test]
941    fn test_properties_add_duplicate_single_value() {
942        let mut props = Properties::new();
943
944        // Add first time - should succeed
945        props
946            .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(0))
947            .unwrap();
948
949        // Add second time - should fail
950        let result = props.add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1));
951        assert!(result.is_err());
952    }
953
954    #[test]
955    fn test_properties_get() {
956        let mut props = Properties::new();
957        props
958            .add(
959                PropertyId::ContentType,
960                PropertyValue::Utf8String("text/html".to_string()),
961            )
962            .unwrap();
963
964        let value = props.get(PropertyId::ContentType).unwrap();
965        match value {
966            PropertyValue::Utf8String(s) => assert_eq!(s, "text/html"),
967            _ => panic!("Wrong value type"),
968        }
969
970        assert!(props.get(PropertyId::ResponseTopic).is_none());
971    }
972
973    #[test]
974    fn test_properties_get_all() {
975        let mut props = Properties::new();
976
977        // Add multiple user properties
978        props
979            .add(
980                PropertyId::UserProperty,
981                PropertyValue::Utf8StringPair("k1".to_string(), "v1".to_string()),
982            )
983            .unwrap();
984        props
985            .add(
986                PropertyId::UserProperty,
987                PropertyValue::Utf8StringPair("k2".to_string(), "v2".to_string()),
988            )
989            .unwrap();
990
991        let values = props.get_all(PropertyId::UserProperty).unwrap();
992        assert_eq!(values.len(), 2);
993    }
994
995    #[test]
996    fn test_properties_encode_decode_empty() {
997        let props = Properties::new();
998        let mut buf = BytesMut::new();
999
1000        props.encode(&mut buf).unwrap();
1001        assert_eq!(buf[0], 0); // Empty properties = 0 length
1002
1003        let decoded = Properties::decode(&mut buf).unwrap();
1004        assert!(decoded.is_empty());
1005    }
1006
1007    #[test]
1008    fn test_properties_encode_decode_single_values() {
1009        let mut props = Properties::new();
1010
1011        // Add various property types
1012        props
1013            .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
1014            .unwrap();
1015        props
1016            .add(PropertyId::TopicAlias, PropertyValue::TwoByteInteger(100))
1017            .unwrap();
1018        props
1019            .add(
1020                PropertyId::SessionExpiryInterval,
1021                PropertyValue::FourByteInteger(3600),
1022            )
1023            .unwrap();
1024        props
1025            .add(
1026                PropertyId::SubscriptionIdentifier,
1027                PropertyValue::VariableByteInteger(123),
1028            )
1029            .unwrap();
1030        props
1031            .add(
1032                PropertyId::ContentType,
1033                PropertyValue::Utf8String("text/plain".to_string()),
1034            )
1035            .unwrap();
1036        props
1037            .add(
1038                PropertyId::CorrelationData,
1039                PropertyValue::BinaryData(Bytes::from(vec![1, 2, 3, 4])),
1040            )
1041            .unwrap();
1042        props
1043            .add(
1044                PropertyId::UserProperty,
1045                PropertyValue::Utf8StringPair("key".to_string(), "value".to_string()),
1046            )
1047            .unwrap();
1048
1049        let mut buf = BytesMut::new();
1050        props.encode(&mut buf).unwrap();
1051
1052        let decoded = Properties::decode(&mut buf).unwrap();
1053        assert_eq!(decoded.len(), props.len());
1054
1055        // Verify each property
1056        match decoded.get(PropertyId::PayloadFormatIndicator).unwrap() {
1057            PropertyValue::Byte(v) => assert_eq!(*v, 1),
1058            _ => panic!("Wrong type"),
1059        }
1060
1061        match decoded.get(PropertyId::TopicAlias).unwrap() {
1062            PropertyValue::TwoByteInteger(v) => assert_eq!(*v, 100),
1063            _ => panic!("Wrong type"),
1064        }
1065
1066        match decoded.get(PropertyId::ContentType).unwrap() {
1067            PropertyValue::Utf8String(v) => assert_eq!(v, "text/plain"),
1068            _ => panic!("Wrong type"),
1069        }
1070    }
1071
1072    #[test]
1073    fn test_properties_encode_decode_multiple_values() {
1074        let mut props = Properties::new();
1075
1076        // Add multiple user properties
1077        props
1078            .add(
1079                PropertyId::UserProperty,
1080                PropertyValue::Utf8StringPair("env".to_string(), "prod".to_string()),
1081            )
1082            .unwrap();
1083        props
1084            .add(
1085                PropertyId::UserProperty,
1086                PropertyValue::Utf8StringPair("version".to_string(), "1.0".to_string()),
1087            )
1088            .unwrap();
1089
1090        // Add multiple subscription identifiers
1091        props
1092            .add(
1093                PropertyId::SubscriptionIdentifier,
1094                PropertyValue::VariableByteInteger(10),
1095            )
1096            .unwrap();
1097        props
1098            .add(
1099                PropertyId::SubscriptionIdentifier,
1100                PropertyValue::VariableByteInteger(20),
1101            )
1102            .unwrap();
1103
1104        let mut buf = BytesMut::new();
1105        props.encode(&mut buf).unwrap();
1106
1107        let decoded = Properties::decode(&mut buf).unwrap();
1108
1109        let user_props = decoded.get_all(PropertyId::UserProperty).unwrap();
1110        assert_eq!(user_props.len(), 2);
1111
1112        let sub_ids = decoded.get_all(PropertyId::SubscriptionIdentifier).unwrap();
1113        assert_eq!(sub_ids.len(), 2);
1114    }
1115
1116    #[test]
1117    fn test_properties_decode_invalid_property_id() {
1118        let mut buf = BytesMut::new();
1119        buf.put_u8(1); // Properties length
1120        buf.put_u8(0xFF); // Invalid property ID
1121
1122        let result = Properties::decode(&mut buf);
1123        assert!(result.is_err());
1124    }
1125
1126    #[test]
1127    fn test_properties_decode_insufficient_data() {
1128        let mut buf = BytesMut::new();
1129        buf.put_u8(10); // Claims 10 bytes but buffer is empty
1130
1131        let result = Properties::decode(&mut buf);
1132        assert!(result.is_err());
1133    }
1134
1135    #[test]
1136    fn test_properties_encoded_len() {
1137        let mut props = Properties::new();
1138        props
1139            .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
1140            .unwrap();
1141        props
1142            .add(
1143                PropertyId::ContentType,
1144                PropertyValue::Utf8String("test".to_string()),
1145            )
1146            .unwrap();
1147
1148        let mut buf = BytesMut::new();
1149        props.encode(&mut buf).unwrap();
1150
1151        assert_eq!(props.encoded_len(), buf.len());
1152    }
1153
1154    #[test]
1155    fn test_all_property_ids_have_correct_types() {
1156        // Test that all property IDs in from_u8 have matching value types
1157        for id in 0u8..=0x2A {
1158            if let Some(prop_id) = PropertyId::from_u8(id) {
1159                // Just verify it has a value type (no panic)
1160                let _ = prop_id.value_type();
1161            }
1162        }
1163    }
1164}