async_mqtt 5.0.0
Loading...
Searching...
No Matches
v5_connect.hpp
1// Copyright Takatoshi Kondo 2022
2//
3// Distributed under the Boost Software License, Version 1.0.
4// (See accompanying file LICENSE_1_0.txt or copy at
5// http://www.boost.org/LICENSE_1_0.txt)
6
7#if !defined(ASYNC_MQTT_PACKET_V5_CONNECT_HPP)
8#define ASYNC_MQTT_PACKET_V5_CONNECT_HPP
9
10#include <utility>
11#include <numeric>
12
13#include <boost/numeric/conversion/cast.hpp>
14
15#include <async_mqtt/exception.hpp>
16#include <async_mqtt/buffer.hpp>
17#include <async_mqtt/variable_bytes.hpp>
18
19#include <async_mqtt/util/move.hpp>
20#include <async_mqtt/util/static_vector.hpp>
21#include <async_mqtt/util/endian_convert.hpp>
22#include <async_mqtt/util/utf8validate.hpp>
23
24#include <async_mqtt/packet/fixed_header.hpp>
25#include <async_mqtt/packet/copy_to_static_vector.hpp>
26#include <async_mqtt/packet/connect_flags.hpp>
27#include <async_mqtt/packet/will.hpp>
28#include <async_mqtt/packet/property_variant.hpp>
29
30namespace async_mqtt::v5 {
31
32namespace as = boost::asio;
33
41public:
69 bool clean_start,
70 std::uint16_t keep_alive_sec,
73 optional<buffer> password = nullopt,
74 properties props = {}
78 force_move(client_id),
79 nullopt,
80 force_move(user_name),
81 force_move(password),
82 force_move(props)
83 )
84 {}
85
115 bool clean_start,
116 std::uint16_t keep_alive_sec,
119 optional<buffer> user_name = nullopt,
120 optional<buffer> password = nullopt,
121 properties props = {}
122 )
123 : fixed_header_{
124 make_fixed_header(control_packet_type::connect, 0b0000)
125 },
126 connect_flags_{0},
127 // protocol name length, protocol name, protocol level, connect flag, client id length, client id, keep alive
128 remaining_length_(boost::numeric_cast<std::uint32_t>(
129 2 + // protocol name length
130 4 + // protocol name
131 1 + // protocol level
132 1 + // connect flag
133 2 + // keep alive
134 2 + // client id length
135 client_id.size() // client id
136 )),
137 protocol_name_and_level_{0x00, 0x04, 'M', 'Q', 'T', 'T', 0x05},
138 client_id_{force_move(client_id)},
139 client_id_length_buf_(2),
140 keep_alive_buf_(2),
141 property_length_(boost::numeric_cast<std::uint32_t>(async_mqtt::size(props))),
142 props_(force_move(props))
143 {
144 using namespace std::literals;
145 endian_store(keep_alive_sec, keep_alive_buf_.data());
146 endian_store(boost::numeric_cast<std::uint16_t>(client_id_.size()), client_id_length_buf_.data());
147
148 if (!utf8string_check(client_id_)) {
149 throw make_error(
150 errc::bad_message,
151 "v5::connect_packet client_id invalid utf8"
152 );
153 }
154
155 if (clean_start) connect_flags_ |= connect_flags::mask_clean_start;
156 if (user_name) {
157 if (!utf8string_check(*user_name)) {
158 throw make_error(
159 errc::bad_message,
160 "v5::connect_packet user name invalid utf8"
161 );
162 }
163 connect_flags_ |= connect_flags::mask_user_name_flag;
164 user_name_ = force_move(*user_name);
165 user_name_length_buf_ = endian_static_vector(boost::numeric_cast<std::uint16_t>(user_name_.size()));
166 remaining_length_ += 2 + user_name_.size();
167 }
168 if (password) {
169 connect_flags_ |= connect_flags::mask_password_flag;
170 password_ = force_move(*password);
171 password_length_buf_ = endian_static_vector(boost::numeric_cast<std::uint16_t>(password_.size()));
172 remaining_length_ += 2 + password_.size();
173 }
174
175 auto pb = val_to_variable_bytes(boost::numeric_cast<std::uint32_t>(property_length_));
176 for (auto e : pb) {
177 property_length_buf_.push_back(e);
178 }
179
180 for (auto const& prop : props_) {
181 auto id = prop.id();
182 if (!validate_property(property_location::connect, id)) {
183 throw make_error(
184 errc::bad_message,
185 "v5::connect_packet property "s + id_to_str(id) + " is not allowed"
186 );
187 }
188 }
189
190 remaining_length_ += property_length_buf_.size() + property_length_;
191
192 if (w) {
193 connect_flags_ |= connect_flags::mask_will_flag;
194 if (w->get_retain() == pub::retain::yes) connect_flags_ |= connect_flags::mask_will_retain;
195 connect_flags::set_will_qos(connect_flags_, w->get_qos());
196 if (!utf8string_check(w->topic())) {
197 throw make_error(
198 errc::bad_message,
199 "v5::connect_packet will topic invalid utf8"
200 );
201 }
202 will_topic_ = force_move(w->topic());
203 will_topic_length_buf_ = endian_static_vector(boost::numeric_cast<std::uint16_t>(will_topic_.size()));
204 if (w->message().size() > 0xffffL) {
205 throw make_error(
206 errc::bad_message,
207 "v5::connect_packet will message too long"
208 );
209 }
210 will_message_ = force_move(w->message());
211 will_message_length_buf_ = endian_static_vector(boost::numeric_cast<std::uint16_t>(will_message_.size()));
212 will_props_ = force_move(w->props());
213 will_property_length_ = boost::numeric_cast<std::uint32_t>(async_mqtt::size(will_props_));
214 auto pb = val_to_variable_bytes(boost::numeric_cast<std::uint32_t>(will_property_length_));
215 for (auto e : pb) {
216 will_property_length_buf_.push_back(e);
217 }
218
219 for (auto const& prop : will_props_) {
220 auto id = prop.id();
221 if (!validate_property(property_location::will, id)) {
222 throw make_error(
223 errc::bad_message,
224 "v5::connect_packet will_property "s + id_to_str(id) + " is not allowed"
225 );
226 }
227 }
228
229 remaining_length_ +=
230 will_property_length_buf_.size() + will_property_length_ +
231 2 + will_topic_.size() +
232 2 + will_message_.size();
233 }
234
235 auto rb = val_to_variable_bytes(boost::numeric_cast<std::uint32_t>(remaining_length_));
236 for (auto e : rb) {
237 remaining_length_buf_.push_back(e);
238 }
239 }
240
241 connect_packet(buffer buf) {
242 // fixed_header
243 if (buf.empty()) {
244 throw make_error(
245 errc::bad_message,
246 "v5::connect_packet fixed_header doesn't exist"
247 );
248 }
249 fixed_header_ = static_cast<std::uint8_t>(buf.front());
250 buf.remove_prefix(1);
251 auto cpt_opt = get_control_packet_type_with_check(fixed_header_);
252 if (!cpt_opt || *cpt_opt != control_packet_type::connect) {
253 throw make_error(
254 errc::bad_message,
255 "v5::connect_packet fixed_header is invalid"
256 );
257 }
258
259 // remaining_length
260 if (auto vl_opt = insert_advance_variable_length(buf, remaining_length_buf_)) {
261 remaining_length_ = *vl_opt;
262 }
263 else {
264 throw make_error(errc::bad_message, "v5::connect_packet remaining length is invalid");
265 }
266 if (remaining_length_ != buf.size()) {
267 throw make_error(errc::bad_message, "v5::connect_packet remaining length doesn't match buf.size()");
268 }
269
270 // protocol name and level
271 if (!insert_advance(buf, protocol_name_and_level_)) {
272 throw make_error(
273 errc::bad_message,
274 "v5::connect_packet length of protocol_name or level is invalid"
275 );
276 }
277 static_vector<char, 7> expected_protocol_name_and_level {
278 0, 4, 'M', 'Q', 'T', 'T', 5
279 };
280 if (protocol_name_and_level_ != expected_protocol_name_and_level) {
281 throw make_error(
282 errc::bad_message,
283 "v5::connect_packet contents of protocol_name or level is invalid"
284 );
285 }
286
287 // connect_flags
288 if (buf.size() < 1) {
289 throw make_error(
290 errc::bad_message,
291 "v5::connect_packet connect_flags doesn't exist"
292 );
293 }
294 connect_flags_ = buf.front();
295 if (connect_flags_ & 0b00000001) {
296 throw make_error(
297 errc::bad_message,
298 "v5::connect_packet connect_flags reserved bit0 is 1 (must be 0)"
299 );
300 }
301 buf.remove_prefix(1);
302
303 // keep_alive
304 if (!insert_advance(buf, keep_alive_buf_)) {
305 throw make_error(
306 errc::bad_message,
307 "v5::connect_packet keep_alive is invalid"
308 );
309 }
310
311 // property
312 auto it = buf.begin();
313 if (auto pl_opt = variable_bytes_to_val(it, buf.end())) {
314 property_length_ = *pl_opt;
315 std::copy(buf.begin(), it, std::back_inserter(property_length_buf_));
316 buf.remove_prefix(std::size_t(std::distance(buf.begin(), it)));
317 if (buf.size() < property_length_) {
318 throw make_error(
319 errc::bad_message,
320 "v5::connect_packet properties_don't match its length"
321 );
322 }
323 auto prop_buf = buf.substr(0, property_length_);
324 props_ = make_properties(prop_buf, property_location::connect);
325 buf.remove_prefix(property_length_);
326 }
327 else {
328 throw make_error(
329 errc::bad_message,
330 "v5::connect_packet property_length is invalid"
331 );
332 }
333
334 // client_id_length
335 if (!insert_advance(buf, client_id_length_buf_)) {
336 throw make_error(
337 errc::bad_message,
338 "v5::connect_packet length of client_id is invalid"
339 );
340 }
341 auto client_id_length = endian_load<std::uint16_t>(client_id_length_buf_.data());
342
343 // client_id
344 if (buf.size() < client_id_length) {
345 throw make_error(
346 errc::bad_message,
347 "v5::connect_packet client_id doesn't match its length"
348 );
349 }
350 client_id_ = buf.substr(0, client_id_length);
351 if (!utf8string_check(client_id_)) {
352 throw make_error(
353 errc::bad_message,
354 "v5::connect_packet client_id invalid utf8"
355 );
356 }
357 buf.remove_prefix(client_id_length);
358
359 // will
360 if (connect_flags::has_will_flag(connect_flags_)) {
361 // will_qos
362 auto will_qos = connect_flags::will_qos(connect_flags_);
363 if (will_qos != qos::at_most_once &&
364 will_qos != qos::at_least_once &&
365 will_qos != qos::exactly_once) {
366 throw make_error(
367 errc::bad_message,
368 "v5::connect_packet will_qos is invalid"
369 );
370 }
371
372 // will_property
373 auto it = buf.begin();
374 if (auto pl_opt = variable_bytes_to_val(it, buf.end())) {
375 will_property_length_ = *pl_opt;
376 std::copy(buf.begin(), it, std::back_inserter(will_property_length_buf_));
377 buf.remove_prefix(std::size_t(std::distance(buf.begin(), it)));
378 if (buf.size() < will_property_length_) {
379 throw make_error(
380 errc::bad_message,
381 "v5::connect_packet properties_don't match its length"
382 );
383 }
384 auto prop_buf = buf.substr(0, will_property_length_);
385 will_props_ = make_properties(prop_buf, property_location::will);
386 buf.remove_prefix(will_property_length_);
387 }
388 else {
389 throw make_error(
390 errc::bad_message,
391 "v5::connect_packet property_length is invalid"
392 );
393 }
394
395 // will_topic_length
396 if (!insert_advance(buf, will_topic_length_buf_)) {
397 throw make_error(
398 errc::bad_message,
399 "v5::connect_packet length of will_topic is invalid"
400 );
401 }
402 auto will_topic_length = endian_load<std::uint16_t>(will_topic_length_buf_.data());
403
404 // will_topic
405 if (buf.size() < will_topic_length) {
406 throw make_error(
407 errc::bad_message,
408 "v5::connect_packet will_topic doesn't match its length"
409 );
410 }
411 will_topic_ = buf.substr(0, will_topic_length);
412 if (!utf8string_check(will_topic_)) {
413 throw make_error(
414 errc::bad_message,
415 "v5::connect_packet will topic invalid utf8"
416 );
417 }
418 buf.remove_prefix(will_topic_length);
419
420 // will_message_length
421 if (!insert_advance(buf, will_message_length_buf_)) {
422 throw make_error(
423 errc::bad_message,
424 "v5::connect_packet length of will_message is invalid"
425 );
426 }
427 auto will_message_length = endian_load<std::uint16_t>(will_message_length_buf_.data());
428
429 // will_message
430 if (buf.size() < will_message_length) {
431 throw make_error(
432 errc::bad_message,
433 "v5::connect_packet will_message doesn't match its length"
434 );
435 }
436 will_message_ = buf.substr(0, will_message_length);
437 buf.remove_prefix(will_message_length);
438 }
439 else {
440 auto will_retain = connect_flags::will_retain(connect_flags_);
441 auto will_qos = connect_flags::will_qos(connect_flags_);
442 if (will_retain == pub::retain::yes) {
443 throw make_error(
444 errc::bad_message,
445 "v5::connect_packet combination of will_flag and will_retain is invalid"
446 );
447 }
448 if (will_qos != qos::at_most_once) {
449 throw make_error(
450 errc::bad_message,
451 "v5::connect_packet combination of will_flag and will_qos is invalid"
452 );
453 }
454 }
455 // user_name
456 if (connect_flags::has_user_name_flag(connect_flags_)) {
457 // user_name_topic_name_length
458 if (!insert_advance(buf, user_name_length_buf_)) {
459 throw make_error(
460 errc::bad_message,
461 "v5::connect_packet length of user_name is invalid"
462 );
463 }
464 auto user_name_length = endian_load<std::uint16_t>(user_name_length_buf_.data());
465
466 // user_name
467 if (buf.size() < user_name_length) {
468 throw make_error(
469 errc::bad_message,
470 "v5::connect_packet user_name doesn't match its length"
471 );
472 }
473 user_name_ = buf.substr(0, user_name_length);
474 if (!utf8string_check(user_name_)) {
475 throw make_error(
476 errc::bad_message,
477 "v5::connect_packet user name invalid utf8"
478 );
479 }
480 buf.remove_prefix(user_name_length);
481 }
482
483 // password
484 if (connect_flags::has_password_flag(connect_flags_)) {
485 // password_topic_name_length
486 if (!insert_advance(buf, password_length_buf_)) {
487 throw make_error(
488 errc::bad_message,
489 "v5::connect_packet length of password is invalid"
490 );
491 }
492 auto password_length = endian_load<std::uint16_t>(password_length_buf_.data());
493
494 // password
495 if (buf.size() != password_length) {
496 throw make_error(
497 errc::bad_message,
498 "v5::connect_packet password doesn't match its length"
499 );
500 }
501 password_ = buf.substr(0, password_length);
502 buf.remove_prefix(password_length);
503 }
504 }
505
506 constexpr control_packet_type type() const {
507 return control_packet_type::connect;
508 }
509
515 std::vector<as::const_buffer> const_buffer_sequence() const {
516 std::vector<as::const_buffer> ret;
518
519 ret.emplace_back(as::buffer(&fixed_header_, 1));
520 ret.emplace_back(as::buffer(remaining_length_buf_.data(), remaining_length_buf_.size()));
521 ret.emplace_back(as::buffer(protocol_name_and_level_.data(), protocol_name_and_level_.size()));
522 ret.emplace_back(as::buffer(&connect_flags_, 1));
523 ret.emplace_back(as::buffer(keep_alive_buf_.data(), keep_alive_buf_.size()));
524
525 ret.emplace_back(as::buffer(property_length_buf_.data(), property_length_buf_.size()));
526 auto props_cbs = async_mqtt::const_buffer_sequence(props_);
527 std::move(props_cbs.begin(), props_cbs.end(), std::back_inserter(ret));
528
529 ret.emplace_back(as::buffer(client_id_length_buf_.data(), client_id_length_buf_.size()));
530 ret.emplace_back(as::buffer(client_id_));
531
532 if (connect_flags::has_will_flag(connect_flags_)) {
533 ret.emplace_back(as::buffer(will_property_length_buf_.data(), will_property_length_buf_.size()));
534 auto will_props_cbs = async_mqtt::const_buffer_sequence(will_props_);
535 std::move(will_props_cbs.begin(), will_props_cbs.end(), std::back_inserter(ret));
536 ret.emplace_back(as::buffer(will_topic_length_buf_.data(), will_topic_length_buf_.size()));
537 ret.emplace_back(as::buffer(will_topic_));
538 ret.emplace_back(as::buffer(will_message_length_buf_.data(), will_message_length_buf_.size()));
539 ret.emplace_back(as::buffer(will_message_));
540 }
541
542 if (connect_flags::has_user_name_flag(connect_flags_)) {
543 ret.emplace_back(as::buffer(user_name_length_buf_.data(), user_name_length_buf_.size()));
544 ret.emplace_back(as::buffer(user_name_));
545 }
546
547 if (connect_flags::has_password_flag(connect_flags_)) {
548 ret.emplace_back(as::buffer(password_length_buf_.data(), password_length_buf_.size()));
549 ret.emplace_back(as::buffer(password_));
550 }
551
552 return ret;
553 }
554
559 std::size_t size() const {
560 return
561 1 + // fixed header
562 remaining_length_buf_.size() +
563 remaining_length_;
564 }
565
570 std::size_t num_of_const_buffer_sequence() const {
571 return
572 1 + // fixed header
573 1 + // remaining length
574 1 + // protocol name and level
575 1 + // connect flags
576 1 + // keep alive
577 1 + // property length
578 async_mqtt::num_of_const_buffer_sequence(props_) +
579
580 2 + // client id length, client id
581
582 [&] () -> std::size_t {
583 if (connect_flags::has_will_flag(connect_flags_)) {
584 return
585 1 + // will_property length
586 async_mqtt::num_of_const_buffer_sequence(will_props_) +
587 2 + // will topic name length, will topic name
588 2; // will message length, will message
589 }
590 return 0;
591 } () +
592 [&] () -> std::size_t {
593 if (connect_flags::has_user_name_flag(connect_flags_)) {
594 return 2; // user name length, user name
595 }
596 return 0;
597 } () +
598 [&] () -> std::size_t {
599 if (connect_flags::has_password_flag(connect_flags_)) {
600 return 2; // password length, password
601 }
602 return 0;
603 } ();
604 }
605
610 bool clean_start() const {
611 return connect_flags::has_clean_start(connect_flags_);
612 }
613
618 std::uint16_t keep_alive() const {
619 return endian_load<std::uint16_t>(keep_alive_buf_.data());
620 }
621
627 return client_id_;
628 }
629
635 if (connect_flags::has_user_name_flag(connect_flags_)) {
636 return user_name_;
637 }
638 else {
639 return nullopt;
640 }
641 }
642
648 if (connect_flags::has_password_flag(connect_flags_)) {
649 return password_;
650 }
651 else {
652 return nullopt;
653 }
654 }
655
661 if (connect_flags::has_will_flag(connect_flags_)) {
662 pub::opts opts =
663 connect_flags::will_retain(connect_flags_) |
664 connect_flags::will_qos(connect_flags_);
665 return
666 async_mqtt::will{
667 will_topic_,
668 will_message_,
669 opts,
670 will_props_
671 };
672 }
673 else {
674 return nullopt;
675 }
676 }
677
682 properties const& props() const {
683 return props_;
684 }
685
686private:
687 std::uint8_t fixed_header_;
688 char connect_flags_;
689
690 std::size_t remaining_length_;
691 static_vector<char, 4> remaining_length_buf_;
692
693 static_vector<char, 7> protocol_name_and_level_;
694 buffer client_id_;
695 static_vector<char, 2> client_id_length_buf_;
696
697 buffer will_topic_;
698 static_vector<char, 2> will_topic_length_buf_;
699 buffer will_message_;
700 static_vector<char, 2> will_message_length_buf_;
701 std::size_t will_property_length_;
702 static_vector<char, 4> will_property_length_buf_;
703 properties will_props_;
704
705 buffer user_name_;
706 static_vector<char, 2> user_name_length_buf_;
707 buffer password_;
708 static_vector<char, 2> password_length_buf_;
709
710 static_vector<char, 2> keep_alive_buf_;
711
712 std::size_t property_length_;
713 static_vector<char, 4> property_length_buf_;
714 properties props_;
715};
716
717inline std::ostream& operator<<(std::ostream& o, connect_packet const& v) {
718 o <<
719 "v5::connect{" <<
720 "cid:" << v.client_id() << "," <<
721 "ka:" << v.keep_alive() << "," <<
722 "cs:" << v.clean_start();
723 if (v.user_name()) {
724 o << ",un:" << *v.user_name();
725 }
726 if (v.password()) {
727 o << ",pw:" << "*****";
728 }
729 if (v.get_will()) {
730 o << ",will:" << *v.get_will();
731 }
732 if (!v.props().empty()) {
733 o << ",ps:" << v.props();
734 };
735 o << "}";
736 return o;
737}
738
739} // namespace async_mqtt::v5
740
741#endif // ASYNC_MQTT_PACKET_V5_CONNECT_HPP
Definition packet_variant.hpp:49
buffer that has string_view interface This class provides string_view interface. This class hold stri...
Definition buffer.hpp:30
buffer substr(size_type pos=0, size_type count=npos) const &
get substring The returned buffer ragnge is the same as string_view::substr(). In addition the lifeti...
Definition buffer.hpp:201
MQTT CONNECT packet (v5)
Definition v5_connect.hpp:40
connect_packet(bool clean_start, std::uint16_t keep_alive_sec, buffer client_id, optional< buffer > user_name=nullopt, optional< buffer > password=nullopt, properties props={})
Definition v5_connect.hpp:68
connect_packet(bool clean_start, std::uint16_t keep_alive_sec, buffer client_id, optional< will > w, optional< buffer > user_name=nullopt, optional< buffer > password=nullopt, properties props={})
Definition v5_connect.hpp:114
std::size_t size() const
Get packet size.
Definition v5_connect.hpp:559
optional< buffer > user_name() const
Get user_name.
Definition v5_connect.hpp:634
properties const & props() const
Definition v5_connect.hpp:682
std::uint16_t keep_alive() const
Get keep_alive.
Definition v5_connect.hpp:618
std::vector< as::const_buffer > const_buffer_sequence() const
Create const buffer sequence it is for boost asio APIs.
Definition v5_connect.hpp:515
optional< will > get_will() const
Get will.
Definition v5_connect.hpp:660
bool clean_start() const
Get clean_start.
Definition v5_connect.hpp:610
buffer client_id() const
Get client_id.
Definition v5_connect.hpp:626
optional< buffer > password() const
Get password.
Definition v5_connect.hpp:647
std::size_t num_of_const_buffer_sequence() const
Get number of element of const_buffer_sequence.
Definition v5_connect.hpp:570
MQTT PublishOptions.
Definition pubopts.hpp:87