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 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 encode_variable_int(buf, u32::from(*id as u8))?;
310
311 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 pub fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
343 let props_len = decode_variable_int(buf)? as usize;
345
346 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 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 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 #[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 #[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 len += crate::encoding::variable_int_len(u32::from(*id as u8));
429
430 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); }
927
928 #[test]
929 fn test_properties_add_type_mismatch() {
930 let mut props = Properties::new();
931
932 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 props
946 .add(PropertyId::PayloadFormatIndicator, PropertyValue::Byte(0))
947 .unwrap();
948
949 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 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); 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 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 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 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 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); buf.put_u8(0xFF); 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); 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 for id in 0u8..=0x2A {
1158 if let Some(prop_id) = PropertyId::from_u8(id) {
1159 let _ = prop_id.value_type();
1161 }
1162 }
1163 }
1164}