]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
Conceptify and Helper-size encoding.h internals jfw-concepts-encoding 70678/head
authorJesse F. Williamson <jfw@ibm.com>
Fri, 24 Jul 2026 14:23:01 +0000 (07:23 -0700)
committerJesse F. Williamson <jfw@ibm.com>
Wed, 29 Jul 2026 17:39:30 +0000 (10:39 -0700)
Assisted-by: Codex:GPT-5
Signed-off-by: Jesse F. Williamson <jfw@ibm.com>
src/crimson/osd/recovery_backend.cc
src/include/denc.h
src/include/encoding.h
src/osd/PrimaryLogPG.cc
src/test/encoding.cc
src/test/test_denc.cc

index d40fb1704d31a6db9c4810b637e2fc89cde5c754..255be74005cfdc47cc394a4ccf4748309a27fd9b 100644 (file)
@@ -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(
index cbe17a725666a5ba7cc622907d37bef63af04fde..119f040aa7a620fa1a4387d6d6fe21ef01425aa9 100644 (file)
@@ -1377,93 +1377,86 @@ public:
   }
 };
 
-//
-// boost::optional<T>
-//
-template<typename T>
-struct denc_traits<
-  boost::optional<T>,
-  std::enable_if_t<denc_traits<T>::supported>> {
-  using traits = denc_traits<T>;
+namespace _denc {
+  template<typename Optional>
+  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<typename Optional, typename T>
+  struct optional_base {
+    using traits = denc_traits<T>;
 
-  static void bound_encode(const boost::optional<T>& 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<T>& 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<T>& 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<typename U = T>
-  static std::enable_if_t<!!sizeof(U) && !need_contiguous>
-  decode(boost::optional<T>& 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<typename U = T>
-  static void encode_nohead(const boost::optional<T>& 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<T>& 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<T>
+//
+template<typename T>
+struct denc_traits<
+  boost::optional<T>,
+  std::enable_if_t<denc_traits<T>::supported>>
+  : public _denc::optional_base<boost::optional<T>, T> {};
 
 template<>
 struct denc_traits<boost::none_t> {
@@ -1488,86 +1481,8 @@ struct denc_traits<boost::none_t> {
 template<typename T>
 struct denc_traits<
   std::optional<T>,
-  std::enable_if_t<denc_traits<T>::supported>> {
-  using traits = denc_traits<T>;
-
-  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<T>& 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<T>& 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<T>& 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<typename U = T>
-  static std::enable_if_t<!!sizeof(U) && !need_contiguous>
-  decode(std::optional<T>& 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<T>& 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<T>& 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<denc_traits<T>::supported>>
+  : public _denc::optional_base<std::optional<T>, T> {};
 
 template<>
 struct denc_traits<std::nullopt_t> {
@@ -1785,13 +1700,77 @@ inline std::enable_if_t<traits::supported && !traits::featured> 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<char *>(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<char*>(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
index d073c1e7c627a50558274c6184cb849dec727e73..10493e614d3bbf46791fc4d3857205d83ba151ca 100644 (file)
@@ -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 <sage@newdream.net>
+ * 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
 #include <optional>
 #include <unordered_map>
 #include <unordered_set>
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <chrono>
+#include <climits>
+#include <cstdio>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <functional>
+#include <limits>
+#include <list>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
 
 #include <boost/container/small_vector.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
 #include <boost/optional/optional_io.hpp>
 #include <boost/tuple/tuple.hpp>
 
+#include <fcntl.h>
+#include <unistd.h>
+
 #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<itype *>(&v);                                        \
+    e = std::bit_cast<itype>(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<itype *>(&v) = e;                                        \
+    itype raw = e;                                                     \
+    v = std::bit_cast<type>(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<const char *>(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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<Clock, Duration>& t,
 // -----------------------------
 // STL container types
 
+namespace encoding_detail {
+
+template<typename TraitsT>
+concept needs_legacy_encoding = !TraitsT::supported;
+
+template<typename FirstTraitsT, typename SecondTraitsT>
+concept pair_needs_legacy_encoding =
+  needs_legacy_encoding<FirstTraitsT> ||
+  needs_legacy_encoding<SecondTraitsT>;
+
+template<typename KeyT, typename ValueT>
+concept map_decodes_by_emplace =
+  std::move_constructible<KeyT> &&
+  std::move_constructible<ValueT>;
+
+template<typename OptionalT>
+void encode_optional(const OptionalT& p, bufferlist& bl);
+
 template<typename T>
-inline void encode(const boost::optional<T> &p, bufferlist &bl);
-template<typename T>
-inline void decode(boost::optional<T> &p, bufferlist::const_iterator &bp);
-template<typename T>
-inline void encode(const std::optional<T> &p, bufferlist &bl);
-template<typename T>
-inline void decode(std::optional<T> &p, bufferlist::const_iterator &bp);
-template<class A, class B, class C>
-inline void encode(const boost::tuple<A, B, C> &t, bufferlist& bl);
-template<class A, class B, class C>
-inline void decode(boost::tuple<A, B, C> &t, bufferlist::const_iterator &bp);
-template<class A, class B,
-        typename a_traits=denc_traits<A>, typename b_traits=denc_traits<B>>
-inline std::enable_if_t<!a_traits::supported || !b_traits::supported>
-encode(const std::pair<A,B> &p, bufferlist &bl, uint64_t features);
-template<class A, class B,
-        typename a_traits=denc_traits<A>, typename b_traits=denc_traits<B>>
-inline std::enable_if_t<!a_traits::supported ||
-                       !b_traits::supported>
-encode(const std::pair<A,B> &p, bufferlist &bl);
-template<class A, class B,
-        typename a_traits=denc_traits<A>, typename b_traits=denc_traits<B>>
-inline std::enable_if_t<!a_traits::supported ||
-                       !b_traits::supported>
-decode(std::pair<A,B> &pa, bufferlist::const_iterator &p);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::list<T, Alloc>& ls, bufferlist& bl);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::list<T,Alloc>& ls, bufferlist& bl, uint64_t features);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(std::list<T,Alloc>& ls, bufferlist::const_iterator& p);
-template<class T, class Alloc>
-inline void encode(const std::list<std::shared_ptr<T>, Alloc>& ls,
-                  bufferlist& bl);
-template<class T, class Alloc>
-inline void encode(const std::list<std::shared_ptr<T>, Alloc>& ls,
-                  bufferlist& bl, uint64_t features);
-template<class T, class Alloc>
-inline void decode(std::list<std::shared_ptr<T>, Alloc>& ls,
-                  bufferlist::const_iterator& p);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::set<T,Comp,Alloc>& s, bufferlist& bl);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(std::set<T,Comp,Alloc>& s, bufferlist::const_iterator& p);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode_nohead(const std::set<T,Comp,Alloc>& s, bufferlist& bl);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode_nohead(unsigned len, std::set<T,Comp,Alloc>& s, bufferlist::iterator& p);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const boost::container::flat_set<T, Comp, Alloc>& s, bufferlist& bl);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(boost::container::flat_set<T, Comp, Alloc>& s, bufferlist::const_iterator& p);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode_nohead(const boost::container::flat_set<T, Comp, Alloc>& s,
-             bufferlist& bl);
-template<class T, class Comp, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode_nohead(unsigned len, boost::container::flat_set<T, Comp, Alloc>& s,
-             bufferlist::iterator& p);
-template<class T, class Comp, class Alloc>
-inline void encode(const std::multiset<T,Comp,Alloc>& s, bufferlist& bl);
-template<class T, class Comp, class Alloc>
-inline void decode(std::multiset<T,Comp,Alloc>& s, bufferlist::const_iterator& p);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::vector<T,Alloc>& v, bufferlist& bl, uint64_t features);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::vector<T,Alloc>& v, bufferlist& bl);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(std::vector<T,Alloc>& v, bufferlist::const_iterator& p);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode_nohead(const std::vector<T,Alloc>& v, bufferlist& bl);
-template<class T, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode_nohead(unsigned len, std::vector<T,Alloc>& v, bufferlist::const_iterator& p);
-template<class T,class Alloc>
-inline void encode(const std::vector<std::shared_ptr<T>,Alloc>& v,
-                  bufferlist& bl,
-                  uint64_t features);
-template<class T, class Alloc>
-inline void encode(const std::vector<std::shared_ptr<T>,Alloc>& v,
-                  bufferlist& bl);
-template<class T, class Alloc>
-inline void decode(std::vector<std::shared_ptr<T>,Alloc>& v,
-                  bufferlist::const_iterator& p);
-// small_vector
-template<class T, std::size_t N, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl, uint64_t features);
-template<class T, std::size_t N, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl);
-template<class T, std::size_t N, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(boost::container::small_vector<T,N,Alloc>& v, bufferlist::const_iterator& p);
-template<class T, std::size_t N, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode_nohead(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl);
-template<class T, std::size_t N, class Alloc, typename traits=denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode_nohead(unsigned len, boost::container::small_vector<T,N,Alloc>& v, bufferlist::const_iterator& p);
-// std::map
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported ||
-                       !u_traits::supported>
-encode(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl, uint64_t features);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode(std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc>
-inline void decode_noclear(std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode_nohead(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode_nohead(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl, uint64_t features);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode_nohead(unsigned n, std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode(const boost::container::flat_map<T,U,Comp,Alloc>& m, bufferlist& bl);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode(const boost::container::flat_map<T,U,Comp,Alloc>& m, bufferlist& bl,
-       uint64_t features);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode(boost::container::flat_map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc>
-inline void decode_noclear(boost::container::flat_map<T,U,Comp,Alloc>& m,
-                          bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode_nohead(const boost::container::flat_map<T,U,Comp,Alloc>& m,
-             bufferlist& bl);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-encode_nohead(const boost::container::flat_map<T,U,Comp,Alloc>& m,
-             bufferlist& bl, uint64_t features);
-template<class T, class U, class Comp, class Alloc,
-        typename t_traits=denc_traits<T>, typename u_traits=denc_traits<U>>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode_nohead(unsigned n, boost::container::flat_map<T,U,Comp,Alloc>& m,
-             bufferlist::const_iterator& p);
-template<class T, class U, class Comp, class Alloc>
-inline void encode(const std::multimap<T,U,Comp,Alloc>& m, bufferlist& bl);
-template<class T, class U, class Comp, class Alloc>
-inline void decode(std::multimap<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class U, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_map<T,U,Hash,Pred,Alloc>& m, bufferlist& bl,
-                  uint64_t features);
-template<class T, class U, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_map<T,U,Hash,Pred,Alloc>& m, bufferlist& bl);
-template<class T, class U, class Hash, class Pred, class Alloc>
-inline void decode(std::unordered_map<T,U,Hash,Pred,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_set<T,Hash,Pred,Alloc>& m, bufferlist& bl);
-template<class T, class Hash, class Pred, class Alloc>
-inline void decode(std::unordered_set<T,Hash,Pred,Alloc>& m, bufferlist::const_iterator& p);
-template<class T, class Alloc>
-inline void encode(const std::deque<T,Alloc>& ls, bufferlist& bl, uint64_t features);
-template<class T, class Alloc>
-inline void encode(const std::deque<T,Alloc>& ls, bufferlist& bl);
-template<class T, class Alloc>
-inline void decode(std::deque<T,Alloc>& ls, bufferlist::const_iterator& p);
-template<class T, size_t N, typename traits = denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::array<T, N>& v, bufferlist& bl, uint64_t features);
-template<class T, size_t N, typename traits = denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-encode(const std::array<T, N>& v, bufferlist& bl);
-template<class T, size_t N, typename traits = denc_traits<T>>
-inline std::enable_if_t<!traits::supported>
-decode(std::array<T, N>& v, bufferlist::const_iterator& p);
+std::optional<T> decode_optional(bufferlist::const_iterator& p);
 
-// full bl decoder
-template<class T>
-inline void decode(T &o, const bufferlist& bl)
-{
-  auto p = bl.begin();
-  decode(o, p);
-  ceph_assert(p.end());
-}
+template<typename FnT>
+void for_each_count(unsigned n, FnT&& fn);
+
+template<typename RangeT>
+void encode_range_nohead(const RangeT& r, bufferlist& bl);
+
+template<typename RangeT>
+void encode_range_nohead(const RangeT& r, bufferlist& bl, uint64_t features);
+
+template<typename RangeT, typename IteratorT>
+void decode_range_nohead(RangeT& r, IteratorT& p);
+
+template<typename RangeT>
+void encode_range(const RangeT& r, bufferlist& bl);
+
+template<typename RangeT>
+void encode_range(const RangeT& r, bufferlist& bl, uint64_t features);
+
+template<typename ContainerT, typename IteratorT>
+void decode_by_resize_nohead(unsigned len, ContainerT& c,
+                             IteratorT& p);
+
+template<typename ContainerT>
+void decode_by_resize(ContainerT& c, bufferlist::const_iterator& p);
+
+template<typename ContainerT, typename IteratorT>
+void decode_by_emplace_back(unsigned len, ContainerT& c,
+                            IteratorT& p);
+
+template<typename ContainerT>
+void decode_by_emplace_back(ContainerT& c, bufferlist::const_iterator& p);
+
+template<typename ContainerT>
+void clear_and_reserve(ContainerT& c, size_t n);
+
+template<typename ContainerT, typename IteratorT>
+void decode_by_insert(unsigned len, ContainerT& c,
+                      IteratorT& p);
+
+template<typename ContainerT>
+void decode_by_insert(ContainerT& c, bufferlist::const_iterator& p);
+
+template<typename RangeT>
+void encode_shared_ptr_range(const RangeT& r, bufferlist& bl);
+
+template<typename RangeT>
+void encode_shared_ptr_range(const RangeT& r, bufferlist& bl,
+                             uint64_t features);
+
+template<typename ValueT, typename EncodeFnT>
+void append_cached_default_encoding(std::optional<std::string>& bytes,
+                                    bufferlist& bl,
+                                    EncodeFnT&& encode_value);
+
+template<typename RangeT, typename EncodeFnT>
+void encode_shared_ptr_range_with(const RangeT& r, bufferlist& bl,
+                                  EncodeFnT&& encode_value);
+
+template<typename ContainerT>
+void decode_shared_ptr_sequence(ContainerT& c,
+                                bufferlist::const_iterator& p);
+
+template<typename PairT>
+void encode_pair_members(const PairT& item, bufferlist& bl);
+
+template<typename PairT>
+void encode_pair_members(const PairT& item, bufferlist& bl,
+                         uint64_t features);
+
+template<typename PairT>
+void decode_pair_members(PairT& item, bufferlist::const_iterator& p);
+
+template<typename MapT>
+void encode_pair_range_nohead(const MapT& m, bufferlist& bl);
+
+template<typename MapT>
+void encode_pair_range_nohead(const MapT& m, bufferlist& bl,
+                              uint64_t features);
+
+template<typename MapT>
+void encode_pair_range(const MapT& m, bufferlist& bl);
+
+template<typename MapT>
+void encode_pair_range(const MapT& m, bufferlist& bl, uint64_t features);
+
+template<typename MapT, typename IteratorT>
+void decode_map_entries_by_subscript(unsigned n, MapT& m,
+                                     IteratorT& p);
+
+template<typename MapT, typename IteratorT>
+void decode_map_entries_by_emplace(unsigned n, MapT& m,
+                                   IteratorT& p);
+
+template<typename MapT, typename IteratorT>
+void decode_map_entries(unsigned n, MapT& m, IteratorT& p);
+
+template<typename MapT>
+void decode_map_by_subscript(unsigned n, MapT& m,
+                             bufferlist::const_iterator& p);
+
+template<typename MapT>
+void decode_map_by_subscript(MapT& m, bufferlist::const_iterator& p);
+
+template<typename MapT>
+void decode_map(unsigned n, MapT& m, bufferlist::const_iterator& p);
+
+template<typename MapT>
+void decode_map(MapT& m, bufferlist::const_iterator& p);
+
+} // namespace encoding_detail
 
 // boost optional
 template<typename T>
-inline void encode(const boost::optional<T> &p, bufferlist &bl)
+inline void encode(const boost::optional<T>& p, bufferlist& bl)
 {
-  __u8 present = static_cast<bool>(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<typename T>
-inline void decode(boost::optional<T> &p, bufferlist::const_iterator &bp)
+inline void decode(boost::optional<T>& 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<T>(bp);
+  if (decoded) {
+    p = std::move(*decoded);
+    return;
   }
+
+  p.reset();
 }
-#pragma GCC diagnostic pop
-#pragma GCC diagnostic warning "-Wpragmas"
 
 // std optional
 template<typename T>
-inline void encode(const std::optional<T> &p, bufferlist &bl)
+inline void encode(const std::optional<T>& p, bufferlist& bl)
 {
-  __u8 present = static_cast<bool>(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<typename T>
-inline void decode(std::optional<T> &p, bufferlist::const_iterator &bp)
+inline void decode(std::optional<T>& 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<T>(bp);
 }
+#pragma GCC diagnostic pop
 
 // std::tuple
 template<typename... Ts>
-inline void encode(const std::tuple<Ts...> &t, bufferlist& bl)
+inline void encode(const std::tuple<Ts...>t, bufferlist& bl)
 {
   ceph::for_each(t, [&bl](const auto& e) {
-      encode(e, bl);
-    });
+    encode(e, bl);
+  });
 }
 template<typename... Ts>
-inline void decode(std::tuple<Ts...> &t, bufferlist::const_iterator &bp)
+inline void decode(std::tuple<Ts...>& 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<class A, class B, class C>
-inline void encode(const boost::tuple<A, B, C> &t, bufferlist& bl)
+inline void encode(const boost::tuple<A, B, C>t, bufferlist& bl)
 {
   encode(boost::get<0>(t), bl);
   encode(boost::get<1>(t), bl);
   encode(boost::get<2>(t), bl);
 }
 template<class A, class B, class C>
-inline void decode(boost::tuple<A, B, C> &t, bufferlist::const_iterator &bp)
+inline void decode(boost::tuple<A, B, C>& 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<A,B>
+// std::pair<A, B>
 template<class A, class B,
-        typename a_traits, typename b_traits>
-inline std::enable_if_t<!a_traits::supported || !b_traits::supported>
-  encode(const std::pair<A,B> &p, bufferlist &bl, uint64_t features)
+        typename a_traits = denc_traits<A>, typename b_traits = denc_traits<B>>
+requires encoding_detail::pair_needs_legacy_encoding<a_traits, b_traits>
+inline void encode(const std::pair<A, B>& 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<class A, class B,
-        typename a_traits, typename b_traits>
-inline std::enable_if_t<!a_traits::supported ||
-                       !b_traits::supported>
-  encode(const std::pair<A,B> &p, bufferlist &bl)
+        typename a_traits = denc_traits<A>, typename b_traits = denc_traits<B>>
+requires encoding_detail::pair_needs_legacy_encoding<a_traits, b_traits>
+inline void encode(const std::pair<A, B>& p, bufferlist& bl)
 {
-  encode(p.first, bl);
-  encode(p.second, bl);
+  encoding_detail::encode_pair_members(p, bl);
 }
-template<class A, class B, typename a_traits, typename b_traits>
-inline std::enable_if_t<!a_traits::supported ||
-                       !b_traits::supported>
-  decode(std::pair<A,B> &pa, bufferlist::const_iterator &p)
+template<class A, class B,
+        typename a_traits = denc_traits<A>, typename b_traits = denc_traits<B>>
+requires encoding_detail::pair_needs_legacy_encoding<a_traits, b_traits>
+inline void decode(std::pair<A, B>& pa, bufferlist::const_iterator& p)
 {
-  decode(pa.first, p);
-  decode(pa.second, p);
+  encoding_detail::decode_pair_members(pa, p);
 }
 
 // std::list<T>
-template<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const std::list<T, Alloc>& 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<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const std::list<T,Alloc>& 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<char*>(&en));
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::list<T, Alloc>& ls, bufferlist& bl)
+{
+  encoding_detail::encode_range(ls, bl);
+}
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::list<T, Alloc>& ls, bufferlist& bl, uint64_t features)
+{
+  encoding_detail::encode_range(ls, bl, features);
 }
 
-template<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode(std::list<T,Alloc>& ls, bufferlist::const_iterator& p)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(std::list<T, Alloc>& 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<std::shared_ptr<T>>
 template<class T, class Alloc>
 inline void encode(const std::list<std::shared_ptr<T>, 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<class T, class Alloc>
 inline void encode(const std::list<std::shared_ptr<T>, 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<class T, class Alloc>
 inline void decode(std::list<std::shared_ptr<T>, Alloc>& ls,
-                  bufferlist::const_iterator& p)
+                   bufferlist::const_iterator& p)
 {
-  __u32 n;
-  decode(n, p);
-  ls.clear();
-  while (n--) {
-    auto ref = std::make_shared<T>();
-    decode(*ref, p);
-    ls.emplace_back(std::move(ref));
-  }
+  encoding_detail::decode_shared_ptr_sequence(ls, p);
 }
 
 // std::set<T>
-template<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const std::set<T,Comp,Alloc>& s, bufferlist& bl)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::set<T, Comp, Alloc>& 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<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode(std::set<T,Comp,Alloc>& s, bufferlist::const_iterator& p)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(std::set<T, Comp, Alloc>& 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<class T, class Comp, class Alloc, typename traits>
-inline typename std::enable_if<!traits::supported>::type
-  encode_nohead(const std::set<T,Comp,Alloc>& s, bufferlist& bl)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode_nohead(const std::set<T, Comp, Alloc>& s, bufferlist& bl)
 {
-  for (auto p = s.begin(); p != s.end(); ++p)
-    encode(*p, bl);
+  encoding_detail::encode_range_nohead(s, bl);
 }
-template<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode_nohead(unsigned len, std::set<T,Comp,Alloc>& s, bufferlist::const_iterator& p)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode_nohead(unsigned len, std::set<T, Comp, Alloc>& s,
+                          bufferlist::const_iterator& p)
 {
-  for (unsigned i=0; i<len; i++) {
-    T v;
-    decode(v, p);
-    s.insert(v);
-  }
+  encoding_detail::decode_by_insert(len, s, p);
 }
 
 // boost::container::flat_set<T>
-template<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-encode(const boost::container::flat_set<T, Comp, Alloc>& s, bufferlist& bl)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const boost::container::flat_set<T, Comp, Alloc>& 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<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-decode(boost::container::flat_set<T, Comp, Alloc>& s, bufferlist::const_iterator& p)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(boost::container::flat_set<T, Comp, Alloc>& 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<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-encode_nohead(const boost::container::flat_set<T, Comp, Alloc>& s,
-             bufferlist& bl)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode_nohead(const boost::container::flat_set<T, Comp, Alloc>& s,
+                          bufferlist& bl)
 {
-  for (const auto& e : s)
-    encode(e, bl);
+  encoding_detail::encode_range_nohead(s, bl);
 }
-template<class T, class Comp, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-decode_nohead(unsigned len, boost::container::flat_set<T, Comp, Alloc>& s,
-             bufferlist::iterator& p)
+template<class T, class Comp, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode_nohead(unsigned len,
+                          boost::container::flat_set<T, Comp, Alloc>& s,
+                          bufferlist::const_iterator& p)
 {
-  s.reserve(len);
-  for (unsigned i=0; i<len; i++) {
-    T v;
-    decode(v, p);
-    s.insert(v);
-  }
+  encoding_detail::decode_by_insert(len, s, p);
 }
 
 // multiset
 template<class T, class Comp, class Alloc>
-inline void encode(const std::multiset<T,Comp,Alloc>& s, bufferlist& bl)
+inline void encode(const std::multiset<T, Comp, Alloc>& 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<class T, class Comp, class Alloc>
-inline void decode(std::multiset<T,Comp,Alloc>& s, bufferlist::const_iterator& p)
+inline void decode(std::multiset<T, Comp, Alloc>& 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<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const std::vector<T,Alloc>& v, bufferlist& bl, uint64_t features)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::vector<T, Alloc>& 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<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const std::vector<T,Alloc>& v, bufferlist& bl)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::vector<T, Alloc>& 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<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode(std::vector<T,Alloc>& v, bufferlist::const_iterator& p)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(std::vector<T, Alloc>& v, bufferlist::const_iterator& p)
 {
-  __u32 n;
-  decode(n, p);
-  v.resize(n);
-  for (__u32 i=0; i<n; i++) 
-    decode(v[i], p);
+  encoding_detail::decode_by_resize(v, p);
 }
 
-template<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode_nohead(const std::vector<T,Alloc>& v, bufferlist& bl)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode_nohead(const std::vector<T, Alloc>& v, bufferlist& bl)
 {
-  for (auto p = v.begin(); p != v.end(); ++p)
-    encode(*p, bl);
+  encoding_detail::encode_range_nohead(v, bl);
 }
-template<class T, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode_nohead(unsigned len, std::vector<T,Alloc>& v, bufferlist::const_iterator& p)
+template<class T, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode_nohead(unsigned len, std::vector<T, Alloc>& v,
+                          bufferlist::const_iterator& p)
 {
-  v.resize(len);
-  for (__u32 i=0; i<v.size(); i++) 
-    decode(v[i], p);
+  encoding_detail::decode_by_resize_nohead(len, v, p);
 }
 
 // small vector
-template<class T, std::size_t N, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl, uint64_t features)
+template<class T, std::size_t N, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const boost::container::small_vector<T, N, Alloc>& 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<class T, std::size_t N, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl)
+template<class T, std::size_t N, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const boost::container::small_vector<T, N, Alloc>& 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<class T, std::size_t N, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode(boost::container::small_vector<T,N,Alloc>& v, bufferlist::const_iterator& p)
+template<class T, std::size_t N, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(boost::container::small_vector<T, N, Alloc>& 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<class T, std::size_t N, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  encode_nohead(const boost::container::small_vector<T,N,Alloc>& v, bufferlist& bl)
+template<class T, std::size_t N, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode_nohead(const boost::container::small_vector<T, N, Alloc>& v,
+                          bufferlist& bl)
 {
-  for (const auto& i : v)
-    encode(i, bl);
+  encoding_detail::encode_range_nohead(v, bl);
 }
-template<class T, std::size_t N, class Alloc, typename traits>
-inline std::enable_if_t<!traits::supported>
-  decode_nohead(unsigned len, boost::container::small_vector<T,N,Alloc>& v, bufferlist::const_iterator& p)
+template<class T, std::size_t N, class Alloc, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode_nohead(unsigned len,
+                          boost::container::small_vector<T, N, Alloc>& 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<class T,class Alloc>
-inline void encode(const std::vector<std::shared_ptr<T>,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<class T, class Alloc>
+inline void encode(const std::vector<std::shared_ptr<T>, Alloc>& v,
+                   bufferlist& bl,
+                   uint64_t features)
+{
+  encoding_detail::encode_shared_ptr_range(v, bl, features);
 }
 template<class T, class Alloc>
-inline void encode(const std::vector<std::shared_ptr<T>,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<std::shared_ptr<T>, Alloc>& v,
+                   bufferlist& bl)
+{
+  encoding_detail::encode_shared_ptr_range(v, bl);
 }
 template<class T, class Alloc>
-inline void decode(std::vector<std::shared_ptr<T>,Alloc>& v,
-                  bufferlist::const_iterator& p)
+inline void decode(std::vector<std::shared_ptr<T>, Alloc>& v,
+                   bufferlist::const_iterator& p)
 {
-  __u32 n;
-  decode(n, p);
-  v.clear();
-  v.reserve(n);
-  while (n--) {
-    auto ref = std::make_shared<T>();
-    decode(*ref, p);
-    v.emplace_back(std::move(ref));
-  }
+  encoding_detail::decode_shared_ptr_sequence(v, p);
 }
 
 // map
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported ||
-                       !u_traits::supported>
-  encode(const std::map<T,U,Comp,Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode(const std::map<T,U,Comp,Alloc>& 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<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode(const std::map<T, U, Comp, Alloc>& m, bufferlist& bl)
+{
+  encoding_detail::encode_pair_range(m, bl);
 }
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  decode(std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode(const std::map<T, U, Comp, Alloc>& 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 <std::move_constructible T, std::move_constructible U, class Comp, class Alloc,
-    typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode(std::map<T, U, Comp, Alloc>& m, bufferlist::const_iterator& p)
+template<class T, class U, class Comp, class Alloc,
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void decode(std::map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc>
-inline void decode_noclear(std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p)
+[[maybe_unused]] inline void decode_noclear(std::map<T, U, Comp, Alloc>& m,
+                                            bufferlist::const_iterator& p)
 {
-  __u32 n;
-  decode(n, p);
-  while (n--) {
-    T k;
-    decode(k, p);
-    decode(m[k], p);
-  }
-}
-template<std::move_constructible T, std::move_constructible U, class Comp, class Alloc>
-inline void decode_noclear(std::map<T,U,Comp,Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode_nohead(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode_nohead(const std::map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode_nohead(const std::map<T,U,Comp,Alloc>& m, bufferlist& bl, uint64_t features)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode_nohead(const std::map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  decode_nohead(unsigned n, std::map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void decode_nohead(unsigned n, std::map<T, U, Comp, Alloc>& m,
+                          bufferlist::const_iterator& p)
 {
-  m.clear();
-  while (n--) {
-    T k;
-    decode(k, p);
-    decode(m[k], p);
-  }
-}
-
-template <std::move_constructible T, std::move_constructible U, class Comp, class Alloc,
-    typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-decode_nohead(unsigned n, std::map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode(const boost::container::flat_map<T,U,Comp,Alloc>& m, bufferlist& bl)
-{
-  __u32 n = (__u32)(m.size());
-  encode(n, bl);
-  for (typename boost::container::flat_map<T,U,Comp>::const_iterator p
-        = m.begin(); p != m.end(); ++p) {
-    encode(p->first, bl);
-    encode(p->second, bl);
-  }
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode(const boost::container::flat_map<T, U, Comp, Alloc>& m,
+                   bufferlist& bl)
+{
+  encoding_detail::encode_pair_range(m, bl);
 }
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode(const boost::container::flat_map<T,U,Comp,Alloc>& 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<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode(const boost::container::flat_map<T, U, Comp, Alloc>& m,
+                   bufferlist& bl, uint64_t features)
+{
+  encoding_detail::encode_pair_range(m, bl, features);
 }
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  decode(boost::container::flat_map<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void decode(boost::container::flat_map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc>
-inline void decode_noclear(boost::container::flat_map<T,U,Comp,Alloc>& m,
-                          bufferlist::const_iterator& p)
+[[maybe_unused]] inline void decode_noclear(
+  boost::container::flat_map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode_nohead(const boost::container::flat_map<T,U,Comp,Alloc>& 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<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode_nohead(const boost::container::flat_map<T, U, Comp, Alloc>& m,
+                          bufferlist& bl)
+{
+  encoding_detail::encode_pair_range_nohead(m, bl);
 }
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-  inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  encode_nohead(const boost::container::flat_map<T,U,Comp,Alloc>& 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<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void encode_nohead(const boost::container::flat_map<T, U, Comp, Alloc>& m,
+                          bufferlist& bl, uint64_t features)
+{
+  encoding_detail::encode_pair_range_nohead(m, bl, features);
 }
 template<class T, class U, class Comp, class Alloc,
-        typename t_traits, typename u_traits>
-inline std::enable_if_t<!t_traits::supported || !u_traits::supported>
-  decode_nohead(unsigned n, boost::container::flat_map<T,U,Comp,Alloc>& m,
-               bufferlist::const_iterator& p)
+        typename t_traits = denc_traits<T>, typename u_traits = denc_traits<U>>
+requires encoding_detail::pair_needs_legacy_encoding<t_traits, u_traits>
+inline void decode_nohead(unsigned n,
+                          boost::container::flat_map<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc>
-inline void encode(const std::multimap<T,U,Comp,Alloc>& m, bufferlist& bl)
+inline void encode(const std::multimap<T, U, Comp, Alloc>& 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<class T, class U, class Comp, class Alloc>
-inline void decode(std::multimap<T,U,Comp,Alloc>& m, bufferlist::const_iterator& p)
+inline void decode(std::multimap<T, U, Comp, Alloc>& m,
+                   bufferlist::const_iterator& p)
 {
-  __u32 n;
-  decode(n, p);
   m.clear();
-  while (n--) {
-    typename std::pair<T,U> tu = std::pair<T,U>();
+
+  encoding_detail::for_each_count(encoding_detail::decode_count(p), [&m, &p] {
+    auto tu = std::pair<T, U> {};
     decode(tu.first, p);
-    typename std::multimap<T,U,Comp,Alloc>::iterator it = m.insert(tu);
+    auto it = m.insert(std::move(tu));
     decode(it->second, p);
-  }
+  });
 }
 
 // std::unordered_map
 template<class T, class U, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_map<T,U,Hash,Pred,Alloc>& 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<class T, class U, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_map<T,U,Hash,Pred,Alloc>& m, bufferlist& bl)
+inline void encode(const std::unordered_map<T, U, Hash, Pred, Alloc>& 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<class T, class U, class Hash, class Pred, class Alloc>
-inline void decode(std::unordered_map<T,U,Hash,Pred,Alloc>& m, bufferlist::const_iterator& p)
+inline void encode(const std::unordered_map<T, U, Hash, Pred, Alloc>& 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 <std::move_constructible T, std::move_constructible U, class Hash, class Pred, class Alloc>
-inline void decode(std::unordered_map<T, U, Hash, Pred, Alloc>& m, bufferlist::const_iterator& p)
+template<class T, class U, class Hash, class Pred, class Alloc>
+inline void decode(std::unordered_map<T, U, Hash, Pred, Alloc>& 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<class T, class Hash, class Pred, class Alloc>
-inline void encode(const std::unordered_set<T,Hash,Pred,Alloc>& m, bufferlist& bl)
+inline void encode(const std::unordered_set<T, Hash, Pred, Alloc>& 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<class T, class Hash, class Pred, class Alloc>
-inline void decode(std::unordered_set<T,Hash,Pred,Alloc>& m, bufferlist::const_iterator& p)
+inline void decode(std::unordered_set<T, Hash, Pred, Alloc>& 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<class T, class Alloc>
-inline void encode(const std::deque<T,Alloc>& ls, bufferlist& bl, uint64_t features)
+inline void encode(const std::deque<T, Alloc>& 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<class T, class Alloc>
-inline void encode(const std::deque<T,Alloc>& ls, bufferlist& bl)
+inline void encode(const std::deque<T, Alloc>& 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<class T, class Alloc>
-inline void decode(std::deque<T,Alloc>& ls, bufferlist::const_iterator& p)
+inline void decode(std::deque<T, Alloc>& 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<T, N>
-template<class T, size_t N, typename traits>
-inline std::enable_if_t<!traits::supported>
-encode(const std::array<T, N>& v, bufferlist& bl, uint64_t features)
+template<class T, size_t N, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::array<T, N>& v, bufferlist& bl, uint64_t features)
 {
-  for (const auto& e : v)
-    encode(e, bl, features);
+  encoding_detail::encode_range_nohead(v, bl, features);
 }
-template<class T, size_t N, typename traits>
-inline std::enable_if_t<!traits::supported>
-encode(const std::array<T, N>& v, bufferlist& bl)
+template<class T, size_t N, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void encode(const std::array<T, N>& v, bufferlist& bl)
 {
-  for (const auto& e : v)
-    encode(e, bl);
+  encoding_detail::encode_range_nohead(v, bl);
+}
+template<class T, size_t N, typename traits = denc_traits<T>>
+requires encoding_detail::needs_legacy_encoding<traits>
+inline void decode(std::array<T, N>& v, bufferlist::const_iterator& p)
+{
+  encoding_detail::decode_range_nohead(v, p);
+}
+
+namespace encoding_detail {
+
+template<typename OptionalT>
+void encode_optional(const OptionalT& p, bufferlist& bl)
+{
+  __u8 present = static_cast<bool>(p);
+  encode(present, bl);
+  if (!p) {
+    return;
+  }
+
+  encode(*p, bl);
+}
+
+template<typename T>
+std::optional<T> decode_optional(bufferlist::const_iterator& p)
+{
+  __u8 present;
+  decode(present, p);
+  if (!present) {
+    return std::nullopt;
+  }
+
+  T value{};
+  decode(value, p);
+  return value;
+}
+
+template<typename FnT>
+void for_each_count(unsigned n, FnT&& fn)
+{
+  std::ranges::for_each(std::views::iota(0u, n), [&fn](auto) {
+    std::invoke(fn);
+  });
+}
+
+template<typename RangeT>
+void encode_range_nohead(const RangeT& r, bufferlist& bl)
+{
+  std::ranges::for_each(r, [&bl](const auto& item) {
+    encode(item, bl);
+  });
+}
+
+template<typename RangeT>
+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<typename RangeT, typename IteratorT>
+void decode_range_nohead(RangeT& r, IteratorT& p)
+{
+  std::ranges::for_each(r, [&p](auto& item) {
+    decode(item, p);
+  });
+}
+
+template<typename RangeT>
+void encode_range(const RangeT& r, bufferlist& bl)
+{
+  encode_count(std::ranges::size(r), bl);
+  encode_range_nohead(r, bl);
+}
+
+template<typename RangeT>
+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<typename ContainerT>
+void reserve_if_possible(ContainerT& c, size_t n)
+{
+  if constexpr (requires { c.reserve(n); }) {
+    c.reserve(n);
+  }
+}
+
+template<typename ContainerT>
+void clear_and_reserve(ContainerT& c, size_t n)
+{
+  c.clear();
+  reserve_if_possible(c, n);
+}
+
+template<typename ContainerT, typename IteratorT>
+void decode_by_resize_nohead(unsigned len, ContainerT& c,
+                             IteratorT& p)
+{
+  c.resize(len);
+  decode_range_nohead(c, p);
+}
+
+template<typename ContainerT>
+void decode_by_resize(ContainerT& c, bufferlist::const_iterator& p)
+{
+  decode_by_resize_nohead(decode_count(p), c, p);
+}
+
+template<typename ContainerT, typename IteratorT>
+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<typename ContainerT>
+void decode_by_emplace_back(ContainerT& c, bufferlist::const_iterator& p)
+{
+  decode_by_emplace_back(decode_count(p), c, p);
+}
+
+template<typename ContainerT, typename IteratorT>
+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<typename ContainerT>
+void decode_by_insert(ContainerT& c, bufferlist::const_iterator& p)
+{
+  decode_by_insert(decode_count(p), c, p);
+}
+
+template<typename RangeT>
+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<typename RangeT>
+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<typename ValueT, typename EncodeFnT>
+void append_cached_default_encoding(std::optional<std::string>& 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<typename RangeT, typename EncodeFnT>
+void encode_shared_ptr_range_with(const RangeT& r, bufferlist& bl,
+                                  EncodeFnT&& encode_value)
+{
+  using value_type = typename std::ranges::range_value_t<RangeT>::element_type;
+
+  encode_count(std::ranges::size(r), bl);
+
+  auto null_bytes = std::optional<std::string> {};
+
+  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<value_type>(null_bytes, bl, encode_value);
+  });
+}
+
+template<typename ContainerT>
+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<typename ContainerT::value_type::element_type>();
+    decode(*ref, p);
+    c.emplace_back(std::move(ref));
+  });
+}
+
+template<typename PairT>
+void encode_pair_members(const PairT& item, bufferlist& bl)
+{
+  encode(item.first, bl);
+  encode(item.second, bl);
+}
+
+template<typename PairT>
+void encode_pair_members(const PairT& item, bufferlist& bl,
+                         uint64_t features)
+{
+  encode(item.first, bl, features);
+  encode(item.second, bl, features);
+}
+
+template<typename PairT>
+void decode_pair_members(PairT& item, bufferlist::const_iterator& p)
+{
+  decode(item.first, p);
+  decode(item.second, p);
+}
+
+template<typename MapT>
+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<class T, size_t N, typename traits>
-inline std::enable_if_t<!traits::supported>
-decode(std::array<T, N>& v, bufferlist::const_iterator& p)
+
+template<typename MapT>
+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<typename MapT>
+void encode_pair_range(const MapT& m, bufferlist& bl)
+{
+  encode_count(std::ranges::size(m), bl);
+  encode_pair_range_nohead(m, bl);
+}
+
+template<typename MapT>
+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<typename MapT, typename IteratorT>
+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<typename MapT, typename IteratorT>
+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<typename MapT, typename IteratorT>
+void decode_map_entries(unsigned n, MapT& m, IteratorT& p)
+{
+  if constexpr (map_decodes_by_emplace<typename MapT::key_type,
+                                       typename MapT::mapped_type>) {
+    decode_map_entries_by_emplace(n, m, p);
+    return;
+  }
+
+  decode_map_entries_by_subscript(n, m, p);
+}
+
+template<typename MapT>
+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<typename MapT>
+void decode_map_by_subscript(MapT& m, bufferlist::const_iterator& p)
+{
+  decode_map_by_subscript(decode_count(p), m, p);
+}
+
+template<typename MapT>
+void decode_map(unsigned n, MapT& m, bufferlist::const_iterator& p)
+{
+  clear_and_reserve(m, n);
+  decode_map_entries(n, m, p);
+}
+
+template<typename MapT>
+void decode_map(MapT& m, bufferlist::const_iterator& p)
+{
+  decode_map(decode_count(p), m, p);
+}
+
+} // namespace encoding_detail
+
+// full bl decoder
+template<class T>
+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<T, N>& 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<T, N>& 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<T, N>& 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<typename IteratorT>
+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<typename IteratorT>
+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<typename VersionT, typename IteratorT>
+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<typename VersionT, typename IteratorT>
+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<typename FillerT>
+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<const char *>(&struct_v));
+  filler.copy_in(sizeof(struct_compat),
+                 reinterpret_cast<const char *>(&struct_compat));
+  filler.copy_in(sizeof(struct_len),
+                 reinterpret_cast<const char *>(&struct_len));
+}
+
+template<typename IteratorT>
+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<typename PayloadT, typename IteratorT>
+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<T, N>& 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<T, N>& 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<T, N>& 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<T, N>& 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 {
 
index 9a479331b24eac1364bbbde306d06000375ce85e..ce1a0347bf29f0a917f30d84ba099c89f41dd7c9 100644 (file)
@@ -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;
 
index 3d508909d6d54c935146597b488caedbd2520c65..cd49ef3e25764894590720e1fe20d7fe925b380c 100644 (file)
@@ -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 <fmt/format.h>
 #include "gtest/gtest.h"
 
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cstdint>
+#include <functional>
 #include <iostream> // for std::cout
+#include <list>
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/container/small_vector.hpp>
+#include <boost/optional.hpp>
+#include <boost/tuple/tuple.hpp>
 
 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 <typename Size, typename T>
 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<int>(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<uint64_t, denc_traits<uint64_t>>(const uint64_t&,
-                                             bufferlist&,
-                                             uint64_t f) {
+                                             buffer::list&,
+                                             uint64_t features) {
   static_assert(denc_traits<uint64_t>::supported,
                 "should support new encoder");
   static_assert(!denc_traits<uint64_t>::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<ceph_le64, denc_traits<ceph_le64>>(const ceph_le64&,
-                                               bufferlist&,
-                                               uint64_t f) {
+                                               buffer::list&,
+                                               uint64_t features) {
   static_assert(denc_traits<ceph_le64>::supported,
                 "should support new encoder");
   static_assert(!denc_traits<ceph_le64>::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<uint32_t> {}(v.n) ^ (std::hash<std::string> {}(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 <typename T>
+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 <typename T>
+void expect_encoding_compat_round_trip(const T& src)
+{
+  EXPECT_EQ(src, encoding_compat_round_trip(src));
+}
+
+template <typename T>
+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 <typename T>
+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<char>(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<char>(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<uint32_t>(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<uint64_t>(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 <typename T>
+std::string encode_compat_to_string(const T& v)
+{
+  buffer::list bl;
+  encode(v, bl);
+  return bl.to_str();
+}
+
+template <typename T>
+void expect_compat_wire_format(const T& v, std::string_view expected)
+{
+  EXPECT_EQ(expected, encode_compat_to_string(v));
+}
+
+template <typename T>
+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 <typename T>
+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 <std::ranges::sized_range RangeT>
+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<encoding_compat_record> { alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    std::list<encoding_compat_record> { alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    std::deque<encoding_compat_record> { alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    boost::container::small_vector<encoding_compat_record, 4> {
+      alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    std::array<encoding_compat_record, 3> { 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<float>(uint32_t { 0x00000000 }),
+                           uint32_t { 0x00000000 });
+  expect_float_wire_format(std::bit_cast<float>(uint32_t { 0x80000000 }),
+                           uint32_t { 0x80000000 });
+  expect_float_wire_format(std::bit_cast<float>(uint32_t { 0x3f800000 }),
+                           uint32_t { 0x3f800000 });
+  expect_float_wire_format(std::bit_cast<float>(uint32_t { 0x3dfbe76d }),
+                           uint32_t { 0x3dfbe76d });
+  expect_float_wire_format(std::bit_cast<float>(uint32_t { 0x7fc00001 }),
+                           uint32_t { 0x7fc00001 });
+
+  expect_float_wire_format(std::bit_cast<double>(uint64_t { 0x0000000000000000 }),
+                           uint64_t { 0x0000000000000000 });
+  expect_float_wire_format(std::bit_cast<double>(uint64_t { 0x8000000000000000 }),
+                           uint64_t { 0x8000000000000000 });
+  expect_float_wire_format(std::bit_cast<double>(uint64_t { 0x3ff0000000000000 }),
+                           uint64_t { 0x3ff0000000000000 });
+  expect_float_wire_format(std::bit_cast<double>(uint64_t { 0x3fbf7ced916872b0 }),
+                           uint64_t { 0x3fbf7ced916872b0 });
+  expect_float_wire_format(std::bit_cast<double>(uint64_t { 0x7ff8000000000001 }),
+                           uint64_t { 0x7ff8000000000001 });
+}
+
+TEST(EncodingCompatibility, LegacySetContainersRoundTrip)
+{
+  expect_encoding_compat_round_trip(
+    std::set<encoding_compat_record> { alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    boost::container::flat_set<encoding_compat_record> { alpha, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    std::multiset<encoding_compat_record> { alpha, bravo, bravo, charlie });
+  expect_encoding_compat_round_trip(
+    std::unordered_set<encoding_compat_record, encoding_compat_record_hash> {
+      alpha, bravo, charlie });
+}
+
+TEST(EncodingCompatibility, LegacyMapContainersRoundTrip)
+{
+  expect_encoding_compat_round_trip(
+    std::map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    });
+  expect_encoding_compat_round_trip(
+    boost::container::flat_map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    });
+  expect_encoding_compat_round_trip(
+    std::multimap<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { alpha, bravo_alt },
+      { bravo, charlie },
+    });
+  expect_encoding_compat_round_trip(
+    std::unordered_map<encoding_compat_record,
+                       encoding_compat_record,
+                       encoding_compat_record_hash> {
+      { alpha, bravo },
+      { bravo, charlie },
+    });
+}
+
+TEST(EncodingCompatibility, LegacyNoHeadRoundTrip)
+{
+  expect_encoding_compat_nohead_round_trip(
+    std::vector<encoding_compat_record> { alpha, bravo, charlie }, 3);
+  expect_encoding_compat_nohead_round_trip(
+    boost::container::small_vector<encoding_compat_record, 4> {
+      alpha, bravo, charlie }, 3);
+  expect_encoding_compat_nohead_round_trip(
+    std::set<encoding_compat_record> { alpha, bravo, charlie }, 3);
+  expect_encoding_compat_nohead_round_trip(
+    boost::container::flat_set<encoding_compat_record> {
+      alpha, bravo, charlie }, 3);
+  expect_encoding_compat_nohead_round_trip(
+    std::map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    }, 2);
+  expect_encoding_compat_nohead_round_trip(
+    boost::container::flat_map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    }, 2);
+}
+
+TEST(EncodingCompatibility, LegacyNoHeadDecodeClearsDestination)
+{
+  expect_nohead_decode_replaces_existing(
+    std::vector<encoding_compat_record> { alpha, bravo },
+    std::vector<encoding_compat_record> { charlie });
+  expect_nohead_decode_replaces_existing(
+    boost::container::small_vector<encoding_compat_record, 4> { alpha, bravo },
+    boost::container::small_vector<encoding_compat_record, 4> { charlie });
+  expect_nohead_decode_replaces_existing(
+    std::set<encoding_compat_record> { alpha, bravo },
+    std::set<encoding_compat_record> { charlie });
+  expect_nohead_decode_replaces_existing(
+    boost::container::flat_set<encoding_compat_record> { alpha, bravo },
+    boost::container::flat_set<encoding_compat_record> { charlie });
+  expect_nohead_decode_replaces_existing(
+    std::map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+    },
+    std::map<encoding_compat_record, encoding_compat_record> {
+      { charlie, alpha },
+    });
+  expect_nohead_decode_replaces_existing(
+    boost::container::flat_map<encoding_compat_record,
+                               encoding_compat_record> {
+      { alpha, bravo },
+    },
+    boost::container::flat_map<encoding_compat_record,
+                               encoding_compat_record> {
+      { charlie, alpha },
+    });
+}
+
+TEST(EncodingCompatibility, LegacySetNoHeadEncodingWireFormat)
+{
+  const std::set<encoding_compat_record> 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<encoding_compat_record> 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<encoding_compat_record> { alpha });
+  expect_encoding_compat_round_trip(
+    std::optional<encoding_compat_record> {});
+  expect_encoding_compat_round_trip(
+    boost::optional<encoding_compat_record> { bravo });
+  expect_encoding_compat_round_trip(
+    boost::optional<encoding_compat_record> {});
+  expect_encoding_compat_round_trip(
+    std::tuple<encoding_compat_record, std::string, uint32_t> {
+      alpha, "tuple", 7 });
+  expect_encoding_compat_round_trip(
+    std::pair<encoding_compat_record, encoding_compat_record> {
+      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<encoding_compat_record>;
+
+  const std::list<compat_ptr> list_src {
+    std::make_shared<encoding_compat_record>(alpha),
+    nullptr,
+    std::make_shared<encoding_compat_record>(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<compat_ptr> vector_src {
+    std::make_shared<encoding_compat_record>(alpha),
+    nullptr,
+    std::make_shared<encoding_compat_record>(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<encoding_compat_record> { alpha, bravo, charlie }, 3);
+  expect_feature_encode_calls(
+    std::list<encoding_compat_record> { alpha, bravo, charlie }, 3);
+  expect_feature_encode_calls(
+    std::deque<encoding_compat_record> { alpha, bravo, charlie }, 3);
+  expect_feature_encode_calls(
+    boost::container::small_vector<encoding_compat_record, 4> {
+      alpha, bravo, charlie }, 3);
+  expect_feature_encode_calls(
+    std::array<encoding_compat_record, 3> { alpha, bravo, charlie }, 3);
+  expect_feature_encode_calls(
+    std::map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    }, 4);
+  expect_feature_encode_calls(
+    boost::container::flat_map<encoding_compat_record, encoding_compat_record> {
+      { alpha, bravo },
+      { bravo, charlie },
+    }, 4);
+  expect_feature_encode_calls(
+    std::unordered_map<encoding_compat_record,
+                       encoding_compat_record,
+                       encoding_compat_record_hash> {
+      { alpha, bravo },
+      { bravo, charlie },
+    }, 4);
+  expect_feature_encode_calls(
+    std::vector<std::shared_ptr<encoding_compat_record>> {
+      std::make_shared<encoding_compat_record>(alpha),
+      nullptr,
+      std::make_shared<encoding_compat_record>(bravo),
+    }, 3);
+}
+
+TEST(EncodingCompatibility, DecodeNoClearMapPreservesDuplicateKeys)
+{
+  const std::map<encoding_compat_record, encoding_compat_record> src {
+    { alpha, bravo },
+  };
+  buffer::list bl;
+  encode(src, bl);
+
+  std::map<encoding_compat_record, encoding_compat_record> 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<encoding_compat_record,
+                                  encoding_compat_record> src {
+    { alpha, bravo },
+  };
+  buffer::list bl;
+  encode(src, bl);
+
+  boost::container::flat_map<encoding_compat_record,
+                             encoding_compat_record> 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<float>(uint32_t { 0x3f800000 }) },
+    std::pair { uint64_t { 11 }, std::bit_cast<float>(uint32_t { 0x40000000 }) },
+  };
+  auto bl = make_decode_noclear_payload(entries);
+
+  std::map<uint64_t, float> dst {
+    { uint64_t { 7 }, std::bit_cast<float>(uint32_t { 0x40400000 }) },
+    { uint64_t { 13 }, std::bit_cast<float>(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<uint32_t>(dst.at(7)));
+  EXPECT_EQ(uint32_t { 0x40000000 }, std::bit_cast<uint32_t>(dst.at(11)));
+  EXPECT_EQ(uint32_t { 0x40800000 }, std::bit_cast<uint32_t>(dst.at(13)));
+}
+
+TEST(EncodingCompatibility, DecodeNoClearPrimitiveFlatMapReplacesDuplicateKeys)
+{
+  const auto entries = std::array {
+    std::pair { uint64_t { 7 }, std::bit_cast<float>(uint32_t { 0x3f800000 }) },
+    std::pair { uint64_t { 11 }, std::bit_cast<float>(uint32_t { 0x40000000 }) },
+  };
+  auto bl = make_decode_noclear_payload(entries);
+
+  boost::container::flat_map<uint64_t, float> dst {
+    { uint64_t { 7 }, std::bit_cast<float>(uint32_t { 0x40400000 }) },
+    { uint64_t { 13 }, std::bit_cast<float>(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<uint32_t>(dst.at(7)));
+  EXPECT_EQ(uint32_t { 0x40000000 }, std::bit_cast<uint32_t>(dst.at(11)));
+  EXPECT_EQ(uint32_t { 0x40800000 }, std::bit_cast<uint32_t>(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<uint64_t> { 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<encoding_compat_record> { 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<encoding_compat_record, 2> { 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::shared_ptr<encoding_compat_record>> {
+      std::make_shared<encoding_compat_record>(alpha),
+      nullptr,
+      nullptr,
+      std::make_shared<encoding_compat_record>(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::shared_ptr<encoding_compat_record>> {
+      std::make_shared<encoding_compat_record>(alpha),
+      nullptr,
+      nullptr,
+      std::make_shared<encoding_compat_record>(bravo),
+      nullptr,
+    },
+    shared_ptr_list_wire);
+
+  expect_feature_compat_wire_format(
+    std::vector<std::shared_ptr<encoding_compat_record>> {
+      std::make_shared<encoding_compat_record>(alpha),
+      nullptr,
+      nullptr,
+      std::make_shared<encoding_compat_record>(bravo),
+      nullptr,
+    },
+    shared_ptr_vector_wire);
+}
index 89048691c9a3cbec0246f0e716a0843ee0f9314f..d81f26fae39351c1488513fc1a254c9ea4c05201 100644 (file)
@@ -35,6 +35,39 @@ using namespace std;
 
 // test helpers
 
+template<typename T>
+std::string encode_to_string(const T& v) {
+  bufferlist bl;
+  encode(v, bl);
+  return bl.to_str();
+}
+
+template<typename T>
+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<char>(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<char>(v));
+  s.push_back(static_cast<char>(compat));
+  append_le32(s, len);
+}
+
 template<typename T>
 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);
+}