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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum PropertyId {
12 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 ServerKeepAlive = 0x13,
24 ReceiveMaximum = 0x21,
25 TopicAliasMaximum = 0x22,
26 TopicAlias = 0x23,
27
28 MessageExpiryInterval = 0x02,
30 SessionExpiryInterval = 0x11,
31 WillDelayInterval = 0x18,
32 MaximumPacketSize = 0x27,
33
34 SubscriptionIdentifier = 0x0B,
36
37 ContentType = 0x03,
39 ResponseTopic = 0x08,
40 AssignedClientIdentifier = 0x12,
41 AuthenticationMethod = 0x15,
42 ResponseInformation = 0x1A,
43 ServerReference = 0x1C,
44 ReasonString = 0x1F,
45
46 CorrelationData = 0x09,
48 AuthenticationData = 0x16,
49
50 UserProperty = 0x26,
52}
53
54impl PropertyId {
55 #[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 #[must_use]
92 pub fn allows_multiple(&self) -> bool {
93 matches!(self, Self::UserProperty | Self::SubscriptionIdentifier)
94 }
95
96 #[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#[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#[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 #[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 #[must_use]
177 pub fn matches_type(&self, expected: PropertyValueType) -> bool {
178 self.value_type() == expected
179 }
180}
181
182#[derive(Debug, Clone, Default, PartialEq, Eq)]
184pub struct Properties {
185 properties: HashMap<PropertyId, Vec<PropertyValue>>,
186}
187
188impl Properties {
189 #[must_use]
191 pub fn new() -> Self {
192 Self {
193 properties: HashMap::new(),
194 }
195 }
196
197 pub fn add(&mut self, id: PropertyId, value: PropertyValue) -> Result<()> {
209 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 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 #[must_use]
230 pub fn get(&self, id: PropertyId) -> Option<&PropertyValue> {
231 self.properties.get(&id).and_then(|v| v.first())
232 }
233
234 #[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 #[must_use]
242 pub fn contains(&self, id: PropertyId) -> bool {
243 self.properties.contains_key(&id)
244 }
245
246 #[must_use]
248 pub fn len(&self) -> usize {
249 self.properties.len()
250 }
251
252 #[must_use]
254 pub fn is_empty(&self) -> bool {
255 self.properties.is_empty()
256 }
257
258 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 pub fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
275 let mut props_buf = Vec::new();
277 self.encode_properties(&mut props_buf)?;
278
279 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 buf.put_slice(&props_buf);
293 Ok(())
294 }
295
296 fn encode_properties<B: BufMut>(&self, buf: &mut B) -> Result<()> {
302 for (id, values) in &self.properties {
303 for value in values {
304 encode_variable_int(buf, u32::from(*id as u8))?;
305
306 match value {
307 PropertyValue::Byte(v) => buf.put_u8(*v),
308 PropertyValue::TwoByteInteger(v) => buf.put_u16(*v),
309 PropertyValue::FourByteInteger(v) => buf.put_u32(*v),
310 PropertyValue::VariableByteInteger(v) => encode_variable_int(buf, *v)?,
311 PropertyValue::BinaryData(v) => encode_binary(buf, v)?,
312 PropertyValue::Utf8String(v) => encode_string(buf, v)?,
313 PropertyValue::Utf8StringPair(k, v) => {
314 encode_string(buf, k)?;
315 encode_string(buf, v)?;
316 }
317 }
318 }
319 }
320 Ok(())
321 }
322
323 pub fn encode_direct<B: BufMut>(&self, buf: &mut B) -> Result<()> {
326 let props_len = self.properties_encoded_len();
327 encode_variable_int(
328 buf,
329 props_len
330 .try_into()
331 .map_err(|_| MqttError::PacketTooLarge {
332 size: props_len,
333 max: u32::MAX as usize,
334 })?,
335 )?;
336 self.encode_properties(buf)
337 }
338
339 pub fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
353 let props_len = decode_variable_int(buf)? as usize;
355
356 if buf.remaining() < props_len {
358 return Err(MqttError::MalformedPacket(format!(
359 "Insufficient data for properties: expected {}, got {}",
360 props_len,
361 buf.remaining()
362 )));
363 }
364
365 let mut props_buf = buf.copy_to_bytes(props_len);
366 let mut properties = Self::new();
367
368 while props_buf.has_remaining() {
369 let id_val = decode_variable_int(&mut props_buf)?;
371 let id_byte = u8::try_from(id_val).map_err(|_| MqttError::InvalidPropertyId(255))?;
372
373 let id = PropertyId::from_u8(id_byte).ok_or(MqttError::InvalidPropertyId(id_byte))?;
374
375 let value = match id.value_type() {
377 PropertyValueType::Byte => {
378 if !props_buf.has_remaining() {
379 return Err(MqttError::MalformedPacket(
380 "Insufficient data for byte property".to_string(),
381 ));
382 }
383 PropertyValue::Byte(props_buf.get_u8())
384 }
385 PropertyValueType::TwoByteInteger => {
386 if props_buf.remaining() < 2 {
387 return Err(MqttError::MalformedPacket(
388 "Insufficient data for two-byte integer property".to_string(),
389 ));
390 }
391 PropertyValue::TwoByteInteger(props_buf.get_u16())
392 }
393 PropertyValueType::FourByteInteger => {
394 if props_buf.remaining() < 4 {
395 return Err(MqttError::MalformedPacket(
396 "Insufficient data for four-byte integer property".to_string(),
397 ));
398 }
399 PropertyValue::FourByteInteger(props_buf.get_u32())
400 }
401 PropertyValueType::VariableByteInteger => {
402 PropertyValue::VariableByteInteger(decode_variable_int(&mut props_buf)?)
403 }
404 PropertyValueType::BinaryData => {
405 PropertyValue::BinaryData(decode_binary(&mut props_buf)?)
406 }
407 PropertyValueType::Utf8String => {
408 PropertyValue::Utf8String(decode_string(&mut props_buf)?)
409 }
410 PropertyValueType::Utf8StringPair => {
411 let key = decode_string(&mut props_buf)?;
412 let value = decode_string(&mut props_buf)?;
413 PropertyValue::Utf8StringPair(key, value)
414 }
415 };
416
417 properties.add(id, value)?;
418 }
419
420 Ok(properties)
421 }
422
423 #[must_use]
425 pub fn encoded_len(&self) -> usize {
426 let props_len = self.properties_encoded_len();
427 crate::encoding::variable_int_len(props_len.try_into().unwrap_or(u32::MAX)) + props_len
428 }
429
430 #[must_use]
432 fn properties_encoded_len(&self) -> usize {
433 let mut len = 0;
434
435 for (id, values) in &self.properties {
436 for value in values {
437 len += crate::encoding::variable_int_len(u32::from(*id as u8));
439
440 len += match value {
442 PropertyValue::Byte(_) => 1,
443 PropertyValue::TwoByteInteger(_) => 2,
444 PropertyValue::FourByteInteger(_) => 4,
445 PropertyValue::VariableByteInteger(v) => crate::encoding::variable_int_len(*v),
446 PropertyValue::BinaryData(v) => crate::encoding::binary_len(v),
447 PropertyValue::Utf8String(v) => crate::encoding::string_len(v),
448 PropertyValue::Utf8StringPair(k, v) => {
449 crate::encoding::string_len(k) + crate::encoding::string_len(v)
450 }
451 };
452 }
453 }
454
455 len
456 }
457
458 pub fn set_payload_format_indicator(&mut self, is_utf8: bool) {
462 self.properties
463 .entry(PropertyId::PayloadFormatIndicator)
464 .or_default()
465 .push(PropertyValue::Byte(u8::from(is_utf8)));
466 }
467
468 pub fn set_message_expiry_interval(&mut self, seconds: u32) {
470 self.properties
471 .entry(PropertyId::MessageExpiryInterval)
472 .or_default()
473 .push(PropertyValue::FourByteInteger(seconds));
474 }
475
476 #[allow(clippy::must_use_candidate)]
477 pub fn get_message_expiry_interval(&self) -> Option<u32> {
478 self.properties
479 .get(&PropertyId::MessageExpiryInterval)
480 .and_then(|values| values.first())
481 .and_then(|value| {
482 if let PropertyValue::FourByteInteger(v) = value {
483 Some(*v)
484 } else {
485 None
486 }
487 })
488 }
489
490 pub fn set_topic_alias(&mut self, alias: u16) {
492 self.properties
493 .entry(PropertyId::TopicAlias)
494 .or_default()
495 .push(PropertyValue::TwoByteInteger(alias));
496 }
497
498 #[allow(clippy::must_use_candidate)]
499 pub fn get_topic_alias(&self) -> Option<u16> {
500 self.properties
501 .get(&PropertyId::TopicAlias)
502 .and_then(|values| values.first())
503 .and_then(|value| {
504 if let PropertyValue::TwoByteInteger(v) = value {
505 Some(*v)
506 } else {
507 None
508 }
509 })
510 }
511
512 pub fn set_response_topic(&mut self, topic: String) {
514 self.properties
515 .entry(PropertyId::ResponseTopic)
516 .or_default()
517 .push(PropertyValue::Utf8String(topic));
518 }
519
520 pub fn set_correlation_data(&mut self, data: Bytes) {
522 self.properties
523 .entry(PropertyId::CorrelationData)
524 .or_default()
525 .push(PropertyValue::BinaryData(data));
526 }
527
528 pub fn add_user_property(&mut self, key: String, value: String) {
530 self.properties
531 .entry(PropertyId::UserProperty)
532 .or_default()
533 .push(PropertyValue::Utf8StringPair(key, value));
534 }
535
536 pub fn set_subscription_identifier(&mut self, id: u32) {
538 self.properties
539 .entry(PropertyId::SubscriptionIdentifier)
540 .or_default()
541 .push(PropertyValue::VariableByteInteger(id));
542 }
543
544 #[must_use]
545 pub fn get_subscription_identifier(&self) -> Option<u32> {
546 self.properties
547 .get(&PropertyId::SubscriptionIdentifier)
548 .and_then(|values| values.first())
549 .and_then(|value| {
550 if let PropertyValue::VariableByteInteger(v) = value {
551 Some(*v)
552 } else {
553 None
554 }
555 })
556 }
557
558 pub fn set_session_expiry_interval(&mut self, seconds: u32) {
560 self.properties
561 .entry(PropertyId::SessionExpiryInterval)
562 .or_default()
563 .push(PropertyValue::FourByteInteger(seconds));
564 }
565
566 #[allow(clippy::must_use_candidate)]
567 pub fn get_session_expiry_interval(&self) -> Option<u32> {
568 self.properties
569 .get(&PropertyId::SessionExpiryInterval)
570 .and_then(|values| values.first())
571 .and_then(|value| {
572 if let PropertyValue::FourByteInteger(v) = value {
573 Some(*v)
574 } else {
575 None
576 }
577 })
578 }
579
580 pub fn set_assigned_client_identifier(&mut self, id: String) {
582 self.properties
583 .entry(PropertyId::AssignedClientIdentifier)
584 .or_default()
585 .push(PropertyValue::Utf8String(id));
586 }
587
588 pub fn set_server_keep_alive(&mut self, seconds: u16) {
590 self.properties
591 .entry(PropertyId::ServerKeepAlive)
592 .or_default()
593 .push(PropertyValue::TwoByteInteger(seconds));
594 }
595
596 pub fn set_authentication_method(&mut self, method: String) {
598 self.properties
599 .entry(PropertyId::AuthenticationMethod)
600 .or_default()
601 .push(PropertyValue::Utf8String(method));
602 }
603
604 pub fn set_authentication_data(&mut self, data: Bytes) {
606 self.properties
607 .entry(PropertyId::AuthenticationData)
608 .or_default()
609 .push(PropertyValue::BinaryData(data));
610 }
611
612 #[allow(clippy::must_use_candidate)]
613 pub fn get_authentication_method(&self) -> Option<&String> {
614 self.properties
615 .get(&PropertyId::AuthenticationMethod)
616 .and_then(|values| values.first())
617 .and_then(|value| {
618 if let PropertyValue::Utf8String(s) = value {
619 Some(s)
620 } else {
621 None
622 }
623 })
624 }
625
626 #[allow(clippy::must_use_candidate)]
627 pub fn get_authentication_data(&self) -> Option<&[u8]> {
628 self.properties
629 .get(&PropertyId::AuthenticationData)
630 .and_then(|values| values.first())
631 .and_then(|value| {
632 if let PropertyValue::BinaryData(b) = value {
633 Some(b.as_ref())
634 } else {
635 None
636 }
637 })
638 }
639
640 pub fn set_request_problem_information(&mut self, request: bool) {
642 self.properties
643 .entry(PropertyId::RequestProblemInformation)
644 .or_default()
645 .push(PropertyValue::Byte(u8::from(request)));
646 }
647
648 #[allow(clippy::must_use_candidate)]
649 pub fn get_request_problem_information(&self) -> Option<bool> {
650 self.properties
651 .get(&PropertyId::RequestProblemInformation)
652 .and_then(|values| values.first())
653 .and_then(|value| {
654 if let PropertyValue::Byte(v) = value {
655 Some(*v != 0)
656 } else {
657 None
658 }
659 })
660 }
661
662 pub fn set_will_delay_interval(&mut self, seconds: u32) {
664 self.properties
665 .entry(PropertyId::WillDelayInterval)
666 .or_default()
667 .push(PropertyValue::FourByteInteger(seconds));
668 }
669
670 pub fn set_request_response_information(&mut self, request: bool) {
672 self.properties
673 .entry(PropertyId::RequestResponseInformation)
674 .or_default()
675 .push(PropertyValue::Byte(u8::from(request)));
676 }
677
678 #[allow(clippy::must_use_candidate)]
679 pub fn get_request_response_information(&self) -> Option<bool> {
680 self.properties
681 .get(&PropertyId::RequestResponseInformation)
682 .and_then(|values| values.first())
683 .and_then(|value| {
684 if let PropertyValue::Byte(v) = value {
685 Some(*v != 0)
686 } else {
687 None
688 }
689 })
690 }
691
692 pub fn set_response_information(&mut self, info: String) {
694 self.properties
695 .entry(PropertyId::ResponseInformation)
696 .or_default()
697 .push(PropertyValue::Utf8String(info));
698 }
699
700 pub fn set_server_reference(&mut self, reference: String) {
702 self.properties
703 .entry(PropertyId::ServerReference)
704 .or_default()
705 .push(PropertyValue::Utf8String(reference));
706 }
707
708 pub fn set_reason_string(&mut self, reason: String) {
710 self.properties
711 .entry(PropertyId::ReasonString)
712 .or_default()
713 .push(PropertyValue::Utf8String(reason));
714 }
715
716 pub fn set_receive_maximum(&mut self, max: u16) {
718 self.properties
719 .entry(PropertyId::ReceiveMaximum)
720 .or_default()
721 .push(PropertyValue::TwoByteInteger(max));
722 }
723
724 #[allow(clippy::must_use_candidate)]
725 pub fn get_receive_maximum(&self) -> Option<u16> {
726 self.properties
727 .get(&PropertyId::ReceiveMaximum)
728 .and_then(|values| values.first())
729 .and_then(|value| {
730 if let PropertyValue::TwoByteInteger(v) = value {
731 Some(*v)
732 } else {
733 None
734 }
735 })
736 }
737
738 pub fn set_topic_alias_maximum(&mut self, max: u16) {
740 self.properties
741 .entry(PropertyId::TopicAliasMaximum)
742 .or_default()
743 .push(PropertyValue::TwoByteInteger(max));
744 }
745
746 #[allow(clippy::must_use_candidate)]
747 pub fn get_topic_alias_maximum(&self) -> Option<u16> {
748 self.properties
749 .get(&PropertyId::TopicAliasMaximum)
750 .and_then(|values| values.first())
751 .and_then(|value| {
752 if let PropertyValue::TwoByteInteger(v) = value {
753 Some(*v)
754 } else {
755 None
756 }
757 })
758 }
759
760 pub fn set_maximum_qos(&mut self, qos: u8) {
762 self.properties
763 .entry(PropertyId::MaximumQoS)
764 .or_default()
765 .push(PropertyValue::Byte(qos));
766 }
767
768 pub fn set_retain_available(&mut self, available: bool) {
770 self.properties
771 .entry(PropertyId::RetainAvailable)
772 .or_default()
773 .push(PropertyValue::Byte(u8::from(available)));
774 }
775
776 pub fn set_maximum_packet_size(&mut self, size: u32) {
778 self.properties
779 .entry(PropertyId::MaximumPacketSize)
780 .or_default()
781 .push(PropertyValue::FourByteInteger(size));
782 }
783
784 pub fn set_wildcard_subscription_available(&mut self, available: bool) {
786 self.properties
787 .entry(PropertyId::WildcardSubscriptionAvailable)
788 .or_default()
789 .push(PropertyValue::Byte(u8::from(available)));
790 }
791
792 pub fn set_subscription_identifier_available(&mut self, available: bool) {
794 self.properties
795 .entry(PropertyId::SubscriptionIdentifierAvailable)
796 .or_default()
797 .push(PropertyValue::Byte(u8::from(available)));
798 }
799
800 pub fn set_shared_subscription_available(&mut self, available: bool) {
802 self.properties
803 .entry(PropertyId::SharedSubscriptionAvailable)
804 .or_default()
805 .push(PropertyValue::Byte(u8::from(available)));
806 }
807
808 #[allow(clippy::must_use_candidate)]
809 pub fn get_maximum_qos(&self) -> Option<u8> {
810 self.properties
811 .get(&PropertyId::MaximumQoS)
812 .and_then(|values| values.first())
813 .and_then(|value| {
814 if let PropertyValue::Byte(v) = value {
815 Some(*v)
816 } else {
817 None
818 }
819 })
820 }
821
822 pub fn set_content_type(&mut self, content_type: String) {
824 self.properties
825 .entry(PropertyId::ContentType)
826 .or_default()
827 .push(PropertyValue::Utf8String(content_type));
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834 use bytes::BytesMut;
835
836 #[test]
837 fn test_property_id_from_u8() {
838 assert_eq!(
839 PropertyId::from_u8(0x01),
840 Some(PropertyId::PayloadFormatIndicator)
841 );
842 assert_eq!(PropertyId::from_u8(0x26), Some(PropertyId::UserProperty));
843 assert_eq!(
844 PropertyId::from_u8(0x2A),
845 Some(PropertyId::SharedSubscriptionAvailable)
846 );
847 assert_eq!(PropertyId::from_u8(0xFF), None);
848 assert_eq!(PropertyId::from_u8(0x00), None);
849 }
850
851 #[test]
852 fn test_property_allows_multiple() {
853 assert!(PropertyId::UserProperty.allows_multiple());
854 assert!(PropertyId::SubscriptionIdentifier.allows_multiple());
855 assert!(!PropertyId::PayloadFormatIndicator.allows_multiple());
856 assert!(!PropertyId::SessionExpiryInterval.allows_multiple());
857 }
858
859 #[test]
860 fn test_property_value_type() {
861 assert_eq!(
862 PropertyId::PayloadFormatIndicator.value_type(),
863 PropertyValueType::Byte
864 );
865 assert_eq!(
866 PropertyId::TopicAlias.value_type(),
867 PropertyValueType::TwoByteInteger
868 );
869 assert_eq!(
870 PropertyId::SessionExpiryInterval.value_type(),
871 PropertyValueType::FourByteInteger
872 );
873 assert_eq!(
874 PropertyId::SubscriptionIdentifier.value_type(),
875 PropertyValueType::VariableByteInteger
876 );
877 assert_eq!(
878 PropertyId::ContentType.value_type(),
879 PropertyValueType::Utf8String
880 );
881 assert_eq!(
882 PropertyId::CorrelationData.value_type(),
883 PropertyValueType::BinaryData
884 );
885 assert_eq!(
886 PropertyId::UserProperty.value_type(),
887 PropertyValueType::Utf8StringPair
888 );
889 }
890
891 #[test]
892 fn test_property_value_matches_type() {
893 let byte_val = PropertyValue::Byte(1);
894 assert!(byte_val.matches_type(PropertyValueType::Byte));
895 assert!(!byte_val.matches_type(PropertyValueType::TwoByteInteger));
896
897 let string_val = PropertyValue::Utf8String("test".to_string());
898 assert!(string_val.matches_type(PropertyValueType::Utf8String));
899 assert!(!string_val.matches_type(PropertyValueType::BinaryData));
900 }
901
902 #[test]
903 fn test_properties_add_valid() {
904 let mut props = Properties::new();
905
906 props
908 .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
909 .unwrap();
910 props
911 .add(
912 PropertyId::SessionExpiryInterval,
913 PropertyValue::FourByteInteger(3600),
914 )
915 .unwrap();
916 props
917 .add(
918 PropertyId::ContentType,
919 PropertyValue::Utf8String("text/plain".to_string()),
920 )
921 .unwrap();
922
923 props
925 .add(
926 PropertyId::UserProperty,
927 PropertyValue::Utf8StringPair("key1".to_string(), "value1".to_string()),
928 )
929 .unwrap();
930 props
931 .add(
932 PropertyId::UserProperty,
933 PropertyValue::Utf8StringPair("key2".to_string(), "value2".to_string()),
934 )
935 .unwrap();
936
937 assert_eq!(props.len(), 4); }
939
940 #[test]
941 fn test_properties_add_type_mismatch() {
942 let mut props = Properties::new();
943
944 let result = props.add(
946 PropertyId::PayloadFormatIndicator,
947 PropertyValue::FourByteInteger(100),
948 );
949 assert!(result.is_err());
950 }
951
952 #[test]
953 fn test_properties_add_duplicate_single_value() {
954 let mut props = Properties::new();
955
956 props
958 .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(0))
959 .unwrap();
960
961 let result = props.add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1));
963 assert!(result.is_err());
964 }
965
966 #[test]
967 fn test_properties_get() {
968 let mut props = Properties::new();
969 props
970 .add(
971 PropertyId::ContentType,
972 PropertyValue::Utf8String("text/html".to_string()),
973 )
974 .unwrap();
975
976 let value = props.get(PropertyId::ContentType).unwrap();
977 match value {
978 PropertyValue::Utf8String(s) => assert_eq!(s, "text/html"),
979 _ => panic!("Wrong value type"),
980 }
981
982 assert!(props.get(PropertyId::ResponseTopic).is_none());
983 }
984
985 #[test]
986 fn test_properties_get_all() {
987 let mut props = Properties::new();
988
989 props
991 .add(
992 PropertyId::UserProperty,
993 PropertyValue::Utf8StringPair("k1".to_string(), "v1".to_string()),
994 )
995 .unwrap();
996 props
997 .add(
998 PropertyId::UserProperty,
999 PropertyValue::Utf8StringPair("k2".to_string(), "v2".to_string()),
1000 )
1001 .unwrap();
1002
1003 let values = props.get_all(PropertyId::UserProperty).unwrap();
1004 assert_eq!(values.len(), 2);
1005 }
1006
1007 #[test]
1008 fn test_properties_encode_decode_empty() {
1009 let props = Properties::new();
1010 let mut buf = BytesMut::new();
1011
1012 props.encode(&mut buf).unwrap();
1013 assert_eq!(buf[0], 0); let decoded = Properties::decode(&mut buf).unwrap();
1016 assert!(decoded.is_empty());
1017 }
1018
1019 #[test]
1020 fn test_properties_encode_decode_single_values() {
1021 let mut props = Properties::new();
1022
1023 props
1025 .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
1026 .unwrap();
1027 props
1028 .add(PropertyId::TopicAlias, PropertyValue::TwoByteInteger(100))
1029 .unwrap();
1030 props
1031 .add(
1032 PropertyId::SessionExpiryInterval,
1033 PropertyValue::FourByteInteger(3600),
1034 )
1035 .unwrap();
1036 props
1037 .add(
1038 PropertyId::SubscriptionIdentifier,
1039 PropertyValue::VariableByteInteger(123),
1040 )
1041 .unwrap();
1042 props
1043 .add(
1044 PropertyId::ContentType,
1045 PropertyValue::Utf8String("text/plain".to_string()),
1046 )
1047 .unwrap();
1048 props
1049 .add(
1050 PropertyId::CorrelationData,
1051 PropertyValue::BinaryData(Bytes::from(vec![1, 2, 3, 4])),
1052 )
1053 .unwrap();
1054 props
1055 .add(
1056 PropertyId::UserProperty,
1057 PropertyValue::Utf8StringPair("key".to_string(), "value".to_string()),
1058 )
1059 .unwrap();
1060
1061 let mut buf = BytesMut::new();
1062 props.encode(&mut buf).unwrap();
1063
1064 let decoded = Properties::decode(&mut buf).unwrap();
1065 assert_eq!(decoded.len(), props.len());
1066
1067 match decoded.get(PropertyId::PayloadFormatIndicator).unwrap() {
1069 PropertyValue::Byte(v) => assert_eq!(*v, 1),
1070 _ => panic!("Wrong type"),
1071 }
1072
1073 match decoded.get(PropertyId::TopicAlias).unwrap() {
1074 PropertyValue::TwoByteInteger(v) => assert_eq!(*v, 100),
1075 _ => panic!("Wrong type"),
1076 }
1077
1078 match decoded.get(PropertyId::ContentType).unwrap() {
1079 PropertyValue::Utf8String(v) => assert_eq!(v, "text/plain"),
1080 _ => panic!("Wrong type"),
1081 }
1082 }
1083
1084 #[test]
1085 fn test_properties_encode_decode_multiple_values() {
1086 let mut props = Properties::new();
1087
1088 props
1090 .add(
1091 PropertyId::UserProperty,
1092 PropertyValue::Utf8StringPair("env".to_string(), "prod".to_string()),
1093 )
1094 .unwrap();
1095 props
1096 .add(
1097 PropertyId::UserProperty,
1098 PropertyValue::Utf8StringPair("version".to_string(), "1.0".to_string()),
1099 )
1100 .unwrap();
1101
1102 props
1104 .add(
1105 PropertyId::SubscriptionIdentifier,
1106 PropertyValue::VariableByteInteger(10),
1107 )
1108 .unwrap();
1109 props
1110 .add(
1111 PropertyId::SubscriptionIdentifier,
1112 PropertyValue::VariableByteInteger(20),
1113 )
1114 .unwrap();
1115
1116 let mut buf = BytesMut::new();
1117 props.encode(&mut buf).unwrap();
1118
1119 let decoded = Properties::decode(&mut buf).unwrap();
1120
1121 let user_props = decoded.get_all(PropertyId::UserProperty).unwrap();
1122 assert_eq!(user_props.len(), 2);
1123
1124 let sub_ids = decoded.get_all(PropertyId::SubscriptionIdentifier).unwrap();
1125 assert_eq!(sub_ids.len(), 2);
1126 }
1127
1128 #[test]
1129 fn test_properties_decode_invalid_property_id() {
1130 let mut buf = BytesMut::new();
1131 buf.put_u8(1); buf.put_u8(0xFF); let result = Properties::decode(&mut buf);
1135 assert!(result.is_err());
1136 }
1137
1138 #[test]
1139 fn test_properties_decode_insufficient_data() {
1140 let mut buf = BytesMut::new();
1141 buf.put_u8(10); let result = Properties::decode(&mut buf);
1144 assert!(result.is_err());
1145 }
1146
1147 #[test]
1148 fn test_properties_encoded_len() {
1149 let mut props = Properties::new();
1150 props
1151 .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(1))
1152 .unwrap();
1153 props
1154 .add(
1155 PropertyId::ContentType,
1156 PropertyValue::Utf8String("test".to_string()),
1157 )
1158 .unwrap();
1159
1160 let mut buf = BytesMut::new();
1161 props.encode(&mut buf).unwrap();
1162
1163 assert_eq!(props.encoded_len(), buf.len());
1164 }
1165
1166 #[test]
1167 fn test_all_property_ids_have_correct_types() {
1168 for id in 0u8..=0x2A {
1170 if let Some(prop_id) = PropertyId::from_u8(id) {
1171 let _ = prop_id.value_type();
1173 }
1174 }
1175 }
1176}