File indexing completed on 2025-09-18 08:51:36
0001
0002
0003
0004
0005
0006
0007
0008 #ifndef BOOST_MQTT5_BASE_ENCODERS_HPP
0009 #define BOOST_MQTT5_BASE_ENCODERS_HPP
0010
0011 #include <boost/mqtt5/property_types.hpp>
0012
0013 #include <boost/mqtt5/detail/traits.hpp>
0014
0015 #include <boost/core/identity.hpp>
0016 #include <boost/endian/conversion.hpp>
0017 #include <boost/type_traits/is_detected_exact.hpp>
0018 #include <boost/type_traits/remove_cv_ref.hpp>
0019
0020 #include <cstddef>
0021 #include <cstdint>
0022 #include <string>
0023 #include <type_traits>
0024 #include <utility>
0025
0026 namespace boost::mqtt5::encoders {
0027
0028 namespace basic {
0029
0030 using varint_t = int*;
0031
0032 inline void to_variable_bytes(std::string& s, int32_t val) {
0033 if (val > 0xfffffff) return;
0034 while (val > 127) {
0035 s.push_back(char((val & 0b01111111) | 0b10000000));
0036 val >>= 7;
0037 }
0038 s.push_back(val & 0b01111111);
0039 }
0040
0041 inline size_t variable_length(int32_t val) {
0042 if (val > 0xfffffff) return 0;
0043 size_t rv = 1;
0044 for (; val > 127; ++rv) val >>= 7;
0045 return rv;
0046 }
0047
0048 struct encoder {};
0049
0050 template <size_t bits, typename repr = uint8_t>
0051 class flag_def : public encoder {
0052 template <size_t num_bits>
0053 using least_type = std::conditional_t<
0054 num_bits <= 8, uint8_t,
0055 std::conditional_t<
0056 num_bits <= 16, uint16_t,
0057 std::conditional_t<
0058 num_bits <= 32, uint32_t,
0059 std::conditional_t<num_bits <= 64, uint64_t, void>
0060 >
0061 >
0062 >;
0063
0064 template <size_t oth_bits, typename oth_repr>
0065 friend class flag_def;
0066
0067 repr _val { 0 };
0068
0069 public:
0070 flag_def(repr val) : _val(val) {}
0071 flag_def() = default;
0072
0073 template <
0074 typename T,
0075 typename projection = boost::identity,
0076 std::enable_if_t<detail::is_optional<T>, bool> = true
0077 >
0078 auto operator()(T&& value, projection proj = {}) const {
0079 if constexpr (std::is_same_v<projection, boost::identity>) {
0080 repr val = value.has_value();
0081 return flag_def<bits, repr> { val };
0082 }
0083 else {
0084 repr val = value.has_value() ?
0085 static_cast<repr>(std::invoke(proj, *value)) : 0;
0086 return flag_def<bits, repr> { val };
0087 }
0088 }
0089
0090 template <
0091 typename T,
0092 typename projection = boost::identity,
0093 std::enable_if_t<!detail::is_optional<T>, bool> = true
0094 >
0095 auto operator()(T&& value, projection proj = {}) const {
0096 auto val = static_cast<repr>(std::invoke(proj, value));
0097 return flag_def<bits, repr> { val };
0098 }
0099
0100 size_t byte_size() const { return sizeof(repr); }
0101
0102 template <size_t rhs_bits, typename rhs_repr>
0103 auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
0104 using res_repr = least_type<bits + rhs_bits>;
0105 auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
0106 return flag_def<bits + rhs_bits, res_repr> { val };
0107 }
0108
0109 std::string& encode(std::string& s) const {
0110 using namespace boost::endian;
0111 size_t sz = s.size(); s.resize(sz + sizeof(repr));
0112 auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
0113 endian_store<repr, sizeof(repr), order::big>(p, _val);
0114 return s;
0115 }
0116 };
0117
0118 template <size_t bits, typename repr = uint8_t>
0119 constexpr auto flag = flag_def<bits, repr>{};
0120
0121
0122 template <typename T, typename Repr>
0123 class int_val : public encoder {
0124 T _val;
0125 public:
0126 int_val(T val) : _val(val) {}
0127
0128 size_t byte_size() const {
0129 if constexpr (detail::is_optional<T>) {
0130 if (_val) return val_length(*_val);
0131 return 0;
0132 }
0133 else
0134 return val_length(_val);
0135 }
0136
0137 std::string& encode(std::string& s) const {
0138 if constexpr (detail::is_optional<T>) {
0139 if (_val) return encode_val(s, *_val);
0140 return s;
0141 }
0142 else
0143 return encode_val(s, _val);
0144 }
0145 private:
0146 template <typename U>
0147 static size_t val_length(U&& val) {
0148 if constexpr (std::is_same_v<Repr, varint_t>)
0149 return variable_length(int32_t(val));
0150 else
0151 return sizeof(Repr);
0152 }
0153
0154 template <typename U>
0155 static std::string& encode_val(std::string& s, U&& val) {
0156 using namespace boost::endian;
0157 if constexpr (std::is_same_v<Repr, varint_t>) {
0158 to_variable_bytes(s, int32_t(val));
0159 return s;
0160 }
0161 else {
0162 size_t sz = s.size(); s.resize(sz + sizeof(Repr));
0163 auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
0164 endian_store<Repr, sizeof(Repr), order::big>(p, val);
0165 return s;
0166 }
0167 }
0168 };
0169
0170 template <typename Repr>
0171 class int_def {
0172 public:
0173 template <typename T>
0174 auto operator()(T&& val) const {
0175 return int_val<T, Repr> { std::forward<T>(val) };
0176 }
0177
0178 template <typename T, typename projection>
0179 auto operator()(T&& val, projection proj) const {
0180 if constexpr (detail::is_optional<T>) {
0181 using rv_type = std::invoke_result_t<
0182 projection, typename boost::remove_cv_ref_t<T>::value_type
0183 >;
0184 if (val.has_value())
0185 return (*this)(std::invoke(proj, *val));
0186 return int_val<rv_type, Repr> { rv_type {} };
0187 }
0188 else {
0189 using rv_type = std::invoke_result_t<projection, T>;
0190 return int_val<rv_type, Repr> { std::invoke(proj, val) };
0191 }
0192 }
0193 };
0194
0195 constexpr auto byte_ = int_def<uint8_t> {};
0196 constexpr auto int16_ = int_def<uint16_t> {};
0197 constexpr auto int32_ = int_def<uint32_t> {};
0198 constexpr auto varlen_ = int_def<varint_t> {};
0199
0200
0201 template <typename T>
0202 class array_val : public encoder {
0203 T _val;
0204 bool _with_length;
0205 public:
0206 array_val(T val, bool with_length) :
0207 _val(val), _with_length(with_length)
0208 {
0209 static_assert(
0210 std::is_reference_v<T> || std::is_same_v<T, std::string_view>
0211 );
0212 }
0213
0214 size_t byte_size() const {
0215 if constexpr (detail::is_optional<T>)
0216 return _val ? _with_length * 2 + val_length(*_val) : 0;
0217 else
0218 return _with_length * 2 + val_length(_val);
0219 }
0220
0221 std::string& encode(std::string& s) const {
0222 if constexpr (detail::is_optional<T>) {
0223 if (_val) return encode_val(s, *_val);
0224 return s;
0225 }
0226 else
0227 return encode_val(s, _val);
0228 }
0229
0230 private:
0231 template <typename V>
0232 using has_size = decltype(std::declval<V&>().size());
0233
0234 template <typename U>
0235 static size_t val_length(U&& val) {
0236 if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
0237 return std::strlen(val);
0238
0239 if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
0240 return val.size();
0241 else
0242 return sizeof(val) - 1;
0243 }
0244
0245 template <typename U>
0246 std::string& encode_val(std::string& s, U&& u) const {
0247 using namespace boost::endian;
0248 auto byte_len = val_length(std::forward<U>(u));
0249 if (byte_len == 0 && !_with_length) return s;
0250 if (_with_length) {
0251 size_t sz = s.size(); s.resize(sz + 2);
0252 auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
0253 endian_store<int16_t, sizeof(int16_t), order::big>(
0254 p, int16_t(byte_len)
0255 );
0256 }
0257 s.append(std::begin(u), std::begin(u) + byte_len);
0258 return s;
0259 }
0260 };
0261
0262 template <bool with_length = true>
0263 class array_def {
0264 public:
0265 template <typename T>
0266 auto operator()(T&& val) const {
0267 return array_val<T> { std::forward<T>(val), with_length };
0268 }
0269
0270 template <typename T, typename projection>
0271 auto operator()(T&& val, projection proj) const {
0272 if constexpr (detail::is_optional<T>) {
0273 using rv_type = std::invoke_result_t<
0274 projection, typename boost::remove_cv_ref_t<T>::value_type
0275 >;
0276 if (val.has_value())
0277 return (*this)(std::invoke(proj, *val));
0278 return array_val<rv_type> { rv_type {}, false };
0279 }
0280 else {
0281 const auto& av = std::invoke(proj, val);
0282 return array_val<T> { av, true };
0283 }
0284 }
0285 };
0286
0287 using utf8_def = array_def<true>;
0288
0289 constexpr auto utf8_ = utf8_def {};
0290 constexpr auto binary_ = array_def<true> {};
0291 constexpr auto verbatim_ = array_def<false> {};
0292
0293
0294 template <typename T, typename U>
0295 class composed_val : public encoder {
0296 T _lhs; U _rhs;
0297 public:
0298 composed_val(T lhs, U rhs) :
0299 _lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
0300 {}
0301
0302 size_t byte_size() const {
0303 return _lhs.byte_size() + _rhs.byte_size();
0304 }
0305
0306 std::string& encode(std::string& s) const {
0307 _lhs.encode(s);
0308 return _rhs.encode(s);
0309 }
0310 };
0311
0312 template <
0313 typename T, typename U,
0314 std::enable_if_t<
0315 std::is_base_of_v<encoder, std::decay_t<T>> &&
0316 std::is_base_of_v<encoder, std::decay_t<U>>,
0317 bool
0318 > = true
0319 >
0320 inline auto operator&(T&& t, U&& u) {
0321 return composed_val(std::forward<T>(t), std::forward<U>(u));
0322 }
0323
0324 template <
0325 typename T,
0326 std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<T>>, bool> = true
0327 >
0328 std::string& operator<<(std::string& s, T&& t) {
0329 return t.encode(s);
0330 }
0331
0332 }
0333
0334 namespace prop {
0335
0336 namespace pp = boost::mqtt5::prop;
0337
0338 template <typename T>
0339 auto encoder_for_prop_value(const T& val) {
0340 if constexpr (std::is_same_v<T, uint8_t>)
0341 return basic::int_def<uint8_t>{}(val);
0342 else if constexpr (std::is_same_v<T, uint16_t>)
0343 return basic::int_def<uint16_t>{}(val);
0344 else if constexpr (std::is_same_v<T, int32_t>)
0345 return basic::int_def<basic::varint_t>{}(val);
0346 else if constexpr (std::is_same_v<T, uint32_t>)
0347 return basic::int_def<uint32_t>{}(val);
0348 else if constexpr (std::is_same_v<T, std::string>)
0349 return basic::utf8_def{}(val);
0350 else if constexpr (detail::is_pair<T>)
0351 return encoder_for_prop_value(val.first) &
0352 encoder_for_prop_value(val.second);
0353 }
0354
0355 template <typename T, pp::property_type p, typename Enable = void>
0356 class prop_val;
0357
0358 template <
0359 typename T, pp::property_type p
0360 >
0361 class prop_val<
0362 T, p,
0363 std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
0364 > : public basic::encoder {
0365
0366 static inline boost::remove_cv_ref_t<T> nulltype;
0367 T _val;
0368 public:
0369 prop_val(T val) : _val(val) {
0370 static_assert(std::is_reference_v<T>);
0371 }
0372 prop_val() : _val(nulltype) {}
0373
0374 size_t byte_size() const {
0375 if (!_val) return 0;
0376 return 1 + encoder_for_prop_value(*_val).byte_size();
0377 }
0378
0379 std::string& encode(std::string& s) const {
0380 if (!_val)
0381 return s;
0382 s.push_back(p);
0383 return encoder_for_prop_value(*_val).encode(s);
0384 }
0385 };
0386
0387 template <
0388 typename T, pp::property_type p
0389 >
0390 class prop_val<
0391 T, p,
0392 std::enable_if_t<detail::is_vector<T> || detail::is_small_vector<T>>
0393 > : public basic::encoder {
0394
0395 static inline boost::remove_cv_ref_t<T> nulltype;
0396 T _val;
0397 public:
0398 prop_val(T val) : _val(val) {
0399 static_assert(std::is_reference_v<T>);
0400 }
0401
0402 prop_val() : _val(nulltype) { }
0403
0404 size_t byte_size() const {
0405 if (_val.empty()) return 0;
0406
0407 size_t total_size = 0;
0408 for (const auto& elem : _val)
0409 total_size += 1 + encoder_for_prop_value(elem).byte_size();
0410
0411 return total_size;
0412 }
0413
0414 std::string& encode(std::string& s) const {
0415 if (_val.empty())
0416 return s;
0417
0418 for (const auto& elem: _val) {
0419 s.push_back(p);
0420 encoder_for_prop_value(elem).encode(s);
0421 }
0422
0423 return s;
0424 }
0425 };
0426
0427
0428 template <typename Props>
0429 class props_val : public basic::encoder {
0430 static inline std::decay_t<Props> nulltype;
0431
0432 template <pp::property_type P, typename T>
0433 static auto to_prop_val(const T& val) {
0434 return prop_val<const T&, P>(val);
0435 }
0436
0437 template <pp::property_type ...Ps>
0438 static auto to_prop_vals(const pp::properties<Ps...>& props) {
0439 return std::make_tuple(
0440 to_prop_val<Ps>(
0441 props[std::integral_constant<pp::property_type, Ps> {}]
0442 )...
0443 );
0444 }
0445
0446 template <typename Func>
0447 auto apply_each(Func&& func) const {
0448 return std::apply([&func](const auto&... props) {
0449 return (std::invoke(func, props), ...);
0450 }, _prop_vals);
0451 }
0452
0453 decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
0454 bool _may_omit;
0455
0456 public:
0457 props_val(Props val, bool may_omit) :
0458 _prop_vals(to_prop_vals(val)), _may_omit(may_omit)
0459 {
0460 static_assert(std::is_reference_v<Props>);
0461 }
0462 props_val(bool may_omit) :
0463 _prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
0464 {}
0465
0466 size_t byte_size() const {
0467 size_t psize = props_size();
0468 if (_may_omit && psize == 0) return 0;
0469 return psize + basic::varlen_(psize).byte_size();
0470 }
0471
0472 std::string& encode(std::string& s) const {
0473 size_t psize = props_size();
0474 if (_may_omit && psize == 0) return s;
0475 basic::varlen_(psize).encode(s);
0476 apply_each([&s](const auto& pv) { return pv.encode(s); });
0477 return s;
0478 }
0479 private:
0480 size_t props_size() const {
0481 size_t retval = 0;
0482 apply_each([&retval](const auto& pv) {
0483 return retval += pv.byte_size();
0484 });
0485 return retval;
0486 }
0487 };
0488
0489 template <bool may_omit>
0490 class props_def {
0491 public:
0492 template <typename T>
0493 auto operator()(T&& prop_container) const {
0494 if constexpr (detail::is_optional<T>) {
0495 if (prop_container.has_value())
0496 return (*this)(*prop_container);
0497 return props_val<
0498 const typename boost::remove_cv_ref_t<T>::value_type&
0499 >(true);
0500 }
0501 else {
0502 return props_val<T> { prop_container, may_omit };
0503 }
0504 }
0505 };
0506
0507 constexpr auto props_ = props_def<false> {};
0508 constexpr auto props_may_omit_ = props_def<true> {};
0509
0510 }
0511
0512 }
0513
0514 #endif