From: Jesse F. Williamson Date: Fri, 24 Jul 2026 14:23:01 +0000 (-0700) Subject: Conceptify and Helper-size encoding.h internals X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fheads%2Fjfw-concepts-encoding;p=ceph.git Conceptify and Helper-size encoding.h internals Assisted-by: Codex:GPT-5 Signed-off-by: Jesse F. Williamson --- diff --git a/src/crimson/osd/recovery_backend.cc b/src/crimson/osd/recovery_backend.cc index d40fb1704d31..255be74005cf 100644 --- a/src/crimson/osd/recovery_backend.cc +++ b/src/crimson/osd/recovery_backend.cc @@ -423,8 +423,7 @@ RecoveryBackend::handle_scan_digest( { auto p = m.get_data().cbegin(); // take care to preserve ordering! - bi.clear_objects(); - ::decode_noclear(bi.objects, p); + ::decode(bi.objects, p); } auto recovery_handler = pg.get_recovery_handler(); recovery_handler->dispatch_backfill_event( diff --git a/src/include/denc.h b/src/include/denc.h index cbe17a725666..119f040aa7a6 100644 --- a/src/include/denc.h +++ b/src/include/denc.h @@ -1377,93 +1377,86 @@ public: } }; -// -// boost::optional -// -template -struct denc_traits< - boost::optional, - std::enable_if_t::supported>> { - using traits = denc_traits; +namespace _denc { + template + void clear_optional(Optional& v) { + v.reset(); + } - static constexpr bool supported = true; - static constexpr bool featured = traits::featured; - static constexpr bool bounded = false; - static constexpr bool need_contiguous = traits::need_contiguous; + template + struct optional_base { + using traits = denc_traits; - static void bound_encode(const boost::optional& v, size_t& p, - uint64_t f = 0) { - p += sizeof(bool); - if (v) { - if constexpr (featured) { + static constexpr bool supported = true; + static constexpr bool featured = traits::featured; + static constexpr bool bounded = false; + static constexpr bool need_contiguous = traits::need_contiguous; + + static void bound_encode(const Optional& v, size_t& p, uint64_t f = 0) { + p += sizeof(bool); + if (v) { denc(*v, p, f); - } else { - denc(*v, p); } } - } - static void encode(const boost::optional& v, - ceph::buffer::list::contiguous_appender& p, - uint64_t f = 0) { - denc((bool)v, p); - if (v) { - if constexpr (featured) { - denc(*v, p, f); - } else { - denc(*v, p); - } + static void encode(const Optional& v, + ceph::buffer::list::contiguous_appender& p, + uint64_t f = 0) { + denc((bool)v, p); + encode_nohead(v, p, f); } - } - static void decode(boost::optional& v, ceph::buffer::ptr::const_iterator& p, - uint64_t f = 0) { - bool x; - denc(x, p, f); - if (x) { - v = T{}; - denc(*v, p, f); - } else { - v = boost::none; + static void decode(Optional& v, ceph::buffer::ptr::const_iterator& p, + uint64_t f = 0) { + bool x; + denc(x, p, f); + decode_nohead(x, v, p, f); } - } - template - static std::enable_if_t - decode(boost::optional& v, ceph::buffer::list::const_iterator& p) { - bool x; - denc(x, p); - if (x) { - v = T{}; - denc(*v, p); - } else { - v = boost::none; + static void decode(Optional& v, ceph::buffer::list::const_iterator& p) + requires (!need_contiguous) + { + bool x; + denc(x, p); + if (x) { + v = T{}; + denc(*v, p); + return; + } + + clear_optional(v); } - } - template - static void encode_nohead(const boost::optional& v, - ceph::buffer::list::contiguous_appender& p, - uint64_t f = 0) { - if (v) { - if constexpr (featured) { + static void encode_nohead(const Optional& v, + ceph::buffer::list::contiguous_appender& p, + uint64_t f = 0) { + if (v) { denc(*v, p, f); - } else { - denc(*v, p); } } - } - static void decode_nohead(bool num, boost::optional& v, - ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { - if (num) { - v = T(); - denc(*v, p, f); - } else { - v = boost::none; + static void decode_nohead(bool present, Optional& v, + ceph::buffer::ptr::const_iterator& p, + uint64_t f = 0) { + if (present) { + v = T{}; + denc(*v, p, f); + return; + } + + clear_optional(v); } - } -}; + }; +} + +// +// boost::optional +// +template +struct denc_traits< + boost::optional, + std::enable_if_t::supported>> + : public _denc::optional_base, T> {}; template<> struct denc_traits { @@ -1488,86 +1481,8 @@ struct denc_traits { template struct denc_traits< std::optional, - std::enable_if_t::supported>> { - using traits = denc_traits; - - static constexpr bool supported = true; - static constexpr bool featured = traits::featured; - static constexpr bool bounded = false; - static constexpr bool need_contiguous = traits::need_contiguous; - - static void bound_encode(const std::optional& v, size_t& p, - uint64_t f = 0) { - p += sizeof(bool); - if (v) { - if constexpr (featured) { - denc(*v, p, f); - } else { - denc(*v, p); - } - } - } - - static void encode(const std::optional& v, - ceph::buffer::list::contiguous_appender& p, - uint64_t f = 0) { - denc((bool)v, p); - if (v) { - if constexpr (featured) { - denc(*v, p, f); - } else { - denc(*v, p); - } - } - } - - static void decode(std::optional& v, ceph::buffer::ptr::const_iterator& p, - uint64_t f = 0) { - bool x; - denc(x, p, f); - if (x) { - v = T{}; - denc(*v, p, f); - } else { - v = std::nullopt; - } - } - - template - static std::enable_if_t - decode(std::optional& v, ceph::buffer::list::const_iterator& p) { - bool x; - denc(x, p); - if (x) { - v = T{}; - denc(*v, p); - } else { - v = std::nullopt; - } - } - - static void encode_nohead(const std::optional& v, - ceph::buffer::list::contiguous_appender& p, - uint64_t f = 0) { - if (v) { - if constexpr (featured) { - denc(*v, p, f); - } else { - denc(*v, p); - } - } - } - - static void decode_nohead(bool num, std::optional& v, - ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { - if (num) { - v = T(); - denc(*v, p, f); - } else { - v = std::nullopt; - } - } -}; + std::enable_if_t::supported>> + : public _denc::optional_base, T> {}; template<> struct denc_traits { @@ -1785,13 +1700,77 @@ inline std::enable_if_t decode_nohead( // interoperability. [[maybe_unused]] static void denc_compat_throw( - const char* _pretty_function_, uint8_t code_v, + const char *_pretty_function_, uint8_t code_v, uint8_t struct_v, uint8_t struct_compat) { throw ::ceph::buffer::malformed_input("Decoder at '" + std::string(_pretty_function_) + "' v=" + std::to_string(code_v)+ " cannot decode v=" + std::to_string(struct_v) + " minimal_decoder=" + std::to_string(struct_compat)); } +namespace ceph::denc_detail { + +inline constexpr auto struct_header_len() noexcept +{ + return sizeof(__u8) + sizeof(__u8) + sizeof(uint32_t); +} + +inline void start(size_t& p, __u8 *, __u8 *, char **, uint32_t *) +{ + p += struct_header_len(); +} + +inline void finish(size_t&, char **, uint32_t *) {} + +inline void start(::ceph::buffer::list::contiguous_appender& p, + __u8 *struct_v, __u8 *struct_compat, + char **len_pos, uint32_t *start_oob_off) +{ + ::denc(*struct_v, p); + ::denc(*struct_compat, p); + *len_pos = p.get_pos_add(sizeof(uint32_t)); + *start_oob_off = p.get_out_of_band_offset(); +} + +inline void finish(::ceph::buffer::list::contiguous_appender& p, + char **len_pos, uint32_t *start_oob_off) +{ + ceph_le32 struct_len; + struct_len = p.get_pos() - *len_pos - sizeof(uint32_t) + + p.get_out_of_band_offset() - *start_oob_off; + std::memcpy(*len_pos, &struct_len, sizeof(struct_len)); +} + +inline void start(::ceph::buffer::ptr::const_iterator& p, + __u8 *struct_v, __u8 *struct_compat, + char **start_pos, uint32_t *struct_len, + const char *func) +{ + const auto code_v = *struct_v; + ::denc(*struct_v, p); + ::denc(*struct_compat, p); + if (unlikely(code_v < *struct_compat)) { + ::denc_compat_throw(func, code_v, *struct_v, *struct_compat); + } + ::denc(*struct_len, p); + *start_pos = const_cast(p.get_pos()); +} + +inline void finish(::ceph::buffer::ptr::const_iterator& p, + char **start_pos, uint32_t *struct_len, + const char *func) +{ + const auto pos = p.get_pos(); + const auto end = *start_pos + *struct_len; + if (pos > end) { + throw ::ceph::buffer::malformed_input(func); + } + if (pos < end) { + p += end - pos; + } +} + +} // namespace ceph::denc_detail + // compile-time checker for struct_v to detect mismatch of declared // decoder version with actually implemented blocks like "struct_v < 100". // it addresses the common problem of forgetting to bump the version up @@ -1832,27 +1811,27 @@ struct StructVChecker static void _denc_start(size_t& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ - char **, uint32_t *) { \ - p += 2 + 4; \ + char **len_pos, uint32_t *start_oob_off) { \ + ::ceph::denc_detail::start(p, struct_v, struct_compat, len_pos, \ + start_oob_off); \ } \ static void _denc_finish(size_t& p, \ - char **, uint32_t *) { } \ + char **len_pos, uint32_t *start_oob_off) { \ + ::ceph::denc_detail::finish(p, len_pos, start_oob_off); \ + } \ /* encode */ \ static void _denc_start(::ceph::buffer::list::contiguous_appender& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ char **len_pos, \ uint32_t *start_oob_off) { \ - denc(*struct_v, p); \ - denc(*struct_compat, p); \ - *len_pos = p.get_pos_add(4); \ - *start_oob_off = p.get_out_of_band_offset(); \ + ::ceph::denc_detail::start(p, struct_v, struct_compat, len_pos, \ + start_oob_off); \ } \ static void _denc_finish(::ceph::buffer::list::contiguous_appender& p, \ char **len_pos, \ uint32_t *start_oob_off) { \ - *(ceph_le32*)*len_pos = p.get_pos() - *len_pos - sizeof(uint32_t) + \ - p.get_out_of_band_offset() - *start_oob_off; \ + ::ceph::denc_detail::finish(p, len_pos, start_oob_off); \ } \ /* decode */ \ static void _denc_start(::ceph::buffer::ptr::const_iterator& p, \ @@ -1860,25 +1839,14 @@ struct StructVChecker __u8 *struct_compat, \ char **start_pos, \ uint32_t *struct_len) { \ - __u8 code_v = *struct_v; \ - denc(*struct_v, p); \ - denc(*struct_compat, p); \ - if (unlikely(code_v < *struct_compat)) \ - denc_compat_throw(__PRETTY_FUNCTION__, code_v, *struct_v, *struct_compat);\ - denc(*struct_len, p); \ - *start_pos = const_cast(p.get_pos()); \ + ::ceph::denc_detail::start(p, struct_v, struct_compat, start_pos, \ + struct_len, __PRETTY_FUNCTION__); \ } \ static void _denc_finish(::ceph::buffer::ptr::const_iterator& p, \ char **start_pos, \ uint32_t *struct_len) { \ - const char *pos = p.get_pos(); \ - char *end = *start_pos + *struct_len; \ - if (pos > end) { \ - throw ::ceph::buffer::malformed_input(__PRETTY_FUNCTION__); \ - } \ - if (pos < end) { \ - p += end - pos; \ - } \ + ::ceph::denc_detail::finish(p, start_pos, struct_len, \ + __PRETTY_FUNCTION__); \ } // Helpers for versioning the encoding. These correspond to the diff --git a/src/include/encoding.h b/src/include/encoding.h index d073c1e7c627..10493e614d3b 100644 --- a/src/include/encoding.h +++ b/src/include/encoding.h @@ -1,16 +1,17 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- // vim: ts=8 sw=2 sts=2 expandtab /* * Ceph - scalable distributed file system * * Copyright (C) 2004-2006 Sage Weil + * Copyright (C) 2026 International Business Machines Corp. (IBM) * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software + * License version 2.1, as published by the Free Software * Foundation. See file COPYING. - * + * */ #ifndef CEPH_ENCODING_H #define CEPH_ENCODING_H @@ -26,13 +27,35 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include + #include "common/ceph_time.h" +#include "include/compat.h" #include "include/int_types.h" #include "common/convenience.h" @@ -138,8 +161,8 @@ WRITE_INTTYPE_ENCODER(int16_t, le16) // Under this assumption, we can use raw encoding of floating-point types // on little-endian machines, but we still need to perform a byte swap // on big-endian machines to ensure cross-architecture compatibility. -// To achive that, we reinterpret the values as integers first, which are -// byte-swapped via the ceph_le types as above. The extra conversions +// To achieve that, we bit-cast the values as integers first, which are +// byte-swapped via the ceph_le types as above. The extra conversions // are optimized away on little-endian machines by the compiler. #define WRITE_FLTTYPE_ENCODER(type, itype, etype) \ static_assert(sizeof(type) == sizeof(itype)); \ @@ -147,13 +170,14 @@ WRITE_INTTYPE_ENCODER(int16_t, le16) "floating-point type not using IEEE754 format"); \ inline void encode(type v, ::ceph::bufferlist& bl, uint64_t features=0) { \ ceph_##etype e; \ - e = *reinterpret_cast(&v); \ + e = std::bit_cast(v); \ ::ceph::encode_raw(e, bl); \ } \ inline void decode(type &v, ::ceph::bufferlist::const_iterator& p) { \ ceph_##etype e; \ ::ceph::decode_raw(e, p); \ - *reinterpret_cast(&v) = e; \ + itype raw = e; \ + v = std::bit_cast(raw); \ } WRITE_FLTTYPE_ENCODER(float, uint32_t, le32) @@ -208,14 +232,39 @@ WRITE_FLTTYPE_ENCODER(double, uint64_t, le64) ENCODE_DUMP_PRE(); c.encode(bl, features); ENCODE_DUMP_POST(cl); } \ inline void decode(cl &c, ::ceph::bufferlist::const_iterator &p) { c.decode(p); } +namespace encoding_detail { + +inline void encode_count(size_t n, bufferlist& bl) +{ + // Legacy container/string lengths are stored as 32-bit values on the wire. + encode(static_cast<__u32>(n), bl); +} + +inline __u32 decode_count(bufferlist::const_iterator& p) +{ + __u32 n; + decode(n, p); + return n; +} + +inline void append_bytes(const void *data, size_t len, bufferlist& bl) +{ + if (len) + bl.append(static_cast(data), len); +} + +inline void encode_bytes(const void *data, size_t len, bufferlist& bl) +{ + encode_count(len, bl); + append_bytes(data, len, bl); +} + +} // namespace encoding_detail // string inline void encode(std::string_view s, bufferlist& bl, uint64_t features=0) { - __u32 len = s.length(); - encode(len, bl); - if (len) - bl.append(s.data(), len); + encoding_detail::encode_bytes(s.data(), s.length(), bl); } inline void encode(const std::string& s, bufferlist& bl, uint64_t features=0) { @@ -223,15 +272,14 @@ inline void encode(const std::string& s, bufferlist& bl, uint64_t features=0) } inline void decode(std::string& s, bufferlist::const_iterator& p) { - __u32 len; - decode(len, p); + const auto len = encoding_detail::decode_count(p); s.clear(); p.copy(len, s); } inline void encode_nohead(std::string_view s, bufferlist& bl) { - bl.append(s.data(), s.length()); + encoding_detail::append_bytes(s.data(), s.length(), bl); } inline void encode_nohead(const std::string& s, bufferlist& bl) { @@ -244,7 +292,7 @@ inline void decode_nohead(unsigned len, std::string& s, bufferlist::const_iterat } // const char* (encode only, string compatible) -inline void encode(const char *s, bufferlist& bl) +inline void encode(const char *s, bufferlist& bl) { encode(std::string_view(s, strlen(s)), bl); } @@ -252,17 +300,12 @@ inline void encode(const char *s, bufferlist& bl) // opaque byte vectors inline void encode(std::vector& v, bufferlist& bl) { - uint32_t len = v.size(); - encode(len, bl); - if (len) - bl.append((char *)v.data(), len); + encoding_detail::encode_bytes(v.data(), v.size(), bl); } inline void decode(std::vector& v, bufferlist::const_iterator& p) { - uint32_t len; - - decode(len, p); + const auto len = encoding_detail::decode_count(p); v.resize(len); p.copy(len, (char *)v.data()); } @@ -271,51 +314,51 @@ inline void decode(std::vector& v, bufferlist::const_iterator& p) // buffers // bufferptr (encapsulated) -inline void encode(const buffer::ptr& bp, bufferlist& bl) +inline void encode(const buffer::ptr& bp, bufferlist& bl) { - __u32 len = bp.length(); - encode(len, bl); + const auto len = bp.length(); + encoding_detail::encode_count(len, bl); if (len) bl.append(bp); } inline void decode(buffer::ptr& bp, bufferlist::const_iterator& p) { - __u32 len; - decode(len, p); + const auto len = encoding_detail::decode_count(p); bufferlist s; p.copy(len, s); - if (len) { - if (s.get_num_buffers() == 1) - bp = s.front(); - else - bp = buffer::copy(s.c_str(), s.length()); + if (!len) { + return; + } + + if (1 == s.get_num_buffers()) { + bp = s.front(); + return; } + + bp = buffer::copy(s.c_str(), s.length()); } // bufferlist (encapsulated) -inline void encode(const bufferlist& s, bufferlist& bl) +inline void encode(const bufferlist& s, bufferlist& bl) { - __u32 len = s.length(); - encode(len, bl); + encoding_detail::encode_count(s.length(), bl); bl.append(s); } -inline void encode_destructively(bufferlist& s, bufferlist& bl) +inline void encode_destructively(bufferlist& s, bufferlist& bl) { - __u32 len = s.length(); - encode(len, bl); + encoding_detail::encode_count(s.length(), bl); bl.claim_append(s); } inline void decode(bufferlist& s, bufferlist::const_iterator& p) { - __u32 len; - decode(len, p); + const auto len = encoding_detail::decode_count(p); s.clear(); p.copy(len, s); } -inline void encode_nohead(const bufferlist& s, bufferlist& bl) +inline void encode_nohead(const bufferlist& s, bufferlist& bl) { bl.append(s); } @@ -409,1020 +452,985 @@ void round_trip_decode(std::chrono::time_point& t, // ----------------------------- // STL container types +namespace encoding_detail { + +template +concept needs_legacy_encoding = !TraitsT::supported; + +template +concept pair_needs_legacy_encoding = + needs_legacy_encoding || + needs_legacy_encoding; + +template +concept map_decodes_by_emplace = + std::move_constructible && + std::move_constructible; + +template +void encode_optional(const OptionalT& p, bufferlist& bl); + template -inline void encode(const boost::optional &p, bufferlist &bl); -template -inline void decode(boost::optional &p, bufferlist::const_iterator &bp); -template -inline void encode(const std::optional &p, bufferlist &bl); -template -inline void decode(std::optional &p, bufferlist::const_iterator &bp); -template -inline void encode(const boost::tuple &t, bufferlist& bl); -template -inline void decode(boost::tuple &t, bufferlist::const_iterator &bp); -template, typename b_traits=denc_traits> -inline std::enable_if_t -encode(const std::pair &p, bufferlist &bl, uint64_t features); -template, typename b_traits=denc_traits> -inline std::enable_if_t -encode(const std::pair &p, bufferlist &bl); -template, typename b_traits=denc_traits> -inline std::enable_if_t -decode(std::pair &pa, bufferlist::const_iterator &p); -template> -inline std::enable_if_t -encode(const std::list& ls, bufferlist& bl); -template> -inline std::enable_if_t -encode(const std::list& ls, bufferlist& bl, uint64_t features); -template> -inline std::enable_if_t -decode(std::list& ls, bufferlist::const_iterator& p); -template -inline void encode(const std::list, Alloc>& ls, - bufferlist& bl); -template -inline void encode(const std::list, Alloc>& ls, - bufferlist& bl, uint64_t features); -template -inline void decode(std::list, Alloc>& ls, - bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode(const std::set& s, bufferlist& bl); -template> -inline std::enable_if_t -decode(std::set& s, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode_nohead(const std::set& s, bufferlist& bl); -template> -inline std::enable_if_t -decode_nohead(unsigned len, std::set& s, bufferlist::iterator& p); -template> -inline std::enable_if_t -encode(const boost::container::flat_set& s, bufferlist& bl); -template> -inline std::enable_if_t -decode(boost::container::flat_set& s, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode_nohead(const boost::container::flat_set& s, - bufferlist& bl); -template> -inline std::enable_if_t -decode_nohead(unsigned len, boost::container::flat_set& s, - bufferlist::iterator& p); -template -inline void encode(const std::multiset& s, bufferlist& bl); -template -inline void decode(std::multiset& s, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode(const std::vector& v, bufferlist& bl, uint64_t features); -template> -inline std::enable_if_t -encode(const std::vector& v, bufferlist& bl); -template> -inline std::enable_if_t -decode(std::vector& v, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode_nohead(const std::vector& v, bufferlist& bl); -template> -inline std::enable_if_t -decode_nohead(unsigned len, std::vector& v, bufferlist::const_iterator& p); -template -inline void encode(const std::vector,Alloc>& v, - bufferlist& bl, - uint64_t features); -template -inline void encode(const std::vector,Alloc>& v, - bufferlist& bl); -template -inline void decode(std::vector,Alloc>& v, - bufferlist::const_iterator& p); -// small_vector -template> -inline std::enable_if_t -encode(const boost::container::small_vector& v, bufferlist& bl, uint64_t features); -template> -inline std::enable_if_t -encode(const boost::container::small_vector& v, bufferlist& bl); -template> -inline std::enable_if_t -decode(boost::container::small_vector& v, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode_nohead(const boost::container::small_vector& v, bufferlist& bl); -template> -inline std::enable_if_t -decode_nohead(unsigned len, boost::container::small_vector& v, bufferlist::const_iterator& p); -// std::map -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode(const std::map& m, bufferlist& bl); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode(const std::map& m, bufferlist& bl, uint64_t features); -template, typename u_traits=denc_traits> -inline std::enable_if_t -decode(std::map& m, bufferlist::const_iterator& p); -template -inline void decode_noclear(std::map& m, bufferlist::const_iterator& p); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode_nohead(const std::map& m, bufferlist& bl); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode_nohead(const std::map& m, bufferlist& bl, uint64_t features); -template, typename u_traits=denc_traits> -inline std::enable_if_t -decode_nohead(unsigned n, std::map& m, bufferlist::const_iterator& p); -template, typename u_traits=denc_traits> - inline std::enable_if_t -encode(const boost::container::flat_map& m, bufferlist& bl); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode(const boost::container::flat_map& m, bufferlist& bl, - uint64_t features); -template, typename u_traits=denc_traits> -inline std::enable_if_t -decode(boost::container::flat_map& m, bufferlist::const_iterator& p); -template -inline void decode_noclear(boost::container::flat_map& m, - bufferlist::const_iterator& p); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode_nohead(const boost::container::flat_map& m, - bufferlist& bl); -template, typename u_traits=denc_traits> -inline std::enable_if_t -encode_nohead(const boost::container::flat_map& m, - bufferlist& bl, uint64_t features); -template, typename u_traits=denc_traits> -inline std::enable_if_t -decode_nohead(unsigned n, boost::container::flat_map& m, - bufferlist::const_iterator& p); -template -inline void encode(const std::multimap& m, bufferlist& bl); -template -inline void decode(std::multimap& m, bufferlist::const_iterator& p); -template -inline void encode(const std::unordered_map& m, bufferlist& bl, - uint64_t features); -template -inline void encode(const std::unordered_map& m, bufferlist& bl); -template -inline void decode(std::unordered_map& m, bufferlist::const_iterator& p); -template -inline void encode(const std::unordered_set& m, bufferlist& bl); -template -inline void decode(std::unordered_set& m, bufferlist::const_iterator& p); -template -inline void encode(const std::deque& ls, bufferlist& bl, uint64_t features); -template -inline void encode(const std::deque& ls, bufferlist& bl); -template -inline void decode(std::deque& ls, bufferlist::const_iterator& p); -template> -inline std::enable_if_t -encode(const std::array& v, bufferlist& bl, uint64_t features); -template> -inline std::enable_if_t -encode(const std::array& v, bufferlist& bl); -template> -inline std::enable_if_t -decode(std::array& v, bufferlist::const_iterator& p); +std::optional decode_optional(bufferlist::const_iterator& p); -// full bl decoder -template -inline void decode(T &o, const bufferlist& bl) -{ - auto p = bl.begin(); - decode(o, p); - ceph_assert(p.end()); -} +template +void for_each_count(unsigned n, FnT&& fn); + +template +void encode_range_nohead(const RangeT& r, bufferlist& bl); + +template +void encode_range_nohead(const RangeT& r, bufferlist& bl, uint64_t features); + +template +void decode_range_nohead(RangeT& r, IteratorT& p); + +template +void encode_range(const RangeT& r, bufferlist& bl); + +template +void encode_range(const RangeT& r, bufferlist& bl, uint64_t features); + +template +void decode_by_resize_nohead(unsigned len, ContainerT& c, + IteratorT& p); + +template +void decode_by_resize(ContainerT& c, bufferlist::const_iterator& p); + +template +void decode_by_emplace_back(unsigned len, ContainerT& c, + IteratorT& p); + +template +void decode_by_emplace_back(ContainerT& c, bufferlist::const_iterator& p); + +template +void clear_and_reserve(ContainerT& c, size_t n); + +template +void decode_by_insert(unsigned len, ContainerT& c, + IteratorT& p); + +template +void decode_by_insert(ContainerT& c, bufferlist::const_iterator& p); + +template +void encode_shared_ptr_range(const RangeT& r, bufferlist& bl); + +template +void encode_shared_ptr_range(const RangeT& r, bufferlist& bl, + uint64_t features); + +template +void append_cached_default_encoding(std::optional& bytes, + bufferlist& bl, + EncodeFnT&& encode_value); + +template +void encode_shared_ptr_range_with(const RangeT& r, bufferlist& bl, + EncodeFnT&& encode_value); + +template +void decode_shared_ptr_sequence(ContainerT& c, + bufferlist::const_iterator& p); + +template +void encode_pair_members(const PairT& item, bufferlist& bl); + +template +void encode_pair_members(const PairT& item, bufferlist& bl, + uint64_t features); + +template +void decode_pair_members(PairT& item, bufferlist::const_iterator& p); + +template +void encode_pair_range_nohead(const MapT& m, bufferlist& bl); + +template +void encode_pair_range_nohead(const MapT& m, bufferlist& bl, + uint64_t features); + +template +void encode_pair_range(const MapT& m, bufferlist& bl); + +template +void encode_pair_range(const MapT& m, bufferlist& bl, uint64_t features); + +template +void decode_map_entries_by_subscript(unsigned n, MapT& m, + IteratorT& p); + +template +void decode_map_entries_by_emplace(unsigned n, MapT& m, + IteratorT& p); + +template +void decode_map_entries(unsigned n, MapT& m, IteratorT& p); + +template +void decode_map_by_subscript(unsigned n, MapT& m, + bufferlist::const_iterator& p); + +template +void decode_map_by_subscript(MapT& m, bufferlist::const_iterator& p); + +template +void decode_map(unsigned n, MapT& m, bufferlist::const_iterator& p); + +template +void decode_map(MapT& m, bufferlist::const_iterator& p); + +} // namespace encoding_detail // boost optional template -inline void encode(const boost::optional &p, bufferlist &bl) +inline void encode(const boost::optional& p, bufferlist& bl) { - __u8 present = static_cast(p); - encode(present, bl); - if (p) - encode(p.get(), bl); + encoding_detail::encode_optional(p, bl); } -#pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wuninitialized" template -inline void decode(boost::optional &p, bufferlist::const_iterator &bp) +inline void decode(boost::optional& p, bufferlist::const_iterator& bp) { - __u8 present; - decode(present, bp); - if (present) { - p = T{}; - decode(p.get(), bp); - } else { - p = boost::none; + auto decoded = encoding_detail::decode_optional(bp); + if (decoded) { + p = std::move(*decoded); + return; } + + p.reset(); } -#pragma GCC diagnostic pop -#pragma GCC diagnostic warning "-Wpragmas" // std optional template -inline void encode(const std::optional &p, bufferlist &bl) +inline void encode(const std::optional& p, bufferlist& bl) { - __u8 present = static_cast(p); - encode(present, bl); - if (p) - encode(*p, bl); + encoding_detail::encode_optional(p, bl); } -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuninitialized" template -inline void decode(std::optional &p, bufferlist::const_iterator &bp) +inline void decode(std::optional& p, bufferlist::const_iterator& bp) { - __u8 present; - decode(present, bp); - if (present) { - p = T{}; - decode(*p, bp); - } else { - p = std::nullopt; - } + p = encoding_detail::decode_optional(bp); } +#pragma GCC diagnostic pop // std::tuple template -inline void encode(const std::tuple &t, bufferlist& bl) +inline void encode(const std::tuple& t, bufferlist& bl) { ceph::for_each(t, [&bl](const auto& e) { - encode(e, bl); - }); + encode(e, bl); + }); } template -inline void decode(std::tuple &t, bufferlist::const_iterator &bp) +inline void decode(std::tuple& t, bufferlist::const_iterator& bp) { ceph::for_each(t, [&bp](auto& e) { - decode(e, bp); - }); + decode(e, bp); + }); } -//triple boost::tuple +// triple boost::tuple template -inline void encode(const boost::tuple &t, bufferlist& bl) +inline void encode(const boost::tuple& t, bufferlist& bl) { encode(boost::get<0>(t), bl); encode(boost::get<1>(t), bl); encode(boost::get<2>(t), bl); } template -inline void decode(boost::tuple &t, bufferlist::const_iterator &bp) +inline void decode(boost::tuple& t, bufferlist::const_iterator& bp) { decode(boost::get<0>(t), bp); decode(boost::get<1>(t), bp); decode(boost::get<2>(t), bp); } -// std::pair +// std::pair template -inline std::enable_if_t - encode(const std::pair &p, bufferlist &bl, uint64_t features) + typename a_traits = denc_traits, typename b_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const std::pair& p, bufferlist& bl, uint64_t features) { - encode(p.first, bl, features); - encode(p.second, bl, features); + encoding_detail::encode_pair_members(p, bl, features); } template -inline std::enable_if_t - encode(const std::pair &p, bufferlist &bl) + typename a_traits = denc_traits, typename b_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const std::pair& p, bufferlist& bl) { - encode(p.first, bl); - encode(p.second, bl); + encoding_detail::encode_pair_members(p, bl); } -template -inline std::enable_if_t - decode(std::pair &pa, bufferlist::const_iterator &p) +template, typename b_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void decode(std::pair& pa, bufferlist::const_iterator& p) { - decode(pa.first, p); - decode(pa.second, p); + encoding_detail::decode_pair_members(pa, p); } // std::list -template -inline std::enable_if_t - encode(const std::list& ls, bufferlist& bl) -{ - __u32 n = (__u32)(ls.size()); // c++11 std::list::size() is O(1) - encode(n, bl); - for (auto p = ls.begin(); p != ls.end(); ++p) - encode(*p, bl); -} -template -inline std::enable_if_t - encode(const std::list& ls, bufferlist& bl, uint64_t features) -{ - using counter_encode_t = ceph_le32; - unsigned n = 0; - auto filler = bl.append_hole(sizeof(counter_encode_t)); - for (const auto& item : ls) { - // we count on our own because of buggy std::list::size() implementation - // which doesn't follow the O(1) complexity constraint C++11 has brought. - ++n; - encode(item, bl, features); - } - counter_encode_t en; - en = n; - filler.copy_in(sizeof(en), reinterpret_cast(&en)); +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::list& ls, bufferlist& bl) +{ + encoding_detail::encode_range(ls, bl); +} +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::list& ls, bufferlist& bl, uint64_t features) +{ + encoding_detail::encode_range(ls, bl, features); } -template -inline std::enable_if_t - decode(std::list& ls, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(std::list& ls, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - ls.clear(); - while (n--) { - ls.emplace_back(); - decode(ls.back(), p); - } + encoding_detail::decode_by_emplace_back(ls, p); } // std::list> template inline void encode(const std::list, Alloc>& ls, - bufferlist& bl) + bufferlist& bl) { - __u32 n = (__u32)(ls.size()); // c++11 std::list::size() is O(1) - encode(n, bl); - for (const auto& ref : ls) { - encode(*ref, bl); - } + encoding_detail::encode_shared_ptr_range(ls, bl); } template inline void encode(const std::list, Alloc>& ls, - bufferlist& bl, uint64_t features) + bufferlist& bl, uint64_t features) { - __u32 n = (__u32)(ls.size()); // c++11 std::list::size() is O(1) - encode(n, bl); - for (const auto& ref : ls) { - encode(*ref, bl, features); - } + encoding_detail::encode_shared_ptr_range(ls, bl, features); } template inline void decode(std::list, Alloc>& ls, - bufferlist::const_iterator& p) + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - ls.clear(); - while (n--) { - auto ref = std::make_shared(); - decode(*ref, p); - ls.emplace_back(std::move(ref)); - } + encoding_detail::decode_shared_ptr_sequence(ls, p); } // std::set -template -inline std::enable_if_t - encode(const std::set& s, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::set& s, bufferlist& bl) { - __u32 n = (__u32)(s.size()); - encode(n, bl); - for (auto p = s.begin(); p != s.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range(s, bl); } -template -inline std::enable_if_t - decode(std::set& s, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(std::set& s, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - s.clear(); - while (n--) { - T v; - decode(v, p); - s.insert(v); - } + encoding_detail::decode_by_insert(s, p); } -template -inline typename std::enable_if::type - encode_nohead(const std::set& s, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode_nohead(const std::set& s, bufferlist& bl) { - for (auto p = s.begin(); p != s.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range_nohead(s, bl); } -template -inline std::enable_if_t - decode_nohead(unsigned len, std::set& s, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode_nohead(unsigned len, std::set& s, + bufferlist::const_iterator& p) { - for (unsigned i=0; i -template -inline std::enable_if_t -encode(const boost::container::flat_set& s, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const boost::container::flat_set& s, + bufferlist& bl) { - __u32 n = (__u32)(s.size()); - encode(n, bl); - for (const auto& e : s) - encode(e, bl); + encoding_detail::encode_range(s, bl); } -template -inline std::enable_if_t -decode(boost::container::flat_set& s, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(boost::container::flat_set& s, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - s.clear(); - s.reserve(n); - while (n--) { - T v; - decode(v, p); - s.insert(v); - } + encoding_detail::decode_by_insert(s, p); } -template -inline std::enable_if_t -encode_nohead(const boost::container::flat_set& s, - bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode_nohead(const boost::container::flat_set& s, + bufferlist& bl) { - for (const auto& e : s) - encode(e, bl); + encoding_detail::encode_range_nohead(s, bl); } -template -inline std::enable_if_t -decode_nohead(unsigned len, boost::container::flat_set& s, - bufferlist::iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode_nohead(unsigned len, + boost::container::flat_set& s, + bufferlist::const_iterator& p) { - s.reserve(len); - for (unsigned i=0; i -inline void encode(const std::multiset& s, bufferlist& bl) +inline void encode(const std::multiset& s, bufferlist& bl) { - __u32 n = (__u32)(s.size()); - encode(n, bl); - for (auto p = s.begin(); p != s.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range(s, bl); } template -inline void decode(std::multiset& s, bufferlist::const_iterator& p) +inline void decode(std::multiset& s, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - s.clear(); - while (n--) { - T v; - decode(v, p); - s.insert(v); - } + encoding_detail::decode_by_insert(s, p); } -template -inline std::enable_if_t - encode(const std::vector& v, bufferlist& bl, uint64_t features) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::vector& v, bufferlist& bl, uint64_t features) { - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (auto p = v.begin(); p != v.end(); ++p) - encode(*p, bl, features); + encoding_detail::encode_range(v, bl, features); } -template -inline std::enable_if_t - encode(const std::vector& v, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::vector& v, bufferlist& bl) { - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (auto p = v.begin(); p != v.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range(v, bl); } -template -inline std::enable_if_t - decode(std::vector& v, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(std::vector& v, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - v.resize(n); - for (__u32 i=0; i -inline std::enable_if_t - encode_nohead(const std::vector& v, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode_nohead(const std::vector& v, bufferlist& bl) { - for (auto p = v.begin(); p != v.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range_nohead(v, bl); } -template -inline std::enable_if_t - decode_nohead(unsigned len, std::vector& v, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode_nohead(unsigned len, std::vector& v, + bufferlist::const_iterator& p) { - v.resize(len); - for (__u32 i=0; i -inline std::enable_if_t - encode(const boost::container::small_vector& v, bufferlist& bl, uint64_t features) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const boost::container::small_vector& v, + bufferlist& bl, uint64_t features) { - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (const auto& i : v) - encode(i, bl, features); + encoding_detail::encode_range(v, bl, features); } -template -inline std::enable_if_t - encode(const boost::container::small_vector& v, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const boost::container::small_vector& v, + bufferlist& bl) { - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (const auto& i : v) - encode(i, bl); + encoding_detail::encode_range(v, bl); } -template -inline std::enable_if_t - decode(boost::container::small_vector& v, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(boost::container::small_vector& v, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - v.resize(n); - for (auto& i : v) - decode(i, p); + encoding_detail::decode_by_resize(v, p); } -template -inline std::enable_if_t - encode_nohead(const boost::container::small_vector& v, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode_nohead(const boost::container::small_vector& v, + bufferlist& bl) { - for (const auto& i : v) - encode(i, bl); + encoding_detail::encode_range_nohead(v, bl); } -template -inline std::enable_if_t - decode_nohead(unsigned len, boost::container::small_vector& v, bufferlist::const_iterator& p) +template> +requires encoding_detail::needs_legacy_encoding +inline void decode_nohead(unsigned len, + boost::container::small_vector& v, + bufferlist::const_iterator& p) { - v.resize(len); - for (auto& i : v) - decode(i, p); + encoding_detail::decode_by_resize_nohead(len, v, p); } // vector (shared_ptr) -template -inline void encode(const std::vector,Alloc>& v, - bufferlist& bl, - uint64_t features) -{ - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (const auto& ref : v) { - if (ref) - encode(*ref, bl, features); - else - encode(T(), bl, features); - } +template +inline void encode(const std::vector, Alloc>& v, + bufferlist& bl, + uint64_t features) +{ + encoding_detail::encode_shared_ptr_range(v, bl, features); } template -inline void encode(const std::vector,Alloc>& v, - bufferlist& bl) -{ - __u32 n = (__u32)(v.size()); - encode(n, bl); - for (const auto& ref : v) { - if (ref) - encode(*ref, bl); - else - encode(T(), bl); - } +inline void encode(const std::vector, Alloc>& v, + bufferlist& bl) +{ + encoding_detail::encode_shared_ptr_range(v, bl); } template -inline void decode(std::vector,Alloc>& v, - bufferlist::const_iterator& p) +inline void decode(std::vector, Alloc>& v, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - v.clear(); - v.reserve(n); - while (n--) { - auto ref = std::make_shared(); - decode(*ref, p); - v.emplace_back(std::move(ref)); - } + encoding_detail::decode_shared_ptr_sequence(v, p); } // map template -inline std::enable_if_t - encode(const std::map& m, bufferlist& bl) -{ - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } -} -template -inline std::enable_if_t - encode(const std::map& m, bufferlist& bl, uint64_t features) -{ - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl, features); - encode(p->second, bl, features); - } + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const std::map& m, bufferlist& bl) +{ + encoding_detail::encode_pair_range(m, bl); } template -inline std::enable_if_t - decode(std::map& m, bufferlist::const_iterator& p) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const std::map& m, bufferlist& bl, + uint64_t features) { - __u32 n; - decode(n, p); - m.clear(); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } + encoding_detail::encode_pair_range(m, bl, features); } -template -inline std::enable_if_t -decode(std::map& m, bufferlist::const_iterator& p) +template, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void decode(std::map& m, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - m.clear(); - while (n--) { - T k; - U v; - decode(k, p); - decode(v, p); - m.emplace(std::move(k), std::move(v)); - } + encoding_detail::decode_map(m, p); } + +// Compatibility-only: no production callers were found in-tree. Do not add new +// callers unless preserving the destination's current entries is necessary. +// Actual deprecation is a separate public-header compatibility decision. +// Duplicate decoded keys intentionally preserve the existing mapped value for +// std::map. template -inline void decode_noclear(std::map& m, bufferlist::const_iterator& p) +[[maybe_unused]] inline void decode_noclear(std::map& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } -} -template -inline void decode_noclear(std::map& m, bufferlist::const_iterator& p) -{ - __u32 n; - decode(n, p); - while (n--) { - T k; - U v; - decode(k, p); - decode(v, p); - m.emplace(std::move(k), std::move(v)); - } + encoding_detail::decode_map_entries(encoding_detail::decode_count(p), m, p); } template -inline std::enable_if_t - encode_nohead(const std::map& m, bufferlist& bl) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode_nohead(const std::map& m, bufferlist& bl) { - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } + encoding_detail::encode_pair_range_nohead(m, bl); } template -inline std::enable_if_t - encode_nohead(const std::map& m, bufferlist& bl, uint64_t features) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode_nohead(const std::map& m, + bufferlist& bl, + uint64_t features) { - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl, features); - encode(p->second, bl, features); - } + encoding_detail::encode_pair_range_nohead(m, bl, features); } template -inline std::enable_if_t - decode_nohead(unsigned n, std::map& m, bufferlist::const_iterator& p) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void decode_nohead(unsigned n, std::map& m, + bufferlist::const_iterator& p) { - m.clear(); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } -} - -template -inline std::enable_if_t -decode_nohead(unsigned n, std::map& m, bufferlist::const_iterator& p) -{ - m.clear(); - while (n--) { - T k; - U v; - decode(k, p); - decode(v, p); - m.emplace(std::move(k), std::move(v)); - } + encoding_detail::decode_map(n, m, p); } // boost::container::flat-map template - inline std::enable_if_t - encode(const boost::container::flat_map& m, bufferlist& bl) -{ - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (typename boost::container::flat_map::const_iterator p - = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const boost::container::flat_map& m, + bufferlist& bl) +{ + encoding_detail::encode_pair_range(m, bl); } template - inline std::enable_if_t - encode(const boost::container::flat_map& m, bufferlist& bl, - uint64_t features) -{ - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl, features); - encode(p->second, bl, features); - } + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode(const boost::container::flat_map& m, + bufferlist& bl, uint64_t features) +{ + encoding_detail::encode_pair_range(m, bl, features); } template - inline std::enable_if_t - decode(boost::container::flat_map& m, bufferlist::const_iterator& p) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void decode(boost::container::flat_map& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - m.clear(); - m.reserve(n); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } + encoding_detail::decode_map_by_subscript(m, p); } + +// Compatibility-only: no production callers were found in-tree. Do not add new +// callers unless preserving the destination's current entries is necessary. +// Actual deprecation is a separate public-header compatibility decision. +// Duplicate decoded keys intentionally replace the existing mapped value for +// boost::container::flat_map. template -inline void decode_noclear(boost::container::flat_map& m, - bufferlist::const_iterator& p) +[[maybe_unused]] inline void decode_noclear( + boost::container::flat_map& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); + const auto n = encoding_detail::decode_count(p); m.reserve(m.size() + n); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } + encoding_detail::decode_map_entries_by_subscript(n, m, p); } template - inline std::enable_if_t - encode_nohead(const boost::container::flat_map& m, - bufferlist& bl) -{ - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode_nohead(const boost::container::flat_map& m, + bufferlist& bl) +{ + encoding_detail::encode_pair_range_nohead(m, bl); } template - inline std::enable_if_t - encode_nohead(const boost::container::flat_map& m, - bufferlist& bl, uint64_t features) -{ - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl, features); - encode(p->second, bl, features); - } + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void encode_nohead(const boost::container::flat_map& m, + bufferlist& bl, uint64_t features) +{ + encoding_detail::encode_pair_range_nohead(m, bl, features); } template -inline std::enable_if_t - decode_nohead(unsigned n, boost::container::flat_map& m, - bufferlist::const_iterator& p) + typename t_traits = denc_traits, typename u_traits = denc_traits> +requires encoding_detail::pair_needs_legacy_encoding +inline void decode_nohead(unsigned n, + boost::container::flat_map& m, + bufferlist::const_iterator& p) { - m.clear(); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } + encoding_detail::decode_map_by_subscript(n, m, p); } // multimap template -inline void encode(const std::multimap& m, bufferlist& bl) +inline void encode(const std::multimap& m, bufferlist& bl) { - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } + encoding_detail::encode_pair_range(m, bl); } template -inline void decode(std::multimap& m, bufferlist::const_iterator& p) +inline void decode(std::multimap& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); m.clear(); - while (n--) { - typename std::pair tu = std::pair(); + + encoding_detail::for_each_count(encoding_detail::decode_count(p), [&m, &p] { + auto tu = std::pair {}; decode(tu.first, p); - typename std::multimap::iterator it = m.insert(tu); + auto it = m.insert(std::move(tu)); decode(it->second, p); - } + }); } // std::unordered_map template -inline void encode(const std::unordered_map& m, bufferlist& bl, - uint64_t features) -{ - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl, features); - encode(p->second, bl, features); - } -} -template -inline void encode(const std::unordered_map& m, bufferlist& bl) +inline void encode(const std::unordered_map& m, + bufferlist& bl, + uint64_t features) { - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) { - encode(p->first, bl); - encode(p->second, bl); - } + encoding_detail::encode_pair_range(m, bl, features); } template -inline void decode(std::unordered_map& m, bufferlist::const_iterator& p) +inline void encode(const std::unordered_map& m, + bufferlist& bl) { - __u32 n; - decode(n, p); - m.clear(); - while (n--) { - T k; - decode(k, p); - decode(m[k], p); - } + encoding_detail::encode_pair_range(m, bl); } - -template -inline void decode(std::unordered_map& m, bufferlist::const_iterator& p) +template +inline void decode(std::unordered_map& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - m.clear(); - while (n--) { - T k; - U v; - decode(k, p); - decode(v, p); - m.emplace(std::move(k), std::move(v)); - } + encoding_detail::decode_map(m, p); } // std::unordered_set template -inline void encode(const std::unordered_set& m, bufferlist& bl) +inline void encode(const std::unordered_set& m, + bufferlist& bl) { - __u32 n = (__u32)(m.size()); - encode(n, bl); - for (auto p = m.begin(); p != m.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range(m, bl); } template -inline void decode(std::unordered_set& m, bufferlist::const_iterator& p) +inline void decode(std::unordered_set& m, + bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - m.clear(); - while (n--) { - T k; - decode(k, p); - m.insert(k); - } + encoding_detail::decode_by_insert(m, p); } // deque template -inline void encode(const std::deque& ls, bufferlist& bl, uint64_t features) +inline void encode(const std::deque& ls, bufferlist& bl, + uint64_t features) { - __u32 n = ls.size(); - encode(n, bl); - for (auto p = ls.begin(); p != ls.end(); ++p) - encode(*p, bl, features); + encoding_detail::encode_range(ls, bl, features); } template -inline void encode(const std::deque& ls, bufferlist& bl) +inline void encode(const std::deque& ls, bufferlist& bl) { - __u32 n = ls.size(); - encode(n, bl); - for (auto p = ls.begin(); p != ls.end(); ++p) - encode(*p, bl); + encoding_detail::encode_range(ls, bl); } template -inline void decode(std::deque& ls, bufferlist::const_iterator& p) +inline void decode(std::deque& ls, bufferlist::const_iterator& p) { - __u32 n; - decode(n, p); - ls.clear(); - while (n--) { - ls.emplace_back(); - decode(ls.back(), p); - } + encoding_detail::decode_by_emplace_back(ls, p); } // std::array -template -inline std::enable_if_t -encode(const std::array& v, bufferlist& bl, uint64_t features) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::array& v, bufferlist& bl, uint64_t features) { - for (const auto& e : v) - encode(e, bl, features); + encoding_detail::encode_range_nohead(v, bl, features); } -template -inline std::enable_if_t -encode(const std::array& v, bufferlist& bl) +template> +requires encoding_detail::needs_legacy_encoding +inline void encode(const std::array& v, bufferlist& bl) { - for (const auto& e : v) - encode(e, bl); + encoding_detail::encode_range_nohead(v, bl); +} +template> +requires encoding_detail::needs_legacy_encoding +inline void decode(std::array& v, bufferlist::const_iterator& p) +{ + encoding_detail::decode_range_nohead(v, p); +} + +namespace encoding_detail { + +template +void encode_optional(const OptionalT& p, bufferlist& bl) +{ + __u8 present = static_cast(p); + encode(present, bl); + if (!p) { + return; + } + + encode(*p, bl); +} + +template +std::optional decode_optional(bufferlist::const_iterator& p) +{ + __u8 present; + decode(present, p); + if (!present) { + return std::nullopt; + } + + T value{}; + decode(value, p); + return value; +} + +template +void for_each_count(unsigned n, FnT&& fn) +{ + std::ranges::for_each(std::views::iota(0u, n), [&fn](auto) { + std::invoke(fn); + }); +} + +template +void encode_range_nohead(const RangeT& r, bufferlist& bl) +{ + std::ranges::for_each(r, [&bl](const auto& item) { + encode(item, bl); + }); +} + +template +void encode_range_nohead(const RangeT& r, bufferlist& bl, uint64_t features) +{ + std::ranges::for_each(r, [&bl, features](const auto& item) { + encode(item, bl, features); + }); +} + +template +void decode_range_nohead(RangeT& r, IteratorT& p) +{ + std::ranges::for_each(r, [&p](auto& item) { + decode(item, p); + }); +} + +template +void encode_range(const RangeT& r, bufferlist& bl) +{ + encode_count(std::ranges::size(r), bl); + encode_range_nohead(r, bl); +} + +template +void encode_range(const RangeT& r, bufferlist& bl, uint64_t features) +{ + encode_count(std::ranges::size(r), bl); + encode_range_nohead(r, bl, features); +} + +template +void reserve_if_possible(ContainerT& c, size_t n) +{ + if constexpr (requires { c.reserve(n); }) { + c.reserve(n); + } +} + +template +void clear_and_reserve(ContainerT& c, size_t n) +{ + c.clear(); + reserve_if_possible(c, n); +} + +template +void decode_by_resize_nohead(unsigned len, ContainerT& c, + IteratorT& p) +{ + c.resize(len); + decode_range_nohead(c, p); +} + +template +void decode_by_resize(ContainerT& c, bufferlist::const_iterator& p) +{ + decode_by_resize_nohead(decode_count(p), c, p); +} + +template +void decode_by_emplace_back(unsigned len, ContainerT& c, + IteratorT& p) +{ + clear_and_reserve(c, len); + + for_each_count(len, [&c, &p] { + c.emplace_back(); + decode(c.back(), p); + }); +} + +template +void decode_by_emplace_back(ContainerT& c, bufferlist::const_iterator& p) +{ + decode_by_emplace_back(decode_count(p), c, p); +} + +template +void decode_by_insert(unsigned len, ContainerT& c, + IteratorT& p) +{ + clear_and_reserve(c, len); + + for_each_count(len, [&c, &p] { + typename ContainerT::value_type v; + decode(v, p); + c.insert(std::move(v)); + }); +} + +template +void decode_by_insert(ContainerT& c, bufferlist::const_iterator& p) +{ + decode_by_insert(decode_count(p), c, p); +} + +template +void encode_shared_ptr_range(const RangeT& r, bufferlist& bl) +{ + encode_shared_ptr_range_with(r, bl, [](const auto& value, auto& out) { + encode(value, out); + }); +} + +template +void encode_shared_ptr_range(const RangeT& r, bufferlist& bl, + uint64_t features) +{ + encode_shared_ptr_range_with(r, bl, [features](const auto& value, auto& out) { + encode(value, out, features); + }); +} + +template +void append_cached_default_encoding(std::optional& bytes, + bufferlist& bl, + EncodeFnT&& encode_value) +{ + if (!bytes) { + auto tmp = bufferlist {}; + std::invoke(encode_value, ValueT {}, tmp); + bytes = tmp.to_str(); + } + + bl.append(std::string_view { *bytes }); +} + +template +void encode_shared_ptr_range_with(const RangeT& r, bufferlist& bl, + EncodeFnT&& encode_value) +{ + using value_type = typename std::ranges::range_value_t::element_type; + + encode_count(std::ranges::size(r), bl); + + auto null_bytes = std::optional {}; + + std::ranges::for_each(r, [&bl, &encode_value, &null_bytes](const auto& ref) { + if (ref) { + std::invoke(encode_value, *ref, bl); + return; + } + + append_cached_default_encoding(null_bytes, bl, encode_value); + }); +} + +template +void decode_shared_ptr_sequence(ContainerT& c, + bufferlist::const_iterator& p) +{ + const auto n = decode_count(p); + clear_and_reserve(c, n); + + for_each_count(n, [&c, &p] { + auto ref = std::make_shared(); + decode(*ref, p); + c.emplace_back(std::move(ref)); + }); +} + +template +void encode_pair_members(const PairT& item, bufferlist& bl) +{ + encode(item.first, bl); + encode(item.second, bl); +} + +template +void encode_pair_members(const PairT& item, bufferlist& bl, + uint64_t features) +{ + encode(item.first, bl, features); + encode(item.second, bl, features); +} + +template +void decode_pair_members(PairT& item, bufferlist::const_iterator& p) +{ + decode(item.first, p); + decode(item.second, p); +} + +template +void encode_pair_range_nohead(const MapT& m, bufferlist& bl) +{ + std::ranges::for_each(m, [&bl](const auto& item) { + encode_pair_members(item, bl); + }); } -template -inline std::enable_if_t -decode(std::array& v, bufferlist::const_iterator& p) + +template +void encode_pair_range_nohead(const MapT& m, bufferlist& bl, + uint64_t features) { - for (auto& e : v) - decode(e, p); + std::ranges::for_each(m, [&bl, features](const auto& item) { + encode_pair_members(item, bl, features); + }); +} + +template +void encode_pair_range(const MapT& m, bufferlist& bl) +{ + encode_count(std::ranges::size(m), bl); + encode_pair_range_nohead(m, bl); +} + +template +void encode_pair_range(const MapT& m, bufferlist& bl, uint64_t features) +{ + encode_count(std::ranges::size(m), bl); + encode_pair_range_nohead(m, bl, features); +} + +template +void decode_map_entries_by_subscript(unsigned n, MapT& m, + IteratorT& p) +{ + for_each_count(n, [&m, &p] { + typename MapT::key_type k; + decode(k, p); + decode(m[std::move(k)], p); + }); +} + +template +void decode_map_entries_by_emplace(unsigned n, MapT& m, + IteratorT& p) +{ + for_each_count(n, [&m, &p] { + typename MapT::key_type k; + typename MapT::mapped_type v; + decode(k, p); + decode(v, p); + m.emplace(std::move(k), std::move(v)); + }); +} + +template +void decode_map_entries(unsigned n, MapT& m, IteratorT& p) +{ + if constexpr (map_decodes_by_emplace) { + decode_map_entries_by_emplace(n, m, p); + return; + } + + decode_map_entries_by_subscript(n, m, p); +} + +template +void decode_map_by_subscript(unsigned n, MapT& m, + bufferlist::const_iterator& p) +{ + clear_and_reserve(m, n); + decode_map_entries_by_subscript(n, m, p); +} + +template +void decode_map_by_subscript(MapT& m, bufferlist::const_iterator& p) +{ + decode_map_by_subscript(decode_count(p), m, p); +} + +template +void decode_map(unsigned n, MapT& m, bufferlist::const_iterator& p) +{ + clear_and_reserve(m, n); + decode_map_entries(n, m, p); +} + +template +void decode_map(MapT& m, bufferlist::const_iterator& p) +{ + decode_map(decode_count(p), m, p); +} + +} // namespace encoding_detail + +// full bl decoder +template +inline void decode(T &o, const bufferlist& bl) +{ + auto p = bl.begin(); + decode(o, p); + ceph_assert(p.end()); } } @@ -1442,8 +1450,8 @@ decode(std::array& v, bufferlist::const_iterator& p) __u8 struct_v = v; \ __u8 struct_compat = compat; \ ceph_le32 struct_len; \ - auto filler = (bl).append_hole(sizeof(struct_v) + \ - sizeof(struct_compat) + sizeof(struct_len)); \ + auto filler = (bl).append_hole( \ + ::ceph::encoding_detail::struct_header_len()); \ const auto starting_bl_len = (bl).length(); \ using ::ceph::encode; \ do { @@ -1456,19 +1464,15 @@ decode(std::array& v, bufferlist::const_iterator& p) */ #define ENCODE_FINISH_NEW_COMPAT(bl, new_struct_compat) \ } while (false); \ - if (new_struct_compat) { \ - struct_compat = new_struct_compat; \ - } \ - struct_len = (bl).length() - starting_bl_len; \ - filler.copy_in(sizeof(struct_v), (char *)&struct_v); \ - filler.copy_in(sizeof(struct_compat), \ - (char *)&struct_compat); \ - filler.copy_in(sizeof(struct_len), (char *)&struct_len); + ::ceph::encoding_detail::finish_encode_struct( \ + filler, struct_v, struct_compat, struct_len, \ + new_struct_compat, (bl), starting_bl_len); #define ENCODE_FINISH(bl) ENCODE_FINISH_NEW_COMPAT(bl, 0) -#define DECODE_ERR_OLDVERSION(func, v, compatv) \ - (std::string(func) + " no longer understands old encoding version " #v " < " + std::to_string(compatv)) +#define DECODE_ERR_OLDVERSION(func, v, compatv) \ + (std::string(func) + " no longer understands old encoding version " #v \ + " < " + std::to_string(compatv)) #define DECODE_ERR_NO_COMPAT(func, code_v, v, compatv) \ ("Decoder at '" + std::string(func) + "' v=" + std::to_string(code_v) + \ @@ -1477,6 +1481,154 @@ decode(std::array& v, bufferlist::const_iterator& p) #define DECODE_ERR_PAST(func) \ (std::string(func) + " decode past end of struct encoding") +namespace ceph::encoding_detail { + +inline constexpr auto struct_header_len() noexcept +{ + return sizeof(__u8) + sizeof(__u8) + sizeof(ceph_le32); +} + +struct struct_header final { + __u8 v = 0; + __u8 compat = 0; + __u32 len = 0; +}; + +template +struct_header read_struct_header(IteratorT& bl) +{ + using ::ceph::decode; + + struct_header header; + decode(header.v, bl); + decode(header.compat, bl); + decode(header.len, bl); + + return header; +} + +inline void check_decode_compat(__u8 code_v, __u8 struct_v, + __u8 struct_compat, + const char *func) +{ + if (struct_compat > code_v) { + throw ::ceph::buffer::malformed_input( + DECODE_ERR_NO_COMPAT(func, code_v, struct_v, struct_compat)); + } +} + +template +unsigned checked_struct_end(IteratorT& bl, __u32 struct_len, + const char *func) +{ + if (struct_len > bl.get_remaining()) { + throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(func)); + } + + return bl.get_off() + struct_len; +} + +template +unsigned decode_struct_start(__u8 code_v, VersionT& struct_v, + __u8& struct_compat, __u32& struct_len, + IteratorT& bl, const char *func) +{ + const auto header = read_struct_header(bl); + struct_v = header.v; + struct_compat = header.compat; + struct_len = header.len; + + check_decode_compat(code_v, struct_v, struct_compat, func); + return checked_struct_end(bl, struct_len, func); +} + +template +unsigned decode_legacy_struct_start(__u8 code_v, VersionT& struct_v, + __u8 compat_v, __u8 len_v, + unsigned skip_v, IteratorT& bl, + const char *func) +{ + using ::ceph::decode; + + decode(struct_v, bl); + + if (compat_v <= struct_v) { + __u8 struct_compat; + decode(struct_compat, bl); + check_decode_compat(code_v, struct_v, struct_compat, func); + } + + if (skip_v && compat_v > struct_v) { + if (skip_v > bl.get_remaining()) { + throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(func)); + } + + bl += skip_v; + } + + if (len_v > struct_v) { + return 0; + } + + __u32 struct_len; + decode(struct_len, bl); + return checked_struct_end(bl, struct_len, func); +} + +template +void finish_encode_struct(FillerT& filler, __u8 struct_v, + __u8& struct_compat, ceph_le32& struct_len, + __u8 new_struct_compat, const bufferlist& bl, + size_t starting_bl_len) +{ + if (new_struct_compat) { + struct_compat = new_struct_compat; + } + + struct_len = static_cast<__u32>(bl.length() - starting_bl_len); + filler.copy_in(sizeof(struct_v), + reinterpret_cast(&struct_v)); + filler.copy_in(sizeof(struct_compat), + reinterpret_cast(&struct_compat)); + filler.copy_in(sizeof(struct_len), + reinterpret_cast(&struct_len)); +} + +template +void finish_decode_struct(IteratorT& bl, unsigned struct_end, + const char *func) +{ + if (!struct_end) { + return; + } + + if (struct_end < bl.get_off()) { + throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(func)); + } + + if (struct_end > bl.get_off()) { + bl += struct_end - bl.get_off(); + } +} + +template +void decode_unknown(PayloadT& payload, IteratorT& bl, const char *func) +{ + const auto header = read_struct_header(bl); + checked_struct_end(bl, header.len, func); + + payload.clear(); + + using ::ceph::encode; + + encode(header.v, payload); + encode(header.compat, payload); + encode(header.len, payload); + bl.copy(header.len, payload); +} + +} // namespace ceph::encoding_detail + /** * check for very old encoding * @@ -1485,8 +1637,8 @@ decode(std::array& v, bufferlist::const_iterator& p) * @param oldestv oldest version of the code we can successfully decode. */ #define DECODE_OLDEST(oldestv) \ - if (struct_v < oldestv) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_OLDVERSION(__PRETTY_FUNCTION__, v, oldestv)); + if (oldestv > struct_v) \ + throw ::ceph::buffer::malformed_input(DECODE_ERR_OLDVERSION(__PRETTY_FUNCTION__, v, oldestv)); /** * start a decoding block @@ -1497,74 +1649,33 @@ decode(std::array& v, bufferlist::const_iterator& p) #define DECODE_START(_v, bl) \ StructVChecker<_v> struct_v; \ __u8 struct_compat; \ - using ::ceph::decode; \ - decode(struct_v.v, bl); \ - decode(struct_compat, bl); \ - if (_v < struct_compat) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_NO_COMPAT(__PRETTY_FUNCTION__, _v, struct_v.v, struct_compat)); \ __u32 struct_len; \ - decode(struct_len, bl); \ - if (struct_len > bl.get_remaining()) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - unsigned struct_end = bl.get_off() + struct_len; \ + using ::ceph::decode; \ + unsigned struct_end = ::ceph::encoding_detail::decode_struct_start( \ + _v, struct_v.v, struct_compat, struct_len, bl, __PRETTY_FUNCTION__); \ do { #define DECODE_START_UNCHECKED(v, bl) \ __u8 struct_v, struct_compat; \ - using ::ceph::decode; \ - decode(struct_v, bl); \ - decode(struct_compat, bl); \ - if (v < struct_compat) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_NO_COMPAT(__PRETTY_FUNCTION__, v, struct_v, struct_compat)); \ __u32 struct_len; \ - decode(struct_len, bl); \ - if (struct_len > bl.get_remaining()) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - unsigned struct_end = bl.get_off() + struct_len; \ + using ::ceph::decode; \ + unsigned struct_end = ::ceph::encoding_detail::decode_struct_start( \ + v, struct_v, struct_compat, struct_len, bl, __PRETTY_FUNCTION__); \ do { #define DECODE_UNKNOWN(payload, bl) \ do { \ - __u8 struct_v, struct_compat; \ - using ::ceph::decode; \ - decode(struct_v, bl); \ - decode(struct_compat, bl); \ - __u32 struct_len; \ - decode(struct_len, bl); \ - if (struct_len > bl.get_remaining()) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - payload.clear(); \ - using ::ceph::encode; \ - encode(struct_v, payload); \ - encode(struct_compat, payload); \ - encode(struct_len, payload); \ - bl.copy(struct_len, payload); \ + ::ceph::encoding_detail::decode_unknown( \ + payload, bl, __PRETTY_FUNCTION__); \ } while (0) -/* BEWARE: any change to this macro MUST be also reflected in the duplicative - * DECODE_START_LEGACY_COMPAT_LEN! */ +/* The checked and unchecked legacy wrappers intentionally differ only in the + * local struct_v type. Keep shared behavior in decode_legacy_struct_start(). */ #define __DECODE_START_LEGACY_COMPAT_LEN(_v, compatv, lenv, skip_v, bl) \ using ::ceph::decode; \ StructVChecker<_v> struct_v; \ - decode(struct_v.v, bl); \ - if (struct_v.v >= compatv) { \ - __u8 struct_compat; \ - decode(struct_compat, bl); \ - if (_v < struct_compat) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_NO_COMPAT(__PRETTY_FUNCTION__, _v, struct_v.v, struct_compat)); \ - } else if (skip_v) { \ - if (bl.get_remaining() < skip_v) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - bl += skip_v; \ - } \ - unsigned struct_end = 0; \ - if (struct_v.v >= lenv) { \ - __u32 struct_len; \ - decode(struct_len, bl); \ - if (struct_len > bl.get_remaining()) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - struct_end = bl.get_off() + struct_len; \ - } \ + unsigned struct_end = ::ceph::encoding_detail::decode_legacy_struct_start( \ + _v, struct_v.v, compatv, lenv, skip_v, bl, __PRETTY_FUNCTION__); \ do { /** @@ -1582,28 +1693,11 @@ decode(std::array& v, bufferlist::const_iterator& p) * @param bl bufferlist::iterator containing the encoded data */ -/* BEWARE: this is duplication of __DECODE_START_LEGACY_COMPAT_LEN which - * MUST be changed altogether. For the rationale behind code duplication, - * please `git blame` and refer to the commit message. */ #define DECODE_START_LEGACY_COMPAT_LEN(v, compatv, lenv, bl) \ using ::ceph::decode; \ __u8 struct_v; \ - decode(struct_v, bl); \ - if (struct_v >= compatv) { \ - __u8 struct_compat; \ - decode(struct_compat, bl); \ - if (v < struct_compat) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_NO_COMPAT( \ - __PRETTY_FUNCTION__, v, struct_v, struct_compat)); \ - } \ - unsigned struct_end = 0; \ - if (struct_v >= lenv) { \ - __u32 struct_len; \ - decode(struct_len, bl); \ - if (struct_len > bl.get_remaining()) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - struct_end = bl.get_off() + struct_len; \ - } \ + unsigned struct_end = ::ceph::encoding_detail::decode_legacy_struct_start( \ + v, struct_v, compatv, lenv, 0, bl, __PRETTY_FUNCTION__); \ do { /** @@ -1636,12 +1730,8 @@ decode(std::array& v, bufferlist::const_iterator& p) */ #define DECODE_FINISH(bl) \ } while (false); \ - if (struct_end) { \ - if (bl.get_off() > struct_end) \ - throw ::ceph::buffer::malformed_input(DECODE_ERR_PAST(__PRETTY_FUNCTION__)); \ - if (bl.get_off() < struct_end) \ - bl += struct_end - bl.get_off(); \ - } + ::ceph::encoding_detail::finish_decode_struct( \ + bl, struct_end, __PRETTY_FUNCTION__); namespace ceph { diff --git a/src/osd/PrimaryLogPG.cc b/src/osd/PrimaryLogPG.cc index 9a479331b24e..ce1a0347bf29 100644 --- a/src/osd/PrimaryLogPG.cc +++ b/src/osd/PrimaryLogPG.cc @@ -105,7 +105,6 @@ using ceph::bufferlist; using ceph::bufferptr; using ceph::Formatter; using ceph::decode; -using ceph::decode_noclear; using ceph::encode; using ceph::encode_destructively; @@ -4650,8 +4649,7 @@ void PrimaryLogPG::do_scan( auto p = m->get_data().cbegin(); // take care to preserve ordering! - bi.clear_objects(); - decode_noclear(bi.objects, p); + decode(bi.objects, p); dout(10) << __func__ << " bi.begin=" << bi.begin << " bi.end=" << bi.end << " bi.objects.size()=" << bi.objects.size() << dendl; diff --git a/src/test/encoding.cc b/src/test/encoding.cc index 3d508909d6d5..cd49ef3e2576 100644 --- a/src/test/encoding.cc +++ b/src/test/encoding.cc @@ -1,22 +1,52 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 International Business Machines Corp. (IBM) + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + #include "include/buffer.h" #include "include/encoding.h" #include #include "gtest/gtest.h" +#include +#include +#include +#include +#include #include // for std::cout +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include using namespace std; template < typename T > static void test_encode_and_decode(const T& src) { - bufferlist bl(1000000); + buffer::list bl(1000000); encode(src, bl); T dst; auto i = bl.cbegin(); decode(dst, i); - ASSERT_EQ(src, dst) << "Encoding roundtrip changed the string: orig=" << src << ", but new=" << dst; + ASSERT_EQ(src, dst) + << "Encoding roundtrip changed the string: orig=" << src + << ", but new=" << dst; } TEST(EncodingRoundTrip, StringSimple) { @@ -37,14 +67,16 @@ TEST(EncodingRoundTrip, StringNewline) { template static void test_encode_and_nohead_nohead(Size len, const T& src) { - bufferlist bl(1000000); + buffer::list bl(1000000); encode(len, bl); encode_nohead(src, bl); T dst; auto i = bl.cbegin(); decode(len, i); decode_nohead(len, dst, i); - ASSERT_EQ(src, dst) << "Encoding roundtrip changed the string: orig=" << src << ", but new=" << dst; + ASSERT_EQ(src, dst) + << "Encoding roundtrip changed the string: orig=" << src + << ", but new=" << dst; } TEST(EncodingRoundTrip, StringNoHead) { @@ -58,7 +90,7 @@ TEST(EncodingRoundTrip, StringNoHead) { } TEST(EncodingRoundTrip, BufferListNoHead) { - bufferlist bl; + buffer::list bl; bl.append("is this a dagger which i see before me?"); auto size = bl.length(); test_encode_and_nohead_nohead(static_cast(size), bl); @@ -166,12 +198,12 @@ public: return data == rhs.data; } - friend void decode(ConstructorCounter &s, bufferlist::const_iterator& p) + friend void decode(ConstructorCounter &s, buffer::list::const_iterator& p) { decode(s.data, p); } - friend void encode(const ConstructorCounter &s, bufferlist& p) + friend void encode(const ConstructorCounter &s, buffer::list& p) { encode(s.data, p); } @@ -226,15 +258,15 @@ TEST(EncodingRoundTrip, MultimapConstructorCounter) { my_val_t::init(); test_encode_and_decode < multimap2_t >(multimap2); - EXPECT_EQ(my_key_t::get_default_ctor(), 5); - EXPECT_EQ(my_key_t::get_one_arg_ctor(), 0); - EXPECT_EQ(my_key_t::get_copy_ctor(), 5); - EXPECT_EQ(my_key_t::get_assigns(), 0); + EXPECT_EQ(5, my_key_t::get_default_ctor()); + EXPECT_EQ(0, my_key_t::get_one_arg_ctor()); + EXPECT_EQ(5, my_key_t::get_copy_ctor()); + EXPECT_EQ(0, my_key_t::get_assigns()); - EXPECT_EQ(my_val_t::get_default_ctor(), 5); - EXPECT_EQ(my_val_t::get_one_arg_ctor(), 0); - EXPECT_EQ(my_val_t::get_copy_ctor(), 5); - EXPECT_EQ(my_val_t::get_assigns(), 0); + EXPECT_EQ(5, my_val_t::get_default_ctor()); + EXPECT_EQ(0, my_val_t::get_one_arg_ctor()); + EXPECT_EQ(5, my_val_t::get_copy_ctor()); + EXPECT_EQ(0, my_val_t::get_assigns()); } namespace ceph { @@ -244,26 +276,26 @@ namespace ceph { // include/denc.h template<> void encode>(const uint64_t&, - bufferlist&, - uint64_t f) { + buffer::list&, + uint64_t features) { static_assert(denc_traits::supported, "should support new encoder"); static_assert(!denc_traits::featured, "should not be featured"); - ASSERT_EQ(0UL, f); + ASSERT_EQ(0UL, features); // make sure the test fails if i get called ASSERT_TRUE(false); } template<> void encode>(const ceph_le64&, - bufferlist&, - uint64_t f) { + buffer::list&, + uint64_t features) { static_assert(denc_traits::supported, "should support new encoder"); static_assert(!denc_traits::featured, "should not be featured"); - ASSERT_EQ(0UL, f); + ASSERT_EQ(0UL, features); // make sure the test fails if i get called ASSERT_TRUE(false); } @@ -342,7 +374,7 @@ TEST(EncodingException, Macros) { try { throw exec; } catch (const exception& e) { - ASSERT_NE(string(e.what()).find(expected_what), string::npos); + ASSERT_NE(string::npos, string(e.what()).find(expected_what)); } } } @@ -378,7 +410,7 @@ TEST(small_encoding, varint) { }; for (unsigned i=0; v[i][1]; ++i) { { - bufferlist bl; + buffer::list bl; { auto app = bl.get_contiguous_appender(16, true); denc_varint(v[i][0], app); @@ -386,14 +418,14 @@ TEST(small_encoding, varint) { cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][1]); + ASSERT_EQ(v[i][1], bl.length()); uint32_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_varint(u, p); ASSERT_EQ(v[i][0], u); } { - bufferlist bl; + buffer::list bl; { auto app = bl.get_contiguous_appender(16, true); denc_signed_varint(v[i][0], app); @@ -401,14 +433,14 @@ TEST(small_encoding, varint) { cout << std::hex << v[i][0] << "\t" << v[i][2] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][2]); + ASSERT_EQ(v[i][2], bl.length()); int32_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_signed_varint(u, p); ASSERT_EQ((int32_t)v[i][0], u); } { - bufferlist bl; + buffer::list bl; int64_t x = -(int64_t)v[i][0]; { auto app = bl.get_contiguous_appender(16, true); @@ -417,7 +449,7 @@ TEST(small_encoding, varint) { cout << std::dec << x << std::hex << "\t" << v[i][3] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][3]); + ASSERT_EQ(v[i][3], bl.length()); int64_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_signed_varint(u, p); @@ -456,7 +488,7 @@ TEST(small_encoding, varint_lowz) { }; for (unsigned i=0; v[i][1]; ++i) { { - bufferlist bl; + buffer::list bl; { auto app = bl.get_contiguous_appender(16, true); denc_varint_lowz(v[i][0], app); @@ -464,14 +496,14 @@ TEST(small_encoding, varint_lowz) { cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][1]); + ASSERT_EQ(v[i][1], bl.length()); uint32_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_varint_lowz(u, p); ASSERT_EQ(v[i][0], u); } { - bufferlist bl; + buffer::list bl; int64_t x = v[i][0]; { auto app = bl.get_contiguous_appender(16, true); @@ -480,14 +512,14 @@ TEST(small_encoding, varint_lowz) { cout << std::hex << x << "\t" << v[i][1] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][2]); + ASSERT_EQ(v[i][2], bl.length()); int64_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_signed_varint_lowz(u, p); ASSERT_EQ(x, u); } { - bufferlist bl; + buffer::list bl; int64_t x = -(int64_t)v[i][0]; { auto app = bl.get_contiguous_appender(16, true); @@ -496,7 +528,7 @@ TEST(small_encoding, varint_lowz) { cout << std::dec << x << "\t" << v[i][1] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][3]); + ASSERT_EQ(v[i][3], bl.length()); int64_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_signed_varint_lowz(u, p); @@ -527,7 +559,7 @@ TEST(small_encoding, lba) { {0, 0} }; for (unsigned i=0; v[i][1]; ++i) { - bufferlist bl; + buffer::list bl; { auto app = bl.get_contiguous_appender(16, true); denc_lba(v[i][0], app); @@ -535,7 +567,7 @@ TEST(small_encoding, lba) { cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t"; bl.hexdump(cout, false); cout << std::endl; - ASSERT_EQ(bl.length(), v[i][1]); + ASSERT_EQ(v[i][1], bl.length()); uint64_t u; auto p = bl.begin().get_current_ptr().cbegin(); denc_lba(u, p); @@ -543,3 +575,977 @@ TEST(small_encoding, lba) { } } + +/* + * encoding.h Compatibility Coverage + * + * New tests for interface-preserving modernization of + * include/encoding.h belong below this section. + */ + +namespace { + +struct encoding_compat_record final { + uint32_t n = 0; + std::string s; + + static inline uint64_t last_features = 0; + static inline unsigned feature_encode_calls = 0; + + static void reset_counters() + { + last_features = 0; + feature_encode_calls = 0; + } + + friend bool operator==(const encoding_compat_record& lhs, + const encoding_compat_record& rhs) + { + return lhs.n == rhs.n && lhs.s == rhs.s; + } + + friend bool operator<(const encoding_compat_record& lhs, + const encoding_compat_record& rhs) + { + return std::tie(lhs.n, lhs.s) < std::tie(rhs.n, rhs.s); + } + + friend void encode(const encoding_compat_record& v, buffer::list& bl) + { + encode(v.n, bl); + encode(v.s, bl); + } + + friend void encode(const encoding_compat_record& v, + buffer::list& bl, + uint64_t features) + { + last_features = features; + ++feature_encode_calls; + encode(v, bl); + } + + friend void decode(encoding_compat_record& v, buffer::list::const_iterator& p) + { + decode(v.n, p); + decode(v.s, p); + } + + friend std::ostream& operator<<(std::ostream& os, + const encoding_compat_record& v) + { + return os << "{n=" << v.n << ", s=" << v.s << "}"; + } +}; + +struct encoding_compat_record_hash final { + std::size_t operator()(const encoding_compat_record& v) const + { + return std::hash {}(v.n) ^ (std::hash {}(v.s) << 1); + } +}; + +struct encoding_compat_v1_record final { + uint32_t n = 0; + std::string s; + + void decode(buffer::list::const_iterator& p) + { + DECODE_START(1, p); + decode(n, p); + decode(s, p); + DECODE_FINISH(p); + } + + friend bool operator==(const encoding_compat_v1_record& lhs, + const encoding_compat_v1_record& rhs) + { + return lhs.n == rhs.n && lhs.s == rhs.s; + } +}; + +struct encoding_compat_v2_record final { + uint32_t n = 0; + std::string s; + std::string extra; + + void encode(buffer::list& bl) const + { + ENCODE_START(2, 1, bl); + encode(n, bl); + encode(s, bl); + encode(extra, bl); + ENCODE_FINISH(bl); + } +}; + +struct encoding_macro_record final { + __u8 value = 0; + + void encode(buffer::list& bl) const + { + ENCODE_START(1, 1, bl); + encode(value, bl); + ENCODE_FINISH(bl); + } + + void decode(buffer::list::const_iterator& p) + { + DECODE_START(1, p); + decode(value, p); + DECODE_FINISH(p); + } +}; + +struct encoding_new_compat_record final { + __u8 value = 0; + + void encode(buffer::list& bl) const + { + ENCODE_START(1, 1, bl); + encode(value, bl); + ENCODE_FINISH_NEW_COMPAT(bl, 2); + } +}; + +static const encoding_compat_record alpha { 1, "alpha" }; +static const encoding_compat_record bravo { 2, "bravo" }; +static const encoding_compat_record bravo_alt { 2, "bravo-alt" }; +static const encoding_compat_record charlie { 3, "charlie" }; + +template +T encoding_compat_round_trip(const T& src) +{ + buffer::list bl; + encode(src, bl); + + T dst; + auto p = bl.cbegin(); + decode(dst, p); + + EXPECT_TRUE(p.end()); + return dst; +} + +template +void expect_encoding_compat_round_trip(const T& src) +{ + EXPECT_EQ(src, encoding_compat_round_trip(src)); +} + +template +void expect_encoding_compat_nohead_round_trip(const T& src, unsigned n) +{ + buffer::list bl; + encode_nohead(src, bl); + + T dst; + auto p = bl.cbegin(); + decode_nohead(n, dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(src, dst); +} + +template +void expect_nohead_decode_replaces_existing(const T& src, T dst) +{ + buffer::list bl; + encode_nohead(src, bl); + + auto p = bl.cbegin(); + decode_nohead(src.size(), dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(src, dst); +} + +void append_compat_le32(std::string& out, uint32_t v) +{ + for (unsigned i = 0; i < 4; ++i) { + out.push_back(static_cast(v >> (i * 8))); + } +} + +void append_compat_le64(std::string& out, uint64_t v) +{ + for (unsigned i = 0; i < 8; ++i) { + out.push_back(static_cast(v >> (i * 8))); + } +} + +std::string float_wire_bytes(uint32_t raw) +{ + auto out = std::string {}; + append_compat_le32(out, raw); + return out; +} + +std::string float_wire_bytes(uint64_t raw) +{ + auto out = std::string {}; + append_compat_le64(out, raw); + return out; +} + +void expect_float_wire_format(float v, uint32_t raw) +{ + buffer::list bl; + encode(v, bl); + + EXPECT_EQ(float_wire_bytes(raw), bl.to_str()); + + float out = 0; + auto p = bl.cbegin(); + decode(out, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(raw, std::bit_cast(out)); +} + +void expect_float_wire_format(double v, uint64_t raw) +{ + buffer::list bl; + encode(v, bl); + + EXPECT_EQ(float_wire_bytes(raw), bl.to_str()); + + double out = 0; + auto p = bl.cbegin(); + decode(out, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(raw, std::bit_cast(out)); +} + +void append_compat_string(std::string& out, std::string_view v) +{ + append_compat_le32(out, v.size()); + out.append(v); +} + +void append_compat_record(std::string& out, const encoding_compat_record& v) +{ + append_compat_le32(out, v.n); + append_compat_string(out, v.s); +} + +void encode_macro_header(buffer::list& bl, __u8 struct_v, + __u8 struct_compat, __u32 struct_len) +{ + encode(struct_v, bl); + encode(struct_compat, bl); + encode(struct_len, bl); +} + +void decode_macro_overread(buffer::list::const_iterator& p) +{ + DECODE_START(1, p); + p += 2; + DECODE_FINISH(p); +} + +void decode_macro_unknown(buffer::list& payload, + buffer::list::const_iterator& p) +{ + DECODE_UNKNOWN(payload, p); +} + +std::pair<__u8, __u32> decode_unchecked_macro_header(buffer::list::const_iterator& p) +{ + DECODE_START_UNCHECKED(3, p); + DECODE_FINISH(p); + + return { struct_v, struct_len }; +} + +__u8 decode_legacy_macro_value(buffer::list::const_iterator& p) +{ + __u8 value = 0; + + DECODE_START_LEGACY_COMPAT_LEN(2, 2, 2, p); + decode(value, p); + DECODE_FINISH(p); + + return value; +} + +__u8 decode_legacy_macro_value_16(buffer::list::const_iterator& p) +{ + __u8 value = 0; + + DECODE_START_LEGACY_COMPAT_LEN_16(2, 2, 2, p); + decode(value, p); + DECODE_FINISH(p); + + return value; +} + +__u8 decode_legacy_macro_value_32(buffer::list::const_iterator& p) +{ + __u8 value = 0; + + DECODE_START_LEGACY_COMPAT_LEN_32(2, 2, 2, p); + decode(value, p); + DECODE_FINISH(p); + + return value; +} + +template +std::string encode_compat_to_string(const T& v) +{ + buffer::list bl; + encode(v, bl); + return bl.to_str(); +} + +template +void expect_compat_wire_format(const T& v, std::string_view expected) +{ + EXPECT_EQ(expected, encode_compat_to_string(v)); +} + +template +void expect_feature_compat_wire_format(const T& v, std::string_view expected) +{ + buffer::list bl; + encode(v, bl, 123); + + EXPECT_EQ(expected, bl.to_str()); +} + +template +void expect_feature_encode_calls(const T& v, std::size_t expected_calls) +{ + buffer::list bl; + + encoding_compat_record::reset_counters(); + encode(v, bl, 123); + + EXPECT_EQ(uint64_t { 123 }, encoding_compat_record::last_features); + EXPECT_EQ(expected_calls, encoding_compat_record::feature_encode_calls); +} + +template +buffer::list make_decode_noclear_payload(const RangeT& entries) +{ + auto bl = buffer::list {}; + encode(static_cast<__u32>(std::ranges::size(entries)), bl); + + std::ranges::for_each(entries, [&bl](const auto& item) { + encode(item.first, bl); + encode(item.second, bl); + }); + + return bl; +} + +} // namespace + +TEST(EncodingCompatibility, LegacySequenceContainersRoundTrip) +{ + expect_encoding_compat_round_trip( + std::vector { alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + std::list { alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + std::deque { alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + boost::container::small_vector { + alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + std::array { alpha, bravo, charlie }); +} + +TEST(EncodingCompatibility, BufferTypesRoundTrip) +{ + bufferptr ptr_src("bufferptr-value", 15); + auto ptr_dst = encoding_compat_round_trip(ptr_src); + + ASSERT_EQ(ptr_src.length(), ptr_dst.length()); + EXPECT_EQ(std::string_view(ptr_src.c_str(), ptr_src.length()), + std::string_view(ptr_dst.c_str(), ptr_dst.length())); + + buffer::list list_src; + list_src.append("first-", 6); + list_src.append("second", 6); + auto list_dst = encoding_compat_round_trip(list_src); + + EXPECT_EQ(list_src.length(), list_dst.length()); + EXPECT_EQ(list_src.to_str(), list_dst.to_str()); +} + +TEST(EncodingCompatibility, FloatingPointWireFormat) +{ + expect_float_wire_format(std::bit_cast(uint32_t { 0x00000000 }), + uint32_t { 0x00000000 }); + expect_float_wire_format(std::bit_cast(uint32_t { 0x80000000 }), + uint32_t { 0x80000000 }); + expect_float_wire_format(std::bit_cast(uint32_t { 0x3f800000 }), + uint32_t { 0x3f800000 }); + expect_float_wire_format(std::bit_cast(uint32_t { 0x3dfbe76d }), + uint32_t { 0x3dfbe76d }); + expect_float_wire_format(std::bit_cast(uint32_t { 0x7fc00001 }), + uint32_t { 0x7fc00001 }); + + expect_float_wire_format(std::bit_cast(uint64_t { 0x0000000000000000 }), + uint64_t { 0x0000000000000000 }); + expect_float_wire_format(std::bit_cast(uint64_t { 0x8000000000000000 }), + uint64_t { 0x8000000000000000 }); + expect_float_wire_format(std::bit_cast(uint64_t { 0x3ff0000000000000 }), + uint64_t { 0x3ff0000000000000 }); + expect_float_wire_format(std::bit_cast(uint64_t { 0x3fbf7ced916872b0 }), + uint64_t { 0x3fbf7ced916872b0 }); + expect_float_wire_format(std::bit_cast(uint64_t { 0x7ff8000000000001 }), + uint64_t { 0x7ff8000000000001 }); +} + +TEST(EncodingCompatibility, LegacySetContainersRoundTrip) +{ + expect_encoding_compat_round_trip( + std::set { alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + boost::container::flat_set { alpha, bravo, charlie }); + expect_encoding_compat_round_trip( + std::multiset { alpha, bravo, bravo, charlie }); + expect_encoding_compat_round_trip( + std::unordered_set { + alpha, bravo, charlie }); +} + +TEST(EncodingCompatibility, LegacyMapContainersRoundTrip) +{ + expect_encoding_compat_round_trip( + std::map { + { alpha, bravo }, + { bravo, charlie }, + }); + expect_encoding_compat_round_trip( + boost::container::flat_map { + { alpha, bravo }, + { bravo, charlie }, + }); + expect_encoding_compat_round_trip( + std::multimap { + { alpha, bravo }, + { alpha, bravo_alt }, + { bravo, charlie }, + }); + expect_encoding_compat_round_trip( + std::unordered_map { + { alpha, bravo }, + { bravo, charlie }, + }); +} + +TEST(EncodingCompatibility, LegacyNoHeadRoundTrip) +{ + expect_encoding_compat_nohead_round_trip( + std::vector { alpha, bravo, charlie }, 3); + expect_encoding_compat_nohead_round_trip( + boost::container::small_vector { + alpha, bravo, charlie }, 3); + expect_encoding_compat_nohead_round_trip( + std::set { alpha, bravo, charlie }, 3); + expect_encoding_compat_nohead_round_trip( + boost::container::flat_set { + alpha, bravo, charlie }, 3); + expect_encoding_compat_nohead_round_trip( + std::map { + { alpha, bravo }, + { bravo, charlie }, + }, 2); + expect_encoding_compat_nohead_round_trip( + boost::container::flat_map { + { alpha, bravo }, + { bravo, charlie }, + }, 2); +} + +TEST(EncodingCompatibility, LegacyNoHeadDecodeClearsDestination) +{ + expect_nohead_decode_replaces_existing( + std::vector { alpha, bravo }, + std::vector { charlie }); + expect_nohead_decode_replaces_existing( + boost::container::small_vector { alpha, bravo }, + boost::container::small_vector { charlie }); + expect_nohead_decode_replaces_existing( + std::set { alpha, bravo }, + std::set { charlie }); + expect_nohead_decode_replaces_existing( + boost::container::flat_set { alpha, bravo }, + boost::container::flat_set { charlie }); + expect_nohead_decode_replaces_existing( + std::map { + { alpha, bravo }, + }, + std::map { + { charlie, alpha }, + }); + expect_nohead_decode_replaces_existing( + boost::container::flat_map { + { alpha, bravo }, + }, + boost::container::flat_map { + { charlie, alpha }, + }); +} + +TEST(EncodingCompatibility, LegacySetNoHeadEncodingWireFormat) +{ + const std::set src { alpha, bravo }; + std::string expected; + append_compat_record(expected, alpha); + append_compat_record(expected, bravo); + + buffer::list bl; + encode_nohead(src, bl); + + EXPECT_EQ(expected, bl.to_str()); +} + +TEST(EncodingCompatibility, LegacyFlatSetNoHeadEncodingWireFormat) +{ + const boost::container::flat_set src { alpha, bravo }; + std::string expected; + append_compat_record(expected, alpha); + append_compat_record(expected, bravo); + + buffer::list bl; + encode_nohead(src, bl); + + EXPECT_EQ(expected, bl.to_str()); +} + +TEST(EncodingCompatibility, LegacyOptionalTupleAndPairRoundTrip) +{ + expect_encoding_compat_round_trip( + std::optional { alpha }); + expect_encoding_compat_round_trip( + std::optional {}); + expect_encoding_compat_round_trip( + boost::optional { bravo }); + expect_encoding_compat_round_trip( + boost::optional {}); + expect_encoding_compat_round_trip( + std::tuple { + alpha, "tuple", 7 }); + expect_encoding_compat_round_trip( + std::pair { + alpha, bravo }); + + buffer::list bl; + encode(std::string_view { "string-view" }, bl); + + std::string string_view_out; + auto p = bl.cbegin(); + decode(string_view_out, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ("string-view", string_view_out); + + const auto src = boost::make_tuple(alpha, bravo, charlie); + const auto dst = encoding_compat_round_trip(src); + EXPECT_EQ(boost::get<0>(src), boost::get<0>(dst)); + EXPECT_EQ(boost::get<1>(src), boost::get<1>(dst)); + EXPECT_EQ(boost::get<2>(src), boost::get<2>(dst)); +} + +TEST(EncodingCompatibility, SharedPtrContainersRoundTrip) +{ + using compat_ptr = std::shared_ptr; + + const std::list list_src { + std::make_shared(alpha), + nullptr, + std::make_shared(bravo), + }; + auto list_dst = encoding_compat_round_trip(list_src); + + ASSERT_EQ(list_src.size(), list_dst.size()); + auto list_it = list_dst.begin(); + ASSERT_TRUE(*list_it); + EXPECT_EQ(alpha, **list_it); + ++list_it; + ASSERT_TRUE(*list_it); + EXPECT_EQ(encoding_compat_record {}, **list_it); + ++list_it; + ASSERT_TRUE(*list_it); + EXPECT_EQ(bravo, **list_it); + + const std::vector vector_src { + std::make_shared(alpha), + nullptr, + std::make_shared(bravo), + }; + auto vector_dst = encoding_compat_round_trip(vector_src); + + ASSERT_EQ(vector_src.size(), vector_dst.size()); + ASSERT_TRUE(vector_dst[0]); + ASSERT_TRUE(vector_dst[1]); + ASSERT_TRUE(vector_dst[2]); + EXPECT_EQ(alpha, *vector_dst[0]); + EXPECT_EQ(encoding_compat_record {}, *vector_dst[1]); + EXPECT_EQ(bravo, *vector_dst[2]); +} + +TEST(EncodingCompatibility, FeatureEncodingPropagatesToLegacyContainerElements) +{ + expect_feature_encode_calls( + std::vector { alpha, bravo, charlie }, 3); + expect_feature_encode_calls( + std::list { alpha, bravo, charlie }, 3); + expect_feature_encode_calls( + std::deque { alpha, bravo, charlie }, 3); + expect_feature_encode_calls( + boost::container::small_vector { + alpha, bravo, charlie }, 3); + expect_feature_encode_calls( + std::array { alpha, bravo, charlie }, 3); + expect_feature_encode_calls( + std::map { + { alpha, bravo }, + { bravo, charlie }, + }, 4); + expect_feature_encode_calls( + boost::container::flat_map { + { alpha, bravo }, + { bravo, charlie }, + }, 4); + expect_feature_encode_calls( + std::unordered_map { + { alpha, bravo }, + { bravo, charlie }, + }, 4); + expect_feature_encode_calls( + std::vector> { + std::make_shared(alpha), + nullptr, + std::make_shared(bravo), + }, 3); +} + +TEST(EncodingCompatibility, DecodeNoClearMapPreservesDuplicateKeys) +{ + const std::map src { + { alpha, bravo }, + }; + buffer::list bl; + encode(src, bl); + + std::map dst { + { alpha, charlie }, + { charlie, alpha }, + }; + auto p = bl.cbegin(); + decode_noclear(dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(charlie, dst.at(alpha)); + EXPECT_EQ(alpha, dst.at(charlie)); +} + +TEST(EncodingCompatibility, DecodeNoClearFlatMapReplacesDuplicateKeys) +{ + const boost::container::flat_map src { + { alpha, bravo }, + }; + buffer::list bl; + encode(src, bl); + + boost::container::flat_map dst { + { alpha, charlie }, + { charlie, alpha }, + }; + auto p = bl.cbegin(); + decode_noclear(dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(bravo, dst.at(alpha)); + EXPECT_EQ(alpha, dst.at(charlie)); +} + +TEST(EncodingCompatibility, DecodeNoClearPrimitiveMapPreservesDuplicateKeys) +{ + const auto entries = std::array { + std::pair { uint64_t { 7 }, std::bit_cast(uint32_t { 0x3f800000 }) }, + std::pair { uint64_t { 11 }, std::bit_cast(uint32_t { 0x40000000 }) }, + }; + auto bl = make_decode_noclear_payload(entries); + + std::map dst { + { uint64_t { 7 }, std::bit_cast(uint32_t { 0x40400000 }) }, + { uint64_t { 13 }, std::bit_cast(uint32_t { 0x40800000 }) }, + }; + auto p = bl.cbegin(); + decode_noclear(dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(3u, dst.size()); + EXPECT_EQ(uint32_t { 0x40400000 }, std::bit_cast(dst.at(7))); + EXPECT_EQ(uint32_t { 0x40000000 }, std::bit_cast(dst.at(11))); + EXPECT_EQ(uint32_t { 0x40800000 }, std::bit_cast(dst.at(13))); +} + +TEST(EncodingCompatibility, DecodeNoClearPrimitiveFlatMapReplacesDuplicateKeys) +{ + const auto entries = std::array { + std::pair { uint64_t { 7 }, std::bit_cast(uint32_t { 0x3f800000 }) }, + std::pair { uint64_t { 11 }, std::bit_cast(uint32_t { 0x40000000 }) }, + }; + auto bl = make_decode_noclear_payload(entries); + + boost::container::flat_map dst { + { uint64_t { 7 }, std::bit_cast(uint32_t { 0x40400000 }) }, + { uint64_t { 13 }, std::bit_cast(uint32_t { 0x40800000 }) }, + }; + auto p = bl.cbegin(); + decode_noclear(dst, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(3u, dst.size()); + EXPECT_EQ(uint32_t { 0x3f800000 }, std::bit_cast(dst.at(7))); + EXPECT_EQ(uint32_t { 0x40000000 }, std::bit_cast(dst.at(11))); + EXPECT_EQ(uint32_t { 0x40800000 }, std::bit_cast(dst.at(13))); +} + +TEST(EncodingCompatibility, DecodeFinishSkipsUnknownTrailingFields) +{ + const encoding_compat_v2_record src { 7, "known", "unknown" }; + buffer::list bl; + src.encode(bl); + + encoding_compat_v1_record dst; + auto p = bl.cbegin(); + dst.decode(p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ((encoding_compat_v1_record { 7, "known" }), dst); +} + +TEST(EncodingCompatibility, MacroDecodeRejectsNewerCompat) +{ + buffer::list bl; + encode_macro_header(bl, 2, 2, 0); + + encoding_macro_record dst; + auto p = bl.cbegin(); + + EXPECT_THROW(dst.decode(p), buffer::malformed_input); +} + +TEST(EncodingCompatibility, MacroDecodeRejectsShortPayload) +{ + buffer::list bl; + encode_macro_header(bl, 1, 1, 1); + + encoding_macro_record dst; + auto p = bl.cbegin(); + + EXPECT_THROW(dst.decode(p), buffer::malformed_input); +} + +TEST(EncodingCompatibility, MacroDecodeFinishSkipsUnreadBytes) +{ + buffer::list bl; + encode_macro_header(bl, 1, 1, 2); + encode(__u8 { 17 }, bl); + encode(__u8 { 19 }, bl); + + encoding_macro_record dst; + auto p = bl.cbegin(); + dst.decode(p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(__u8 { 17 }, dst.value); +} + +TEST(EncodingCompatibility, MacroDecodeFinishRejectsOverread) +{ + buffer::list bl; + encode_macro_header(bl, 1, 1, 1); + encode(__u8 { 17 }, bl); + encode(__u8 { 19 }, bl); + + auto p = bl.cbegin(); + + EXPECT_THROW(decode_macro_overread(p), buffer::malformed_input); +} + +TEST(EncodingCompatibility, MacroDecodeUnknownPreservesEnvelope) +{ + const encoding_macro_record src { 42 }; + buffer::list bl; + src.encode(bl); + + buffer::list payload; + auto p = bl.cbegin(); + decode_macro_unknown(payload, p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(bl.to_str(), payload.to_str()); +} + +TEST(EncodingCompatibility, MacroEncodeFinishNewCompatUpdatesEnvelope) +{ + const encoding_new_compat_record src { 43 }; + buffer::list bl; + src.encode(bl); + + encoding_macro_record dst; + auto p = bl.cbegin(); + + EXPECT_THROW(dst.decode(p), buffer::malformed_input); +} + +TEST(EncodingCompatibility, MacroDecodeStartUncheckedExposesHeaderFields) +{ + buffer::list bl; + encode_macro_header(bl, 2, 1, 0); + + auto p = bl.cbegin(); + const auto [struct_v, struct_len] = decode_unchecked_macro_header(p); + + EXPECT_TRUE(p.end()); + EXPECT_EQ(__u8 { 2 }, struct_v); + EXPECT_EQ(__u32 { 0 }, struct_len); +} + +TEST(EncodingCompatibility, MacroLegacyCompatLenDecodesOldPayload) +{ + buffer::list bl; + encode(__u8 { 1 }, bl); + encode(__u8 { 23 }, bl); + + auto p = bl.cbegin(); + + EXPECT_EQ(__u8 { 23 }, decode_legacy_macro_value(p)); + EXPECT_TRUE(p.end()); +} + +TEST(EncodingCompatibility, MacroLegacyCompatLenDecodesCurrentPayload) +{ + buffer::list bl; + encode_macro_header(bl, 2, 1, 1); + encode(__u8 { 29 }, bl); + + auto p = bl.cbegin(); + + EXPECT_EQ(__u8 { 29 }, decode_legacy_macro_value(p)); + EXPECT_TRUE(p.end()); +} + +TEST(EncodingCompatibility, MacroLegacyCompatLenRejectsNewerCompat) +{ + buffer::list bl; + encode_macro_header(bl, 2, 3, 0); + + auto p = bl.cbegin(); + + EXPECT_THROW(decode_legacy_macro_value(p), buffer::malformed_input); +} + +TEST(EncodingCompatibility, MacroLegacyCompatLen16SkipsOldVersionBytes) +{ + buffer::list bl; + encode(__u16 { 1 }, bl); + encode(__u8 { 31 }, bl); + + auto p = bl.cbegin(); + + EXPECT_EQ(__u8 { 31 }, decode_legacy_macro_value_16(p)); + EXPECT_TRUE(p.end()); +} + +TEST(EncodingCompatibility, MacroLegacyCompatLen32SkipsOldVersionBytes) +{ + buffer::list bl; + encode(__u32 { 1 }, bl); + encode(__u8 { 37 }, bl); + + auto p = bl.cbegin(); + + EXPECT_EQ(__u8 { 37 }, decode_legacy_macro_value_32(p)); + EXPECT_TRUE(p.end()); +} + +TEST(EncodingCompatibility, RepresentativeWireFormats) +{ + std::string string_wire; + append_compat_string(string_wire, "hello"); + expect_compat_wire_format(std::string { "hello" }, string_wire); + + std::string vector_u64_wire; + append_compat_le32(vector_u64_wire, 2); + append_compat_le64(vector_u64_wire, 11); + append_compat_le64(vector_u64_wire, 22); + expect_compat_wire_format(std::vector { 11, 22 }, + vector_u64_wire); + + std::string vector_record_wire; + append_compat_le32(vector_record_wire, 2); + append_compat_record(vector_record_wire, alpha); + append_compat_record(vector_record_wire, bravo); + expect_compat_wire_format(std::vector { alpha, bravo }, + vector_record_wire); + + std::string array_record_wire; + append_compat_record(array_record_wire, alpha); + append_compat_record(array_record_wire, bravo); + expect_compat_wire_format(std::array { alpha, bravo }, + array_record_wire); + + std::string shared_ptr_vector_wire; + append_compat_le32(shared_ptr_vector_wire, 5); + append_compat_record(shared_ptr_vector_wire, alpha); + append_compat_record(shared_ptr_vector_wire, encoding_compat_record {}); + append_compat_record(shared_ptr_vector_wire, encoding_compat_record {}); + append_compat_record(shared_ptr_vector_wire, bravo); + append_compat_record(shared_ptr_vector_wire, encoding_compat_record {}); + expect_compat_wire_format( + std::vector> { + std::make_shared(alpha), + nullptr, + nullptr, + std::make_shared(bravo), + nullptr, + }, + shared_ptr_vector_wire); + + std::string shared_ptr_list_wire; + append_compat_le32(shared_ptr_list_wire, 5); + append_compat_record(shared_ptr_list_wire, alpha); + append_compat_record(shared_ptr_list_wire, encoding_compat_record {}); + append_compat_record(shared_ptr_list_wire, encoding_compat_record {}); + append_compat_record(shared_ptr_list_wire, bravo); + append_compat_record(shared_ptr_list_wire, encoding_compat_record {}); + expect_compat_wire_format( + std::list> { + std::make_shared(alpha), + nullptr, + nullptr, + std::make_shared(bravo), + nullptr, + }, + shared_ptr_list_wire); + + expect_feature_compat_wire_format( + std::vector> { + std::make_shared(alpha), + nullptr, + nullptr, + std::make_shared(bravo), + nullptr, + }, + shared_ptr_vector_wire); +} diff --git a/src/test/test_denc.cc b/src/test/test_denc.cc index 89048691c9a3..d81f26fae393 100644 --- a/src/test/test_denc.cc +++ b/src/test/test_denc.cc @@ -35,6 +35,39 @@ using namespace std; // test helpers +template +std::string encode_to_string(const T& v) { + bufferlist bl; + encode(v, bl); + return bl.to_str(); +} + +template +void expect_wire_format(const T& v, const std::string& wire) { + ASSERT_EQ(wire, encode_to_string(v)); + + bufferlist bl; + bl.append(wire); + auto p = bl.cbegin(); + T out; + decode(out, p); + ASSERT_EQ(v, out); + ASSERT_EQ(wire.size(), p.get_off()); +} + +static void append_le32(std::string& s, uint32_t v) { + for (unsigned i = 0; i < 4; ++i) { + s.push_back(static_cast(v >> (i * 8))); + } +} + +static void append_denc_header(std::string& s, uint8_t v, + uint8_t compat, uint32_t len) { + s.push_back(static_cast(v)); + s.push_back(static_cast(compat)); + append_le32(s, len); +} + template void test_encode_decode(T v) { bufferlist bl; @@ -391,6 +424,17 @@ struct foo2_only2_t { }; WRITE_CLASS_DENC_BOUNDED(foo2_only2_t) +struct denc_overread_t { + DENC(denc_overread_t, v, p) { + DENC_START(1, 1, p); + __u8 value; + ::denc(value, p); + ::denc(value, p); + DENC_FINISH(p); + } +}; +WRITE_CLASS_DENC_BOUNDED(denc_overread_t) + struct bar_t { int32_t a = 0; uint64_t b = 123; @@ -801,3 +845,43 @@ TEST(denc, compat_disallows) auto bpi = bl.front().begin(); ASSERT_ANY_THROW(denc(v1,bpi)); } + +TEST(denc, finish_skips_unread_fields) +{ + foo2_accept1_t v2; + v2.a = 5001; + v2.b = 6002; + v2.c = 7003; + + size_t s = 0; + denc(v2, s); + bufferlist bl; + { + auto app = bl.get_contiguous_appender(s); + denc(v2, app); + } + + foo_t v1; + auto bpi = bl.front().begin(); + denc(v1, bpi); + + ASSERT_EQ(v2.a, v1.a); + ASSERT_EQ(v2.b, v1.b); + ASSERT_EQ(bpi.get_pos(), bl.c_str() + bl.length()); +} + +TEST(denc, finish_rejects_overread) +{ + std::string wire; + append_denc_header(wire, 1, 1, 1); + wire.push_back(17); + wire.push_back(19); + + bufferlist bl; + bl.append(wire); + + denc_overread_t value; + auto bpi = bl.front().begin(); + + ASSERT_THROW(denc(value, bpi), buffer::malformed_input); +}