Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ node_modules/

# Tool
.vscode/
.cache/
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

= History

== 10.2.8
* Added Share Name character check. #445

== 10.2.7
* Implemented cmake package version checking. #124, #444,
* Fixed broker tool not delivering all retained messages when wildcard subscription matches multiple topics. #442, #443
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# http://www.boost.org/LICENSE_1_0.txt)

cmake_minimum_required (VERSION 3.13.0)
project(async_mqtt_iface VERSION 10.0.0)
project(async_mqtt_iface VERSION 10.2.8)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- I/O independent (also known as Sans-I/O) MQTT protocol library for C++17.
- Asynchronous MQTT communication library using the MQTT protocol library and Boost.Asio.

Version 10.2.7 [![Actions Status](https://github.com/redboltz/async_mqtt/workflows/CI/badge.svg)](https://github.com/redboltz/async_mqtt/actions)[![codecov](https://codecov.io/gh/redboltz/async_mqtt/branch/main/graph/badge.svg)](https://codecov.io/gh/redboltz/async_mqtt)
Version 10.2.8 [![Actions Status](https://github.com/redboltz/async_mqtt/workflows/CI/badge.svg)](https://github.com/redboltz/async_mqtt/actions)[![codecov](https://codecov.io/gh/redboltz/async_mqtt/branch/main/graph/badge.svg)](https://codecov.io/gh/redboltz/async_mqtt)

## Document

Expand Down
46 changes: 35 additions & 11 deletions include/async_mqtt/protocol/packet/impl/v5_subscribe.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,24 @@ basic_subscribe_packet<PacketIdBytes>::basic_subscribe_packet(
};
break;
}
if (e.opts().get_nl() == sub::nl::yes && !e.sharename().empty()) {
throw system_error{
make_error_code(
disconnect_reason_code::protocol_error
)
};
if (!e.sharename().empty()) {
// MQTT v5.0 spec: It is a Protocol Error to set the No Local bit to 1
// on a Shared Subscription
if (e.opts().get_nl() == sub::nl::yes) {
throw system_error{
make_error_code(
disconnect_reason_code::protocol_error
)
};
}
// MQTT v5.0 spec: ShareName must not contain "/", "+", or "#"
if (e.sharename().find_first_of("/#+"sv) != std::string_view::npos) {
throw system_error{
make_error_code(
disconnect_reason_code::malformed_packet
)
};
}
}
auto size = e.all_topic().size();
remaining_length_ +=
Expand Down Expand Up @@ -218,6 +230,7 @@ properties const& basic_subscribe_packet<PacketIdBytes>::props() const {
template <std::size_t PacketIdBytes>
ASYNC_MQTT_HEADER_ONLY_INLINE
basic_subscribe_packet<PacketIdBytes>::basic_subscribe_packet(buffer buf, error_code& ec) {
using namespace std::literals;
// fixed_header
if (buf.empty()) {
ec = make_error_code(
Expand Down Expand Up @@ -358,11 +371,22 @@ basic_subscribe_packet<PacketIdBytes>::basic_subscribe_packet(buffer buf, error_
return;
}
auto entry = topic_subopts{std::string{topic}, opts};
if (entry.opts().get_nl() == sub::nl::yes && !entry.sharename().empty()) {
ec = make_error_code(
disconnect_reason_code::protocol_error
);
return;
if (!entry.sharename().empty()) {
// MQTT v5.0 spec: It is a Protocol Error to set the No Local bit to 1
// on a Shared Subscription
if (entry.opts().get_nl() == sub::nl::yes) {
ec = make_error_code(
disconnect_reason_code::protocol_error
);
return;
}
// MQTT v5.0 spec: ShareName must not contain "/", "+", or "#"
if (entry.sharename().find_first_of("/#+"sv) != std::string_view::npos) {
ec = make_error_code(
disconnect_reason_code::malformed_packet
);
return;
}
}

entries_.push_back(force_move(entry));
Expand Down
23 changes: 22 additions & 1 deletion include/async_mqtt/protocol/packet/impl/v5_unsubscribe.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ basic_unsubscribe_packet<PacketIdBytes>::basic_unsubscribe_packet(
)
);
}
// MQTT v5.0 spec: ShareName must not contain "/", "+", or "#"
if (!e.sharename().empty()) {
if (e.sharename().find_first_of("/#+"sv) != std::string_view::npos) {
throw system_error{
make_error_code(
disconnect_reason_code::malformed_packet
)
};
}
}
}

remaining_length_buf_ = val_to_variable_bytes(boost::numeric_cast<std::uint32_t>(remaining_length_));
Expand Down Expand Up @@ -176,6 +186,7 @@ properties const& basic_unsubscribe_packet<PacketIdBytes>::props() const {
template <std::size_t PacketIdBytes>
ASYNC_MQTT_HEADER_ONLY_INLINE
basic_unsubscribe_packet<PacketIdBytes>::basic_unsubscribe_packet(buffer buf, error_code& ec) {
using namespace std::literals;
// fixed_header
if (buf.empty()) {
ec = make_error_code(
Expand Down Expand Up @@ -275,7 +286,17 @@ basic_unsubscribe_packet<PacketIdBytes>::basic_unsubscribe_packet(buffer buf, er
return;
}
buf.remove_prefix(topic_length);
entries_.emplace_back(std::string{topic});
auto entry = topic_sharename{std::string{topic}};
// MQTT v5.0 spec: ShareName must not contain "/", "+", or "#"
if (!entry.sharename().empty()) {
if (entry.sharename().find_first_of("/#+"sv) != std::string_view::npos) {
ec = make_error_code(
disconnect_reason_code::malformed_packet
);
return;
}
}
entries_.push_back(force_move(entry));
}
}

Expand Down
58 changes: 56 additions & 2 deletions test/unit/ut_packet_v5_subscribe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE(v5_subscribe) {
try {
std::vector<am::topic_subopts> args {
{
std::string("$share/topic1"), // too long
std::string("$share/topic1"),
am::sub::nl::yes
}
};
Expand All @@ -202,6 +202,46 @@ BOOST_AUTO_TEST_CASE(v5_subscribe) {
BOOST_TEST(se.code() == am::disconnect_reason_code::protocol_error);
}

// ShareName must not contain "+"
try {
std::vector<am::topic_subopts> args {
{
std::string("$share/sn+/topic"),
am::qos::at_most_once
}
};

auto p = am::v5::subscribe_packet{
0x1234, // packet_id
args,
am::properties{}
};
BOOST_TEST(false);
}
catch (am::system_error const& se) {
BOOST_TEST(se.code() == am::disconnect_reason_code::malformed_packet);
}

// ShareName must not contain "#"
try {
std::vector<am::topic_subopts> args {
{
std::string("$share/sn#/topic"),
am::qos::at_most_once
}
};

auto p = am::v5::subscribe_packet{
0x1234, // packet_id
args,
am::properties{}
};
BOOST_TEST(false);
}
catch (am::system_error const& se) {
BOOST_TEST(se.code() == am::disconnect_reason_code::malformed_packet);
}

}

BOOST_AUTO_TEST_CASE(v5_subscribe_pid4) {
Expand Down Expand Up @@ -384,11 +424,25 @@ BOOST_AUTO_TEST_CASE(v5_subscribe_error) {
}
{
// CP RL PID PL TPL TP OPT
am::buffer buf{"\x82\x0e\x12\x34\x00\x00\x08$share/T\x04"sv}; // invalid sharenme + nl
am::buffer buf{"\x82\x0e\x12\x34\x00\x00\x08$share/T\x04"sv}; // invalid sharename + nl
am::error_code ec;
am::v5::subscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::protocol_error);
}
{
// CP RL PID PL TPL TP OPT
am::buffer buf{"\x82\x16\x12\x34\x00\x00\x10$share/sn+/topic\x00"sv}; // sharename contains +
am::error_code ec;
am::v5::subscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::malformed_packet);
}
{
// CP RL PID PL TPL TP OPT
am::buffer buf{"\x82\x16\x12\x34\x00\x00\x10$share/sn#/topic\x00"sv}; // sharename contains #
am::error_code ec;
am::v5::subscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::malformed_packet);
}
}

BOOST_AUTO_TEST_SUITE_END()
48 changes: 48 additions & 0 deletions test/unit/ut_packet_v5_unsubscribe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,40 @@ BOOST_AUTO_TEST_CASE(v5_unsubscribe) {
catch (am::system_error const& se) {
BOOST_TEST(se.code() == am::disconnect_reason_code::malformed_packet);
}

// ShareName must not contain "+"
try {
std::vector<am::topic_sharename> args {
"$share/sn+/topic"
};

auto p = am::v5::unsubscribe_packet{
0x1234, // packet_id
args,
am::properties{}
};
BOOST_TEST(false);
}
catch (am::system_error const& se) {
BOOST_TEST(se.code() == am::disconnect_reason_code::malformed_packet);
}

// ShareName must not contain "#"
try {
std::vector<am::topic_sharename> args {
"$share/sn#/topic"
};

auto p = am::v5::unsubscribe_packet{
0x1234, // packet_id
args,
am::properties{}
};
BOOST_TEST(false);
}
catch (am::system_error const& se) {
BOOST_TEST(se.code() == am::disconnect_reason_code::malformed_packet);
}
}

BOOST_AUTO_TEST_CASE(v5_unsubscribe_pid4) {
Expand Down Expand Up @@ -301,6 +335,20 @@ BOOST_AUTO_TEST_CASE(v5_unsubscribe_error) {
am::v5::unsubscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::malformed_packet);
}
{
// CP RL PID PL TPL TP
am::buffer buf{"\xa2\x15\x12\x34\x00\x00\x10$share/sn+/topic"sv}; // sharename contains +
am::error_code ec;
am::v5::unsubscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::malformed_packet);
}
{
// CP RL PID PL TPL TP
am::buffer buf{"\xa2\x15\x12\x34\x00\x00\x10$share/sn#/topic"sv}; // sharename contains #
am::error_code ec;
am::v5::unsubscribe_packet{buf, ec};
BOOST_TEST(ec == am::disconnect_reason_code::malformed_packet);
}
}

BOOST_AUTO_TEST_SUITE_END()