diff --git a/include/async_mqtt5/impl/codecs/base_decoders.hpp b/include/async_mqtt5/impl/codecs/base_decoders.hpp index f55f36d..4a74116 100644 --- a/include/async_mqtt5/impl/codecs/base_decoders.hpp +++ b/include/async_mqtt5/impl/codecs/base_decoders.hpp @@ -390,6 +390,10 @@ bool parse_to_prop( if constexpr (async_mqtt5::is_vector) { std::string value; + // key + rv = basic::utf8_.parse(iter, last, ctx, rctx, value); + if (rv) prop.push_back(std::move(value)); + // value rv = basic::utf8_.parse(iter, last, ctx, rctx, value); if (rv) prop.push_back(std::move(value)); } @@ -439,7 +443,7 @@ public: // either rv = false or property with prop_id was not found if (!rv || iter == saved) - break; + return false; } first = iter; diff --git a/include/async_mqtt5/impl/codecs/base_encoders.hpp b/include/async_mqtt5/impl/codecs/base_encoders.hpp index 6e4a773..cba2811 100644 --- a/include/async_mqtt5/impl/codecs/base_encoders.hpp +++ b/include/async_mqtt5/impl/codecs/base_encoders.hpp @@ -370,7 +370,7 @@ using encoder_types = std::tuple< prop_encoder_type, prop_encoder_type, prop_encoder_type, - prop_encoder_type>, // varint + prop_encoder_type>, prop_encoder_type>, prop_encoder_type, prop_encoder_type>, @@ -454,12 +454,19 @@ public: size_t byte_size() const { if (_val.empty()) return 0; + size_t total_size = 0; - for (const auto& pr: _val) { - auto sval = encoder_for_prop

(pr); - size_t prop_size = sval.byte_size(); - if (prop_size) total_size += 1 + prop_size; + for (size_t i = 0; i < _val.size() && i + 1 < _val.size(); i += 2) { + auto skey = encoder_for_prop

(_val[i]); + size_t key_size = skey.byte_size(); + + auto sval = encoder_for_prop

(_val[i + 1]); + size_t val_size = sval.byte_size(); + + if (key_size && val_size) + total_size += 1 + key_size + val_size; } + return total_size; } @@ -467,11 +474,16 @@ public: if (_val.empty()) return s; - for (const auto& pr: _val) { - auto sval = encoder_for_prop

(pr); + for (size_t i = 0; i < _val.size() && i + 1 < _val.size(); i += 2) { s.push_back(p); + + auto skey = encoder_for_prop

(_val[i]); + skey.encode(s); + + auto sval = encoder_for_prop

(_val[i + 1]); sval.encode(s); } + return s; } }; diff --git a/test/unit/serialization.cpp b/test/unit/serialization.cpp index 1299d77..5112f4c 100644 --- a/test/unit/serialization.cpp +++ b/test/unit/serialization.cpp @@ -25,7 +25,8 @@ BOOST_AUTO_TEST_CASE(test_connect) { uint16_t topic_alias_max = 1200; uint8_t request_response_information = 1; uint8_t request_problem_information = 0; - std::string_view user_property = "user prop"; + std::string_view user_property_1 = "user prop"; + std::string_view user_property_2 = "user prop val"; std::string_view auth_method = "method"; std::string_view auth_data = "data"; // will @@ -38,7 +39,8 @@ BOOST_AUTO_TEST_CASE(test_connect) { std::string_view will_content_type = "will content type"; std::string_view will_response_topic = "response_topic"; std::string_view will_correlation_data = "correlation data"; - std::string_view will_user_property = "will prop"; + std::string_view will_user_property_1 = "will prop"; + std::string_view will_user_property_2 = "will prop val"; connect_props cprops; cprops[prop::session_expiry_interval] = session_expiry_interval; @@ -47,7 +49,8 @@ BOOST_AUTO_TEST_CASE(test_connect) { cprops[prop::topic_alias_maximum] = topic_alias_max; cprops[prop::request_response_information] = request_response_information; cprops[prop::request_problem_information] = request_problem_information; - cprops[prop::user_property].emplace_back(user_property); + cprops[prop::user_property].emplace_back(user_property_1); + cprops[prop::user_property].emplace_back(user_property_2); cprops[prop::authentication_method] = auth_method; cprops[prop::authentication_data] = auth_data; @@ -58,7 +61,8 @@ BOOST_AUTO_TEST_CASE(test_connect) { w[prop::content_type] = will_content_type; w[prop::response_topic] = will_response_topic; w[prop::correlation_data] = will_correlation_data; - w[prop::user_property].emplace_back(will_user_property); + w[prop::user_property].emplace_back(will_user_property_1); + w[prop::user_property].emplace_back(will_user_property_2); std::optional will_opt { std::move(w) }; auto msg = encoders::encode_connect( @@ -67,11 +71,11 @@ BOOST_AUTO_TEST_CASE(test_connect) { byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing CONNECT fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_connect(remain_length, it); - BOOST_CHECK_MESSAGE(rv, "Parsing CONNECT failed."); + BOOST_ASSERT(rv); const auto& [client_id_, uname_, password_, keep_alive_, clean_start_, cprops_, w_] = *rv; BOOST_CHECK_EQUAL(client_id_, client_id); @@ -81,14 +85,16 @@ BOOST_AUTO_TEST_CASE(test_connect) { BOOST_CHECK_EQUAL(keep_alive_, keep_alive); BOOST_CHECK_EQUAL(clean_start_, clean_start); - cprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + cprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*cprops_[prop::session_expiry_interval], session_expiry_interval); BOOST_CHECK_EQUAL(*cprops_[prop::receive_maximum], receive_max); BOOST_CHECK_EQUAL(*cprops_[prop::maximum_packet_size], maximum_packet_size); BOOST_CHECK_EQUAL(*cprops_[prop::topic_alias_maximum], topic_alias_max); BOOST_CHECK_EQUAL(*cprops_[prop::request_response_information], request_response_information); BOOST_CHECK_EQUAL(*cprops_[prop::request_problem_information], request_problem_information); - BOOST_CHECK_EQUAL(cprops_[prop::user_property][0], user_property); + BOOST_ASSERT(cprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(cprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(cprops_[prop::user_property][1], user_property_2); BOOST_CHECK_EQUAL(*cprops_[prop::authentication_method], auth_method); BOOST_CHECK_EQUAL(*cprops_[prop::authentication_data], auth_data); @@ -97,15 +103,16 @@ BOOST_AUTO_TEST_CASE(test_connect) { BOOST_CHECK_EQUAL((*w_).topic(), will_topic); BOOST_CHECK_EQUAL((*w_).message(), will_message); - (*w_).visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + (*w_).visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*(*w_)[prop::will_delay_interval], will_delay_interval); BOOST_CHECK_EQUAL(*(*w_)[prop::payload_format_indicator], will_payload_format_indicator); BOOST_CHECK_EQUAL(*(*w_)[prop::message_expiry_interval], will_message_expiry_interval); BOOST_CHECK_EQUAL(*(*w_)[prop::content_type], will_content_type); BOOST_CHECK_EQUAL(*(*w_)[prop::response_topic], will_response_topic); BOOST_CHECK_EQUAL(*(*w_)[prop::correlation_data], will_correlation_data); - BOOST_CHECK_EQUAL((*w_)[prop::user_property][0], will_user_property); - + BOOST_ASSERT((*w_)[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL((*w_)[prop::user_property][0], will_user_property_1); + BOOST_CHECK_EQUAL((*w_)[prop::user_property][1], will_user_property_2); } BOOST_AUTO_TEST_CASE(test_connack) { @@ -121,7 +128,8 @@ BOOST_AUTO_TEST_CASE(test_connack) { std::string assigned_client_id = "client_id"; uint16_t topic_alias_max = 128; std::string reason_string = "some reason string"; - std::string user_property = "property"; + std::string user_property_1 = "property"; + std::string user_property_2 = "property val"; uint8_t wildcard_sub = 1; uint8_t sub_id = 1; uint8_t shared_sub = 0; @@ -140,7 +148,8 @@ BOOST_AUTO_TEST_CASE(test_connack) { cprops[prop::assigned_client_identifier] = assigned_client_id; cprops[prop::topic_alias_maximum] = topic_alias_max; cprops[prop::reason_string] = reason_string; - cprops[prop::user_property].push_back(user_property); + cprops[prop::user_property].push_back(user_property_1); + cprops[prop::user_property].push_back(user_property_2); cprops[prop::wildcard_subscription_available] = wildcard_sub; cprops[prop::subscription_identifier_available] = sub_id; cprops[prop::shared_subscription_available] = shared_sub; @@ -154,17 +163,17 @@ BOOST_AUTO_TEST_CASE(test_connack) { byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing CONNACK fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_connack(remain_length, it); - BOOST_CHECK_MESSAGE(rv, "Parsing CONNACK failed."); + BOOST_ASSERT(rv); const auto& [session_present_, reason_code_, cprops_] = *rv; BOOST_CHECK_EQUAL(session_present_, session_present); BOOST_CHECK_EQUAL(reason_code_, reason_code); - cprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + cprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*cprops_[prop::session_expiry_interval], session_expiry_interval); BOOST_CHECK_EQUAL(*cprops_[prop::receive_maximum], receive_maximum); BOOST_CHECK_EQUAL(*cprops_[prop::maximum_qos], max_qos); @@ -173,7 +182,9 @@ BOOST_AUTO_TEST_CASE(test_connack) { BOOST_CHECK_EQUAL(*cprops_[prop::assigned_client_identifier], assigned_client_id); BOOST_CHECK_EQUAL(*cprops_[prop::topic_alias_maximum], topic_alias_max); BOOST_CHECK_EQUAL(*cprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(cprops_[prop::user_property][0], user_property); + BOOST_ASSERT(cprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(cprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(cprops_[prop::user_property][1], user_property_2); BOOST_CHECK_EQUAL(*cprops_[prop::wildcard_subscription_available], wildcard_sub); BOOST_CHECK_EQUAL(*cprops_[prop::subscription_identifier_available], sub_id); BOOST_CHECK_EQUAL(*cprops_[prop::shared_subscription_available], shared_sub); @@ -195,8 +206,8 @@ BOOST_AUTO_TEST_CASE(test_publish) { int16_t topic_alias = 16; std::string response_topic = "topic/response"; std::string correlation_data = "correlation data"; - std::string publish_prop_1 = "first publish prop"; - std::string publish_prop_2 = "second publish prop"; + std::string publish_prop_1 = "key"; + std::string publish_prop_2 = "val"; uint32_t subscription_identifier = 123456; std::string content_type = "application/octet-stream"; @@ -219,11 +230,11 @@ BOOST_AUTO_TEST_CASE(test_publish) { byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBLISH fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_publish(control_byte, remain_length, it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBLISH failed."); + BOOST_ASSERT(rv); const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; BOOST_CHECK(packet_id); @@ -231,12 +242,13 @@ BOOST_AUTO_TEST_CASE(test_publish) { BOOST_CHECK_EQUAL(topic_, topic); BOOST_CHECK_EQUAL(payload_, payload); - pprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + pprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*pprops_[prop::payload_format_indicator], payload_format_indicator); BOOST_CHECK_EQUAL(*pprops_[prop::message_expiry_interval], message_expiry_interval); BOOST_CHECK_EQUAL(*pprops_[prop::topic_alias], topic_alias); BOOST_CHECK_EQUAL(*pprops_[prop::response_topic], response_topic); BOOST_CHECK_EQUAL(*pprops_[prop::correlation_data], correlation_data); + BOOST_ASSERT(pprops_[prop::user_property].size() == 2); BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], publish_prop_1); BOOST_CHECK_EQUAL(pprops_[prop::user_property][1], publish_prop_2); BOOST_CHECK_EQUAL(*pprops_[prop::subscription_identifier], subscription_identifier); @@ -257,11 +269,11 @@ BOOST_AUTO_TEST_CASE(test_large_publish) { byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBLISH fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_publish(control_byte, remain_length, it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBLISH failed."); + BOOST_ASSERT(rv); const auto& [topic_, packet_id_, flags, pprops, payload_] = *rv; BOOST_CHECK(packet_id); @@ -276,17 +288,19 @@ BOOST_AUTO_TEST_CASE(test_puback) { uint8_t reason_code = 0x93; std::string reason_string = "PUBACK reason string"; - std::string user_prop = "PUBACK user prop"; + std::string user_property_1 = "PUBACK user prop"; + std::string user_property_2 = "PUBACK user prop val"; puback_props pprops; pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_prop); + pprops[prop::user_property].emplace_back(user_property_1); + pprops[prop::user_property].emplace_back(user_property_2); auto msg = encoders::encode_puback(packet_id, reason_code, pprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBACK fixed header failed."); + BOOST_ASSERT(header); auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); @@ -294,13 +308,15 @@ BOOST_AUTO_TEST_CASE(test_puback) { const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBACK failed."); + BOOST_ASSERT(rv); const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + pprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(reason_code_, reason_code); BOOST_CHECK_EQUAL(*pprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(pprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_pubrec) { @@ -309,17 +325,19 @@ BOOST_AUTO_TEST_CASE(test_pubrec) { uint8_t reason_code = 0x92; std::string reason_string = "PUBREC reason string"; - std::string user_prop = "PUBREC user prop"; + std::string user_property_1 = "PUBREC user prop"; + std::string user_property_2 = "PUBREC user prop val"; pubrec_props pprops; pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_prop); + pprops[prop::user_property].emplace_back(user_property_1); + pprops[prop::user_property].emplace_back(user_property_2); auto msg = encoders::encode_pubrec(packet_id, reason_code, pprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBREC fixed header failed."); + BOOST_ASSERT(header); auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); @@ -327,13 +345,15 @@ BOOST_AUTO_TEST_CASE(test_pubrec) { const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_pubrec(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBREC failed."); + BOOST_ASSERT(rv); const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + pprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(reason_code_, reason_code); BOOST_CHECK_EQUAL(*pprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(pprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_pubrel) { @@ -342,17 +362,19 @@ BOOST_AUTO_TEST_CASE(test_pubrel) { uint8_t reason_code = 0x00; std::string reason_string = "PUBREL reason string"; - std::string user_prop = "PUBREL user prop"; + std::string user_property_1 = "PUBREL user prop"; + std::string user_property_2 = "PUBREL user prop val"; pubrel_props pprops; pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_prop); + pprops[prop::user_property].emplace_back(user_property_1); + pprops[prop::user_property].emplace_back(user_property_2); auto msg = encoders::encode_pubrel(packet_id, reason_code, pprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBREL fixed header failed."); + BOOST_ASSERT(header); auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); @@ -360,13 +382,15 @@ BOOST_AUTO_TEST_CASE(test_pubrel) { const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_pubrel(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBREL failed."); + BOOST_ASSERT(rv); const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + pprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(reason_code_, reason_code); BOOST_CHECK_EQUAL(*pprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(pprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_pubcomp) { @@ -375,17 +399,19 @@ BOOST_AUTO_TEST_CASE(test_pubcomp) { uint8_t reason_code = 0x00; std::string reason_string = "PUBCOMP reason string"; - std::string user_prop = "PUBCOMP user prop"; + std::string user_property_1 = "PUBCOMP user prop"; + std::string user_property_2 = "PUBCOMP user prop val"; pubcomp_props pprops; pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_prop); + pprops[prop::user_property].emplace_back(user_property_1); + pprops[prop::user_property].emplace_back(user_property_2); auto msg = encoders::encode_pubcomp(packet_id, reason_code, pprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing PUBCOMP fixed header failed."); + BOOST_ASSERT(header); auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); @@ -393,19 +419,22 @@ BOOST_AUTO_TEST_CASE(test_pubcomp) { const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_pubcomp(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing PUBCOMP failed."); + BOOST_ASSERT(rv);; const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + pprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(reason_code_, reason_code); BOOST_CHECK_EQUAL(*pprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(pprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(pprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_subscribe) { //testing variables uint32_t sub_id = 1'234'567; - std::string user_prop = "SUBSCRIBE user prop"; + std::string user_property_1 = "SUBSCRIBE user prop"; + std::string user_property_2 = "SUBSCRIBE user prop val"; // delete using lines when we shorten the enum names using no_local_e = subscribe_options::no_local_e; @@ -419,7 +448,8 @@ BOOST_AUTO_TEST_CASE(test_subscribe) { subscribe_props sprops; sprops[prop::subscription_identifier] = sub_id; - sprops[prop::user_property].push_back(user_prop); + sprops[prop::user_property].push_back(user_property_1); + sprops[prop::user_property].push_back(user_property_2); std::vector filters { { @@ -433,14 +463,14 @@ BOOST_AUTO_TEST_CASE(test_subscribe) { byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing SUBSCRIBE fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); BOOST_CHECK_EQUAL(*packet_id_, packet_id); auto rv = decoders::decode_subscribe(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing SUBSCRIBE failed."); + BOOST_ASSERT(rv); const auto& [sprops_, filters_] = *rv; const auto& filter_ = filters_[0]; @@ -453,9 +483,11 @@ BOOST_AUTO_TEST_CASE(test_subscribe) { static_cast(qos); BOOST_CHECK_EQUAL(options_, mask); - sprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + sprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*sprops_[prop::subscription_identifier], sub_id); - BOOST_CHECK_EQUAL(sprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(sprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(sprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(sprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_suback) { @@ -464,31 +496,35 @@ BOOST_AUTO_TEST_CASE(test_suback) { std::vector reason_codes { 48, 28 }; std::string reason_string = "subscription accepted"; - std::string user_prop = "SUBACK user prop"; + std::string user_property_1 = "SUBACK user prop"; + std::string user_property_2 = "SUBACK user prop val"; suback_props sprops; sprops[prop::reason_string] = reason_string; - sprops[prop::user_property].push_back(user_prop); + sprops[prop::user_property].push_back(user_property_1); + sprops[prop::user_property].push_back(user_property_2); auto msg = encoders::encode_suback(packet_id, reason_codes, sprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing SUBACK fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); BOOST_CHECK_EQUAL(*packet_id_, packet_id); auto rv = decoders::decode_suback(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing SUBACK failed."); + BOOST_ASSERT(rv); const auto& [sprops_, reason_codes_] = *rv; BOOST_CHECK(reason_codes_ == reason_codes); - sprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + sprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*sprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(sprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(sprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(sprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(sprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_unsubscribe) { @@ -496,29 +532,33 @@ BOOST_AUTO_TEST_CASE(test_unsubscribe) { uint16_t packet_id = 14423; std::vector topics { "first topic", "second/topic" }; - std::string user_prop = "UNSUBSCRIBE user prop"; + std::string user_property_1 = "UNSUBSCRIBE user prop"; + std::string user_property_2 = "UNSUBSCRIBE user prop val"; unsubscribe_props uprops; - uprops[prop::user_property].push_back(user_prop); + uprops[prop::user_property].push_back(user_property_1); + uprops[prop::user_property].push_back(user_property_2); auto msg = encoders::encode_unsubscribe(packet_id, topics, uprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing UNSUBSCRIBE fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); BOOST_CHECK_EQUAL(*packet_id_, packet_id); auto rv = decoders::decode_unsubscribe(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(rv, "Parsing UNSUBSCRIBE failed."); + BOOST_ASSERT(rv); const auto& [uprops_, topics_] = *rv; BOOST_CHECK(topics_ == topics); - uprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); - BOOST_CHECK_EQUAL(uprops_[prop::user_property][0], user_prop); + uprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); + BOOST_ASSERT(uprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(uprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(uprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_unsuback) { @@ -527,31 +567,35 @@ BOOST_AUTO_TEST_CASE(test_unsuback) { std::vector reason_codes{ 48, 28 }; std::string reason_string = "some unsuback reason string"; - std::string user_prop = "UNSUBACK user prop"; + std::string user_property_1 = "SUBACK user prop"; + std::string user_property_2 = "SUBACK user prop val"; unsuback_props uprops; uprops[prop::reason_string] = reason_string; - uprops[prop::user_property].push_back(user_prop); + uprops[prop::user_property].push_back(user_property_1); + uprops[prop::user_property].push_back(user_property_2); auto msg = encoders::encode_unsuback(packet_id, reason_codes, uprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing UNSUBACK fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto packet_id_ = decoders::decode_packet_id(it); BOOST_CHECK(packet_id); BOOST_CHECK_EQUAL(*packet_id_, packet_id); auto rv = decoders::decode_unsuback(remain_length - sizeof(uint16_t), it); - BOOST_CHECK_MESSAGE(header, "Parsing UNSUBACK failed."); + BOOST_ASSERT(rv); const auto& [uprops_, reason_codes_] = *rv; BOOST_CHECK(reason_codes_ == reason_codes); - uprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + uprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*uprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(uprops_[prop::user_property][0], user_prop); + BOOST_ASSERT(uprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(uprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(uprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_disconnect) { @@ -560,32 +604,36 @@ BOOST_AUTO_TEST_CASE(test_disconnect) { int32_t session_expiry_interval = 50; std::string reason_string = "a reason"; - std::string user_property = "DISCONNECT user property"; + std::string user_property_1 = "DISCONNECT user prop"; + std::string user_property_2 = "DISCONNECT user prop val"; std::string server_reference = "server"; disconnect_props dprops; dprops[prop::session_expiry_interval] = session_expiry_interval; dprops[prop::reason_string] = reason_string; - dprops[prop::user_property].emplace_back(user_property); + dprops[prop::user_property].emplace_back(user_property_1); + dprops[prop::user_property].emplace_back(user_property_2); dprops[prop::server_reference] = server_reference; auto msg = encoders::encode_disconnect(reason_code, dprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing DISCONNECT fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_disconnect(remain_length, it); - BOOST_CHECK_MESSAGE(header, "Parsing DISCONNECT failed."); + BOOST_ASSERT(rv); const auto& [reason_code_, dprops_] = *rv; BOOST_CHECK_EQUAL(reason_code_, reason_code); - dprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + dprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*dprops_[prop::session_expiry_interval], session_expiry_interval); BOOST_CHECK_EQUAL(*dprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(dprops_[prop::user_property][0], user_property); + BOOST_ASSERT(dprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(dprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(dprops_[prop::user_property][1], user_property_2); BOOST_CHECK_EQUAL(*dprops_[prop::server_reference], server_reference); } @@ -597,32 +645,36 @@ BOOST_AUTO_TEST_CASE(test_auth) { std::string authentication_data = "data"; std::string reason_string = "reason"; - std::string user_property = "AUTH user propety"; + std::string user_property_1 = "AUTH user propety"; + std::string user_property_2 = "AUTH user propety val"; auth_props aprops; aprops[prop::authentication_method] = authentication_method; aprops[prop::authentication_data] = authentication_data; aprops[prop::reason_string] = reason_string; - aprops[prop::user_property].emplace_back(user_property); + aprops[prop::user_property].emplace_back(user_property_1); + aprops[prop::user_property].emplace_back(user_property_2); auto msg = encoders::encode_auth(reason_code, aprops); byte_citer it = msg.cbegin(), last = msg.cend(); auto header = decoders::decode_fixed_header(it, last); - BOOST_CHECK_MESSAGE(header, "Parsing AUTH fixed header failed."); + BOOST_ASSERT(header); const auto& [control_byte, remain_length] = *header; auto rv = decoders::decode_auth(remain_length, it); - BOOST_CHECK_MESSAGE(rv, "Parsing AUTH failed."); + BOOST_ASSERT(rv); const auto& [reason_code_, aprops_] = *rv; BOOST_CHECK_EQUAL(reason_code_, reason_code); - aprops_.visit([](const auto& prop, const auto&) { BOOST_CHECK(prop); return true; }); + aprops_.visit([](const auto& prop, const auto&) { BOOST_ASSERT(prop); return true; }); BOOST_CHECK_EQUAL(*aprops_[prop::authentication_method], authentication_method); BOOST_CHECK_EQUAL(*aprops_[prop::authentication_data], authentication_data); BOOST_CHECK_EQUAL(*aprops_[prop::reason_string], reason_string); - BOOST_CHECK_EQUAL(aprops_[prop::user_property][0], user_property); + BOOST_ASSERT(aprops_[prop::user_property].size() == 2); + BOOST_CHECK_EQUAL(aprops_[prop::user_property][0], user_property_1); + BOOST_CHECK_EQUAL(aprops_[prop::user_property][1], user_property_2); } BOOST_AUTO_TEST_CASE(test_pingreq) { @@ -639,4 +691,171 @@ BOOST_AUTO_TEST_CASE(test_pingresp) { BOOST_CHECK_EQUAL(msg, encoded_pingresp); } +BOOST_AUTO_TEST_CASE(empty_user_property) { + publish_props pprops; + pprops[prop::user_property].emplace_back(""); + pprops[prop::user_property].emplace_back(""); + + auto msg = encoders::encode_publish( + 1, "topic", "payload", + qos_e::at_least_once, retain_e::yes, dup_e::no, + pprops + ); + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_ASSERT(rv); + + const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; + + auto user_props_ = pprops_[prop::user_property]; + BOOST_ASSERT(user_props_.size() == 2); + BOOST_CHECK_EQUAL(user_props_[0], ""); + BOOST_CHECK_EQUAL(user_props_[1], ""); +} + +BOOST_AUTO_TEST_CASE(omit_invalid_user_property) { + // testing variables + std::string_view user_property_1 = "key"; + std::string_view user_property_2 = "val"; + std::string_view user_property_3 = "lone key"; + + publish_props pprops; + pprops[prop::user_property].emplace_back(user_property_1); + pprops[prop::user_property].emplace_back(user_property_2); + pprops[prop::user_property].emplace_back(user_property_3); + + auto msg = encoders::encode_publish( + 1, "topic", "payload", + qos_e::at_least_once, retain_e::yes, dup_e::no, + pprops + ); + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_ASSERT(rv); + + const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; + + auto user_props_ = pprops_[prop::user_property]; + BOOST_ASSERT(user_props_.size() == 2); + BOOST_CHECK_EQUAL(user_props_[0], user_property_1); + BOOST_CHECK_EQUAL(user_props_[1], user_property_2); +} + +BOOST_AUTO_TEST_CASE(omit_all_user_property) { + // testing variables + std::string_view user_property_1 = "key"; + + publish_props pprops; + pprops[prop::user_property].emplace_back(user_property_1); + + auto msg = encoders::encode_publish( + 1, "topic", "payload", + qos_e::at_least_once, retain_e::yes, dup_e::no, + pprops + ); + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_ASSERT(rv); + + const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; + + auto user_props_ = pprops_[prop::user_property]; + BOOST_CHECK(user_props_.size() == 0); +} + +BOOST_AUTO_TEST_CASE(deserialize_user_property) { + // testing variables + const char puback[] = { + 64, 15, 0, 1, 0, 11, 38, 0, 3, 'k', 'e', 'y', 0, 3, 'v', 'a', 'l' + }; + std::string msg { puback, sizeof(puback) / sizeof(char) }; + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_ASSERT(packet_id_); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_ASSERT(rv); + + const auto& [reason_code_, pprops_] = *rv; + auto user_props_ = pprops_[prop::user_property]; + BOOST_ASSERT(user_props_.size() == 2); + BOOST_CHECK_EQUAL(user_props_[0], "key"); + BOOST_CHECK_EQUAL(user_props_[1], "val"); +} + +BOOST_AUTO_TEST_CASE(deserialize_empty_user_property) { + // testing variables + const char puback[] = { + 64, 9, 0, 1, 0, 5, 38, 0, 0, 0, 0 + }; + std::string msg{ puback, sizeof(puback) / sizeof(char) }; + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_ASSERT(packet_id_); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_ASSERT(rv); + + const auto& [reason_code_, pprops_] = *rv; + auto user_props_ = pprops_[prop::user_property]; + BOOST_ASSERT(user_props_.size() == 2); + BOOST_CHECK_EQUAL(user_props_[0], ""); + BOOST_CHECK_EQUAL(user_props_[1], ""); +} + +BOOST_AUTO_TEST_CASE(malformed_user_property) { + // testing variables + const char malformed_puback[] = { + 64, 10, 0, 1, 0, 6, 38, 0, 3, 'k', 'e', 'y' // missing value + }; + std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_ASSERT(packet_id_); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_CHECK(!rv); +} + +BOOST_AUTO_TEST_CASE(malformed_reason_string) { + // testing variables + const char malformed_puback[] = { + 64, 6, 0, 1, 0, 2, 31, 1 + }; + std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; + + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_ASSERT(header); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_ASSERT(packet_id_); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_CHECK(!rv); +} + BOOST_AUTO_TEST_SUITE_END()