// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2016 Allen Samuels * * 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. * */ // If you #include "include/encoding.h" you get the old-style *and* // the new-style definitions. (The old-style needs denc_traits<> in // order to disable the container helpers when new-style traits are // present.) // You can also just #include "include/denc.h" and get only the // new-style helpers. The eventual goal is to drop the legacy // definitions. #ifndef _ENC_DEC_H #define _ENC_DEC_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "include/compat.h" #include "include/intarith.h" #include "include/int_types.h" #include "include/scope_guard.h" #include "buffer.h" #include "byteorder.h" #include "common/convenience.h" #include "common/error_code.h" template struct denc_traits { static constexpr bool supported = false; static constexpr bool featured = false; static constexpr bool bounded = false; static constexpr bool need_contiguous = true; }; template inline constexpr bool denc_supported = denc_traits::supported; // hack for debug only; FIXME //#include //using std::cout; // Define this to compile in a dump of all encoded objects to disk to // populate ceph-object-corpus. Note that there is an almost // identical implementation in encoding.h, but you only need to define // ENCODE_DUMP_PATH here. // // See src/test/encoding/generate-corpus-objects.sh. // //#define ENCODE_DUMP_PATH /tmp/something #ifdef ENCODE_DUMP_PATH # include # include # include # include # define ENCODE_STR(x) #x # define ENCODE_STRINGIFY(x) ENCODE_STR(x) template class DencDumper { public: DencDumper(const char* name, const ceph::bufferlist::contiguous_appender& appender) : name{name}, appender{appender}, bl_offset{appender.bl.length()}, space_offset{space_size()}, start{appender.get_pos()} {} ~DencDumper() { if (do_sample()) { dump(); } } private: static bool do_sample() { // this hackery with bits below is just to get a semi-reasonable // distribution across time. it is somewhat exponential but not // quite. i++; int bits = 0; for (unsigned t = i; t; bits++) t &= t - 1; return bits <= 2; } size_t space_size() const { return appender.get_logical_offset() - appender.get_out_of_band_offset(); } void dump() const { char fn[PATH_MAX]; ::snprintf(fn, sizeof(fn), ENCODE_STRINGIFY(ENCODE_DUMP_PATH) "/%s__%d.%x", name, getpid(), i++); int fd = ::open(fn, O_WRONLY|O_TRUNC|O_CREAT|O_CLOEXEC|O_BINARY, 0644); if (fd < 0) { return; } auto close_fd = make_scope_guard([fd] { ::close(fd); }); if (auto bl_delta = appender.bl.length() - bl_offset; bl_delta > 0) { ceph::bufferlist dump_bl; appender.bl.begin(bl_offset + space_offset).copy(bl_delta - space_offset, dump_bl); const size_t space_len = space_size(); dump_bl.append(appender.get_pos() - space_len, space_len); dump_bl.write_fd(fd); } else { size_t len = appender.get_pos() - start; [[maybe_unused]] int r = ::write(fd, start, len); } } const char* name; const ceph::bufferlist::contiguous_appender& appender; const size_t bl_offset; const size_t space_offset; const char* start; static int i; }; template int DencDumper::i = 0; # define DENC_DUMP_PRE(Type) \ DencDumper _denc_dumper{#Type, p}; #else # define DENC_DUMP_PRE(Type) #endif /* top level level functions look like so ====================================== inline void denc(const T& o, size_t& p, uint64_t features=0); inline void denc(const T& o, ceph::buffer::list::contiguous_appender& p, uint64_t features=0); inline void denc(T& o, ceph::buffer::ptr::const_iterator& p, uint64_t features=0); or (for featured objects) inline void denc(const T& o, size_t& p, uint64_t features); inline void denc(const T& o, ceph::buffer::list::contiguous_appender& p, uint64_t features); inline void denc(T& o, ceph::buffer::ptr::const_iterator& p, uint64_t features); - These are symmetrical, so that they can be used from the magic DENC method of writing the bound_encode/encode/decode methods all in one go; they differ only in the type of p. - These are automatically fabricated via a template that calls into the denc_traits<> methods (see below), provided denc_traits::supported is defined and true. They never need to be written explicitly. static denc_traits<> definitions look like so ============================================= template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool bounded = false; static constexpr bool featured = false; static constexpr bool need_contiguous = true; static void bound_encode(const T &o, size_t& p, uint64_t f=0); static void encode(const T &o, ceph::buffer::list::contiguous_appender& p, uint64_t f=0); static void decode(T& o, ceph::buffer::ptr::const_iterator &p, uint64_t f=0); }; or (for featured objects) template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool bounded = false; static constexpr bool featured = true; static constexpr bool need_contiguous = true; static void bound_encode(const T &o, size_t& p, uint64_t f); static void encode(const T &o, ceph::buffer::list::contiguous_appender& p, uint64_t f); static void decode(T& o, ceph::buffer::ptr::const_iterator &p, uint64_t f=0); }; - denc_traits is normally declared via the WRITE_CLASS_DENC(type) macro, which is used in place of the old-style WRITE_CLASS_ENCODER(type) macro. There are _FEATURED and _BOUNDED variants. The class traits simply call into class methods of the same name (see below). - denc_traits can also be written explicitly for some type to indicate how it should be encoded. This is the "source of truth" for how a type is encoded. - denc_traits are declared for the base integer types, string, ceph::buffer::ptr, and ceph::buffer::list base types. - denc_traits>-like traits are declared for standard container types. class methods look like so ========================== void bound_encode(size_t& p) const; void encode(ceph::buffer::list::contiguous_appender& p) const; void decode(ceph::buffer::ptr::const_iterator &p); or (for featured objects) void bound_encode(size_t& p, uint64_t f) const; void encode(ceph::buffer::list::contiguous_appender& p, uint64_t f) const; void decode(ceph::buffer::ptr::const_iterator &p); - These are normally invoked by the denc_traits<> methods that are declared via WRITE_CLASS_DENC, although you can also invoke them explicitly in your code. - These methods are optimised for contiguous buffer, but denc() will try rebuild a contigous one if the decoded ceph::buffer::list is segmented. If you are concerned about the cost, you might want to define yet another method: void decode(ceph::buffer::list::iterator &p); - These can be defined either explicitly (as above), or can be "magically" defined all in one go using the DENC macro and DENC_{START,FINISH} helpers (which work like the legacy {ENCODE,DECODE}_{START,FINISH} macros): class foo_t { ... DENC(foo_t, v, p) { DENC_START(1, 1, p); denc(v.foo, p); denc(v.bar, p); denc(v.baz, p); DENC_FINISH(p); } ... }; WRITE_CLASS_DENC(foo_t) */ // --------------------------------------------------------------------- // raw types namespace _denc { template inline constexpr bool is_any_of = (... || std::is_same_v); template struct underlying_type { using type = T; }; template struct underlying_type>> { using type = std::underlying_type_t; }; template using underlying_type_t = typename underlying_type::type; } template struct is_const_iterator : std::conditional_t>, std::true_type, std::false_type> {}; template<> struct is_const_iterator : std::false_type {}; template<> struct is_const_iterator : std::false_type { // appender is used for *changing* the buffer }; template inline constexpr bool is_const_iterator_v = is_const_iterator::value; template std::enable_if_t, const T&> get_pos_add(It& i) { return *reinterpret_cast(i.get_pos_add(sizeof(T))); } template std::enable_if_t, T&> get_pos_add(It& i) { return *reinterpret_cast(i.get_pos_add(sizeof(T))); } template struct denc_traits< T, std::enable_if_t< _denc::is_any_of<_denc::underlying_type_t, ceph_le64, ceph_le32, ceph_le16, uint8_t #ifndef _CHAR_IS_SIGNED , int8_t #endif >>> { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = true; static constexpr bool need_contiguous = false; static void bound_encode(const T &o, size_t& p, uint64_t f=0) { p += sizeof(T); } template static std::enable_if_t> encode(const T &o, It& p, uint64_t f=0) { get_pos_add(p) = o; } template static std::enable_if_t> decode(T& o, It& p, uint64_t f=0) { o = get_pos_add(p); } static void decode(T& o, ceph::buffer::list::const_iterator &p) { p.copy(sizeof(T), reinterpret_cast(&o)); } }; // ----------------------------------------------------------------------- // integer types // itype == internal type // otype == external type, i.e., the type on the wire // NOTE: the overload resolution ensures that the legacy encode/decode methods // defined for int types is preferred to the ones defined using the specialized // template, and hence get selected. This machinery prevents these these from // getting glued into the legacy encode/decode methods; the overhead of setting // up a contiguous_appender etc is likely to be slower. namespace _denc { template struct ExtType { using type = void; }; template struct ExtType || std::is_same_v>> { using type = ceph_le16; }; template struct ExtType || std::is_same_v>> { using type = ceph_le32; }; template struct ExtType || std::is_same_v>> { using type = ceph_le64; }; template<> struct ExtType { using type = uint8_t; }; template using ExtType_t = typename ExtType::type; } // namespace _denc template struct denc_traits>>> { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = true; static constexpr bool need_contiguous = false; using etype = _denc::ExtType_t; static void bound_encode(const T &o, size_t& p, uint64_t f=0) { p += sizeof(etype); } template static std::enable_if_t> encode(const T &o, It& p, uint64_t f=0) { get_pos_add(p) = o; } template static std::enable_if_t> decode(T& o, It &p, uint64_t f=0) { o = get_pos_add(p); } static void decode(T& o, ceph::buffer::list::const_iterator &p) { etype e; p.copy(sizeof(etype), reinterpret_cast(&e)); o = e; } }; // varint // // high bit of each byte indicates another byte follows. template inline void denc_varint(T v, size_t& p) { p += sizeof(T) + 1; } template inline void denc_varint(T v, ceph::buffer::list::contiguous_appender& p) { uint8_t byte = v & 0x7f; v >>= 7; while (v) { byte |= 0x80; get_pos_add<__u8>(p) = byte; byte = (v & 0x7f); v >>= 7; } get_pos_add<__u8>(p) = byte; } template inline void denc_varint(T& v, ceph::buffer::ptr::const_iterator& p) { uint8_t byte = *(__u8*)p.get_pos_add(1); v = byte & 0x7f; int shift = 7; while (byte & 0x80) { byte = get_pos_add<__u8>(p); v |= (T)(byte & 0x7f) << shift; shift += 7; } } // signed varint encoding // // low bit = 1 = negative, 0 = positive // high bit of every byte indicates whether another byte follows. inline void denc_signed_varint(int64_t v, size_t& p) { p += sizeof(v) + 2; } template inline std::enable_if_t> denc_signed_varint(int64_t v, It& p) { if (v < 0) { v = (-v << 1) | 1; } else { v <<= 1; } denc_varint(v, p); } template inline std::enable_if_t> denc_signed_varint(T& v, It& p) { int64_t i = 0; denc_varint(i, p); if (i & 1) { v = -(i >> 1); } else { v = i >> 1; } } // varint + lowz encoding // // first(low) 2 bits = how many low zero bits (nibbles) // high bit of each byte = another byte follows // (so, 5 bits data in first byte, 7 bits data thereafter) inline void denc_varint_lowz(uint64_t v, size_t& p) { p += sizeof(v) + 2; } inline void denc_varint_lowz(uint64_t v, ceph::buffer::list::contiguous_appender& p) { int lowznib = v ? (ctz(v) / 4) : 0; if (lowznib > 3) lowznib = 3; v >>= lowznib * 4; v <<= 2; v |= lowznib; denc_varint(v, p); } template inline void denc_varint_lowz(T& v, ceph::buffer::ptr::const_iterator& p) { uint64_t i = 0; denc_varint(i, p); int lowznib = (i & 3); i >>= 2; i <<= lowznib * 4; v = i; } // signed varint + lowz encoding // // first low bit = 1 for negative, 0 for positive // next 2 bits = how many low zero bits (nibbles) // high bit of each byte = another byte follows // (so, 4 bits data in first byte, 7 bits data thereafter) inline void denc_signed_varint_lowz(int64_t v, size_t& p) { p += sizeof(v) + 2; } template inline std::enable_if_t> denc_signed_varint_lowz(int64_t v, It& p) { bool negative = false; if (v < 0) { v = -v; negative = true; } unsigned lowznib = v ? (ctz(v) / 4) : 0u; if (lowznib > 3) lowznib = 3; v >>= lowznib * 4; v <<= 3; v |= lowznib << 1; v |= (int)negative; denc_varint(v, p); } template inline std::enable_if_t> denc_signed_varint_lowz(T& v, It& p) { int64_t i = 0; denc_varint(i, p); int lowznib = (i & 6) >> 1; if (i & 1) { i >>= 3; i <<= lowznib * 4; v = -i; } else { i >>= 3; i <<= lowznib * 4; v = i; } } // LBA // // first 1-3 bits = how many low zero bits // *0 = 12 (common 4 K alignment case) // *01 = 16 // *011 = 20 // *111 = byte // then 28-30 bits of data // then last bit = another byte follows // high bit of each subsequent byte = another byte follows inline void denc_lba(uint64_t v, size_t& p) { p += sizeof(v) + 2; } template inline std::enable_if_t> denc_lba(uint64_t v, It& p) { int low_zero_nibbles = v ? (int)(ctz(v) / 4) : 0; int pos; uint32_t word; int t = low_zero_nibbles - 3; if (t < 0) { pos = 3; word = 0x7; } else if (t < 3) { v >>= (low_zero_nibbles * 4); pos = t + 1; word = (1 << t) - 1; } else { v >>= 20; pos = 3; word = 0x3; } word |= (v << pos) & 0x7fffffff; v >>= 31 - pos; if (!v) { *(ceph_le32*)p.get_pos_add(sizeof(uint32_t)) = word; return; } word |= 0x80000000; *(ceph_le32*)p.get_pos_add(sizeof(uint32_t)) = word; uint8_t byte = v & 0x7f; v >>= 7; while (v) { byte |= 0x80; *(__u8*)p.get_pos_add(1) = byte; byte = (v & 0x7f); v >>= 7; } *(__u8*)p.get_pos_add(1) = byte; } template inline std::enable_if_t> denc_lba(uint64_t& v, It& p) { uint32_t word = *(ceph_le32*)p.get_pos_add(sizeof(uint32_t)); int shift; switch (word & 7) { case 0: case 2: case 4: case 6: v = (uint64_t)(word & 0x7ffffffe) << (12 - 1); shift = 12 + 30; break; case 1: case 5: v = (uint64_t)(word & 0x7ffffffc) << (16 - 2); shift = 16 + 29; break; case 3: v = (uint64_t)(word & 0x7ffffff8) << (20 - 3); shift = 20 + 28; break; case 7: v = (uint64_t)(word & 0x7ffffff8) >> 3; shift = 28; } uint8_t byte = word >> 24; while (byte & 0x80) { byte = *(__u8*)p.get_pos_add(1); v |= (uint64_t)(byte & 0x7f) << shift; shift += 7; } } // --------------------------------------------------------------------- // denc top-level methods that call into denc_traits methods template> inline std::enable_if_t denc( const T& o, size_t& p, uint64_t f=0) { if constexpr (traits::featured) { traits::bound_encode(o, p, f); } else { traits::bound_encode(o, p); } } template> inline std::enable_if_t> denc(const T& o, It& p, uint64_t features=0) { if constexpr (traits::featured) { traits::encode(o, p, features); } else { traits::encode(o, p); } } template> inline std::enable_if_t> denc(T& o, It& p, uint64_t features=0) { if constexpr (traits::featured) { traits::decode(o, p, features); } else { traits::decode(o, p); } } namespace _denc { template struct has_legacy_denc : std::false_type {}; template struct has_legacy_denc() .decode(std::declval< ceph::buffer::list::const_iterator&>()))> : std::true_type { static void decode(T& v, ceph::buffer::list::const_iterator& p) { v.decode(p); } }; template struct has_legacy_denc::need_contiguous>> : std::true_type { static void decode(T& v, ceph::buffer::list::const_iterator& p) { denc_traits::decode(v, p); } }; } template, typename has_legacy_denc=_denc::has_legacy_denc> inline std::enable_if_t denc( T& o, ceph::buffer::list::const_iterator& p) { has_legacy_denc::decode(o, p); } // --------------------------------------------------------------------- // base types and containers // // std::string // template struct denc_traits,A>> { private: using value_type = std::basic_string,A>; public: static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = false; static constexpr bool need_contiguous = false; static void bound_encode(const value_type& s, size_t& p, uint64_t f=0) { p += sizeof(uint32_t) + s.size(); } template static void encode(const value_type& s, It& p, uint64_t f=0) { denc((uint32_t)s.size(), p); memcpy(p.get_pos_add(s.size()), s.data(), s.size()); } template static void decode(value_type& s, It& p, uint64_t f=0) { uint32_t len; denc(len, p); decode_nohead(len, s, p); } static void decode(value_type& s, ceph::buffer::list::const_iterator& p) { uint32_t len; denc(len, p); decode_nohead(len, s, p); } template static void decode_nohead(size_t len, value_type& s, It& p) { s.clear(); if (len) { s.append(p.get_pos_add(len), len); } } static void decode_nohead(size_t len, value_type& s, ceph::buffer::list::const_iterator& p) { if (len) { if constexpr (std::is_same_v) { s.clear(); p.copy(len, s); } else { s.resize(len); p.copy(len, s.data()); } } else { s.clear(); } } template static std::enable_if_t> encode_nohead(const value_type& s, It& p) { auto len = s.length(); maybe_inline_memcpy(p.get_pos_add(len), s.data(), len, 16); } }; // // ceph::buffer::ptr // template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = false; static constexpr bool need_contiguous = false; static void bound_encode(const ceph::buffer::ptr& v, size_t& p, uint64_t f=0) { p += sizeof(uint32_t) + v.length(); } template static std::enable_if_t> encode(const ceph::buffer::ptr& v, It& p, uint64_t f=0) { denc((uint32_t)v.length(), p); p.append(v); } template static std::enable_if_t> decode(ceph::buffer::ptr& v, It& p, uint64_t f=0) { uint32_t len; denc(len, p); v = p.get_ptr(len); } static void decode(ceph::buffer::ptr& v, ceph::buffer::list::const_iterator& p) { uint32_t len; denc(len, p); ceph::buffer::list s; p.copy(len, s); if (len) { if (s.get_num_buffers() == 1) v = s.front(); else v = ceph::buffer::copy(s.c_str(), s.length()); } } }; // // ceph::buffer::list // template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = false; static constexpr bool need_contiguous = false; static void bound_encode(const ceph::buffer::list& v, size_t& p, uint64_t f=0) { p += sizeof(uint32_t) + v.length(); } static void encode(const ceph::buffer::list& v, ceph::buffer::list::contiguous_appender& p, uint64_t f=0) { denc((uint32_t)v.length(), p); p.append(v); } static void decode(ceph::buffer::list& v, ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { uint32_t len; denc(len, p); v.clear(); v.push_back(p.get_ptr(len)); } static void decode(ceph::buffer::list& v, ceph::buffer::list::const_iterator& p) { uint32_t len; denc(len, p); v.clear(); p.copy(len, v); } static void encode_nohead(const ceph::buffer::list& v, ceph::buffer::list::contiguous_appender& p) { p.append(v); } static void decode_nohead(size_t len, ceph::buffer::list& v, ceph::buffer::ptr::const_iterator& p) { v.clear(); if (len) { v.append(p.get_ptr(len)); } } static void decode_nohead(size_t len, ceph::buffer::list& v, ceph::buffer::list::const_iterator& p) { v.clear(); p.copy(len, v); } }; // // std::pair // template struct denc_traits< std::pair, std::enable_if_t> && denc_supported>> { typedef denc_traits a_traits; typedef denc_traits b_traits; static constexpr bool supported = true; static constexpr bool featured = a_traits::featured || b_traits::featured ; static constexpr bool bounded = a_traits::bounded && b_traits::bounded; static constexpr bool need_contiguous = (a_traits::need_contiguous || b_traits::need_contiguous); static void bound_encode(const std::pair& v, size_t& p, uint64_t f = 0) { if constexpr (featured) { denc(v.first, p, f); denc(v.second, p, f); } else { denc(v.first, p); denc(v.second, p); } } static void encode(const std::pair& v, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { if constexpr (featured) { denc(v.first, p, f); denc(v.second, p, f); } else { denc(v.first, p); denc(v.second, p); } } static void decode(std::pair& v, ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { denc(const_cast&>(v.first), p, f); denc(v.second, p, f); } template static std::enable_if_t decode(std::pair& v, ceph::buffer::list::const_iterator& p, uint64_t f = 0) { denc(const_cast&>(v.first), p); denc(v.second, p); } }; namespace _denc { template class C, typename Details, typename ...Ts> struct container_base { private: using container = C; using T = typename Details::T; public: using traits = denc_traits; static constexpr bool supported = true; static constexpr bool featured = traits::featured; static constexpr bool bounded = false; static constexpr bool need_contiguous = traits::need_contiguous; template static void bound_encode(const container& s, size_t& p, uint64_t f = 0) { p += sizeof(uint32_t); if constexpr (traits::bounded) { #if _GLIBCXX_USE_CXX11_ABI // intensionally not calling container's empty() method to not prohibit // compiler from optimizing the check if it and the ::size() operate on // different memory (observed when std::list::empty() works on pointers, // not the size field). if (const auto elem_num = s.size(); elem_num > 0) { #else if (!s.empty()) { const auto elem_num = s.size(); #endif // STL containers use weird element types like std::pair; // cast to something we have denc_traits for. size_t elem_size = 0; if constexpr (traits::featured) { denc(static_cast(*s.begin()), elem_size, f); } else { denc(static_cast(*s.begin()), elem_size); } p += elem_size * elem_num; } } else { for (const T& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } } template static void encode(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { denc((uint32_t)s.size(), p); if constexpr (traits::featured) { encode_nohead(s, p, f); } else { encode_nohead(s, p); } } static void decode(container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { uint32_t num; denc(num, p); decode_nohead(num, s, p, f); } template static std::enable_if_t decode(container& s, ceph::buffer::list::const_iterator& p) { uint32_t num; denc(num, p); decode_nohead(num, s, p); } // nohead static void encode_nohead(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { for (const T& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } static void decode_nohead(size_t num, container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { s.clear(); Details::reserve(s, num); while (num--) { T t; denc(t, p, f); Details::insert(s, std::move(t)); } } template static std::enable_if_t decode_nohead(size_t num, container& s, ceph::buffer::list::const_iterator& p) { s.clear(); Details::reserve(s, num); while (num--) { T t; denc(t, p); Details::insert(s, std::move(t)); } } }; template class container_has_reserve { template struct SFINAE_match; template static std::true_type test(SFINAE_match*); template static std::false_type test(...); public: static constexpr bool value = decltype( test>(0))::value; }; template inline constexpr bool container_has_reserve_v = container_has_reserve::value; template struct container_details_base { using T = typename Container::value_type; static void reserve(Container& c, size_t s) { if constexpr (container_has_reserve_v) { c.reserve(s); } } }; template struct pushback_details : public container_details_base { template static void insert(Container& c, Args&& ...args) { c.emplace_back(std::forward(args)...); } }; } template struct denc_traits< std::list, typename std::enable_if_t::supported>> : public _denc::container_base>, T, Ts...> {}; template struct denc_traits< std::vector, typename std::enable_if_t::supported>> : public _denc::container_base>, T, Ts...> {}; template struct denc_traits< boost::container::small_vector, typename std::enable_if_t::supported>> { private: using container = boost::container::small_vector; public: using traits = denc_traits; static constexpr bool supported = true; static constexpr bool featured = traits::featured; static constexpr bool bounded = false; static constexpr bool need_contiguous = traits::need_contiguous; template static void bound_encode(const container& s, size_t& p, uint64_t f = 0) { p += sizeof(uint32_t); if constexpr (traits::bounded) { if (!s.empty()) { const auto elem_num = s.size(); size_t elem_size = 0; if constexpr (traits::featured) { denc(*s.begin(), elem_size, f); } else { denc(*s.begin(), elem_size); } p += elem_size * elem_num; } } else { for (const T& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } } template static void encode(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { denc((uint32_t)s.size(), p); if constexpr (traits::featured) { encode_nohead(s, p, f); } else { encode_nohead(s, p); } } static void decode(container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { uint32_t num; denc(num, p); decode_nohead(num, s, p, f); } template static std::enable_if_t decode(container& s, ceph::buffer::list::const_iterator& p) { uint32_t num; denc(num, p); decode_nohead(num, s, p); } // nohead static void encode_nohead(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { for (const T& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } static void decode_nohead(size_t num, container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { s.clear(); s.reserve(num); while (num--) { T t; denc(t, p, f); s.push_back(std::move(t)); } } template static std::enable_if_t decode_nohead(size_t num, container& s, ceph::buffer::list::const_iterator& p) { s.clear(); s.reserve(num); while (num--) { T t; denc(t, p); s.push_back(std::move(t)); } } }; namespace _denc { template struct setlike_details : public container_details_base { using T = typename Container::value_type; template static void insert(Container& c, Args&& ...args) { c.emplace_hint(c.cend(), std::forward(args)...); } }; } template struct denc_traits< std::set, std::enable_if_t::supported>> : public _denc::container_base>, T, Ts...> {}; template struct denc_traits< boost::container::flat_set, std::enable_if_t::supported>> : public _denc::container_base< boost::container::flat_set, _denc::setlike_details>, T, Ts...> {}; namespace _denc { template struct maplike_details : public container_details_base { using T = typename Container::value_type; template static void insert(Container& c, Args&& ...args) { c.emplace_hint(c.cend(), std::forward(args)...); } }; } template struct denc_traits< std::map, std::enable_if_t::supported && denc_traits::supported>> : public _denc::container_base>, A, B, Ts...> {}; template struct denc_traits< boost::container::flat_map, std::enable_if_t::supported && denc_traits::supported>> : public _denc::container_base< boost::container::flat_map, _denc::maplike_details>, A, B, Ts...> {}; template struct denc_traits< std::array, std::enable_if_t::supported>> { private: using container = std::array; public: using traits = denc_traits; static constexpr bool supported = true; static constexpr bool featured = traits::featured; static constexpr bool bounded = traits::bounded; static constexpr bool need_contiguous = traits::need_contiguous; static void bound_encode(const container& s, size_t& p, uint64_t f = 0) { if constexpr (traits::bounded) { if constexpr (traits::featured) { if (!s.empty()) { size_t elem_size = 0; denc(*s.begin(), elem_size, f); p += elem_size * s.size(); } } else { size_t elem_size = 0; denc(*s.begin(), elem_size); p += elem_size * N; } } else { for (const auto& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } } static void encode(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { for (const auto& e : s) { if constexpr (traits::featured) { denc(e, p, f); } else { denc(e, p); } } } static void decode(container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { for (auto& e : s) denc(e, p, f); } template static std::enable_if_t decode(container& s, ceph::buffer::list::const_iterator& p) { for (auto& e : s) { denc(e, p); } } }; template struct denc_traits< std::tuple, std::enable_if_t<(denc_traits::supported && ...)>> { private: static_assert(sizeof...(Ts) > 0, "Zero-length tuples are not supported."); using container = std::tuple; public: static constexpr bool supported = true; static constexpr bool featured = (denc_traits::featured || ...); static constexpr bool bounded = (denc_traits::bounded && ...); static constexpr bool need_contiguous = (denc_traits::need_contiguous || ...); template static std::enable_if_t::featured> bound_encode(const container& s, size_t& p, uint64_t f) { ceph::for_each(s, [&p, f] (const auto& e) { if constexpr (denc_traits>::featured) { denc(e, p, f); } else { denc(e, p); } }); } template static std::enable_if_t::featured> bound_encode(const container& s, size_t& p) { ceph::for_each(s, [&p] (const auto& e) { denc(e, p); }); } template static std::enable_if_t::featured> encode(const container& s, ceph::buffer::list::contiguous_appender& p, uint64_t f) { ceph::for_each(s, [&p, f] (const auto& e) { if constexpr (denc_traits>::featured) { denc(e, p, f); } else { denc(e, p); } }); } template static std::enable_if_t::featured> encode(const container& s, ceph::buffer::list::contiguous_appender& p) { ceph::for_each(s, [&p] (const auto& e) { denc(e, p); }); } static void decode(container& s, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { ceph::for_each(s, [&p] (auto& e) { denc(e, p); }); } template static std::enable_if_t::need_contiguous> decode(container& s, ceph::buffer::list::const_iterator& p, uint64_t f = 0) { ceph::for_each(s, [&p] (auto& e) { denc(e, p); }); } }; // // boost::optional // template struct denc_traits< boost::optional, std::enable_if_t::supported>> { using traits = denc_traits; static constexpr bool supported = true; static constexpr bool featured = traits::featured; static constexpr bool bounded = false; static constexpr bool need_contiguous = traits::need_contiguous; static void bound_encode(const boost::optional& v, size_t& p, uint64_t f = 0) { p += sizeof(bool); if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void encode(const boost::optional& v, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { denc((bool)v, p); if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void decode(boost::optional& v, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { bool x; denc(x, p, f); if (x) { v = T{}; denc(*v, p, f); } else { v = boost::none; } } template static std::enable_if_t decode(boost::optional& v, ceph::buffer::list::const_iterator& p) { bool x; denc(x, p); if (x) { v = T{}; denc(*v, p); } else { v = boost::none; } } template static void encode_nohead(const boost::optional& v, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void decode_nohead(bool num, boost::optional& v, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { if (num) { v = T(); denc(*v, p, f); } else { v = boost::none; } } }; template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = true; static constexpr bool need_contiguous = false; static void bound_encode(const boost::none_t& v, size_t& p) { p += sizeof(bool); } static void encode(const boost::none_t& v, ceph::buffer::list::contiguous_appender& p) { denc(false, p); } }; // // std::optional // template struct denc_traits< std::optional, std::enable_if_t::supported>> { using traits = denc_traits; static constexpr bool supported = true; static constexpr bool featured = traits::featured; static constexpr bool bounded = false; static constexpr bool need_contiguous = traits::need_contiguous; static void bound_encode(const std::optional& v, size_t& p, uint64_t f = 0) { p += sizeof(bool); if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void encode(const std::optional& v, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { denc((bool)v, p); if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void decode(std::optional& v, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { bool x; denc(x, p, f); if (x) { v = T{}; denc(*v, p, f); } else { v = std::nullopt; } } template static std::enable_if_t decode(std::optional& v, ceph::buffer::list::const_iterator& p) { bool x; denc(x, p); if (x) { v = T{}; denc(*v, p); } else { v = std::nullopt; } } static void encode_nohead(const std::optional& v, ceph::buffer::list::contiguous_appender& p, uint64_t f = 0) { if (v) { if constexpr (featured) { denc(*v, p, f); } else { denc(*v, p); } } } static void decode_nohead(bool num, std::optional& v, ceph::buffer::ptr::const_iterator& p, uint64_t f = 0) { if (num) { v = T(); denc(*v, p, f); } else { v = std::nullopt; } } }; template<> struct denc_traits { static constexpr bool supported = true; static constexpr bool featured = false; static constexpr bool bounded = true; static constexpr bool need_contiguous = false; static void bound_encode(const std::nullopt_t& v, size_t& p) { p += sizeof(bool); } static void encode(const std::nullopt_t& v, ceph::buffer::list::contiguous_appender& p) { denc(false, p); } }; // ---------------------------------------------------------------------- // class helpers // Write denc_traits<> for a class that defines bound_encode/encode/decode // methods. #define WRITE_CLASS_DENC(T) _DECLARE_CLASS_DENC(T, false) #define WRITE_CLASS_DENC_BOUNDED(T) _DECLARE_CLASS_DENC(T, true) #define _DECLARE_CLASS_DENC(T, b) \ template<> struct denc_traits { \ static constexpr bool supported = true; \ static constexpr bool featured = false; \ static constexpr bool bounded = b; \ static constexpr bool need_contiguous = !_denc::has_legacy_denc::value;\ static void bound_encode(const T& v, size_t& p, uint64_t f=0) { \ v.bound_encode(p); \ } \ static void encode(const T& v, ::ceph::buffer::list::contiguous_appender& p, \ uint64_t f=0) { \ v.encode(p); \ } \ static void decode(T& v, ::ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { \ v.decode(p); \ } \ }; #define WRITE_CLASS_DENC_FEATURED(T) _DECLARE_CLASS_DENC_FEATURED(T, false) #define WRITE_CLASS_DENC_FEATURED_BOUNDED(T) _DECLARE_CLASS_DENC_FEATURED(T, true) #define _DECLARE_CLASS_DENC_FEATURED(T, b) \ template<> struct denc_traits { \ static constexpr bool supported = true; \ static constexpr bool featured = true; \ static constexpr bool bounded = b; \ static constexpr bool need_contiguous = !_denc::has_legacy_denc::value;\ static void bound_encode(const T& v, size_t& p, uint64_t f) { \ v.bound_encode(p, f); \ } \ static void encode(const T& v, ::ceph::buffer::list::contiguous_appender& p, \ uint64_t f) { \ v.encode(p, f); \ } \ static void decode(T& v, ::ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { \ v.decode(p, f); \ } \ }; // ---------------------------------------------------------------------- // encoded_sizeof_wrapper namespace ceph { template > constexpr std::enable_if_t encoded_sizeof_bounded() { size_t p = 0; traits::bound_encode(T(), p); return p; } template > std::enable_if_t encoded_sizeof(const T &t) { size_t p = 0; traits::bound_encode(t, p); return p; } } // namespace ceph // ---------------------------------------------------------------------- // encode/decode wrappers // These glue the new-style denc world into old-style calls to encode // and decode by calling into denc_traits<> methods (when present). namespace ceph { template> inline std::enable_if_t encode( const T& o, ceph::buffer::list& bl, uint64_t features_unused=0) { size_t len = 0; traits::bound_encode(o, len); auto a = bl.get_contiguous_appender(len); traits::encode(o, a); } template> inline std::enable_if_t encode( const T& o, ::ceph::buffer::list& bl, uint64_t features) { size_t len = 0; traits::bound_encode(o, len, features); auto a = bl.get_contiguous_appender(len); traits::encode(o, a, features); } template> inline std::enable_if_t decode( T& o, ::ceph::buffer::list::const_iterator& p) { if (p.end()) throw ::ceph::buffer::end_of_buffer(); const auto& bl = p.get_bl(); const auto remaining = bl.length() - p.get_off(); // it is expensive to rebuild a contigous buffer and drop it, so avoid this. if (!p.is_pointing_same_raw(bl.back()) && remaining > CEPH_PAGE_SIZE) { traits::decode(o, p); } else { // ensure we get a contigous buffer... until the end of the // ceph::buffer::list. we don't really know how much we'll need here, // unfortunately. hopefully it is already contiguous and we're just // bumping the raw ref and initializing the ptr tmp fields. ceph::buffer::ptr tmp; auto t = p; t.copy_shallow(remaining, tmp); auto cp = std::cbegin(tmp); traits::decode(o, cp); p += cp.get_offset(); } } template> inline std::enable_if_t decode( T& o, ceph::buffer::list::const_iterator& p) { if (p.end()) throw ceph::buffer::end_of_buffer(); // ensure we get a contigous buffer... until the end of the // ceph::buffer::list. we don't really know how much we'll need here, // unfortunately. hopefully it is already contiguous and we're just // bumping the raw ref and initializing the ptr tmp fields. ceph::buffer::ptr tmp; auto t = p; t.copy_shallow(p.get_bl().length() - p.get_off(), tmp); auto cp = std::cbegin(tmp); traits::decode(o, cp); p += cp.get_offset(); } // nohead variants template> inline std::enable_if_t encode_nohead( const T& o, ceph::buffer::list& bl) { size_t len = 0; traits::bound_encode(o, len); auto a = bl.get_contiguous_appender(len); traits::encode_nohead(o, a); } template> inline std::enable_if_t decode_nohead( size_t num, T& o, ceph::buffer::list::const_iterator& p) { if (!num) return; if (p.end()) throw ceph::buffer::end_of_buffer(); if constexpr (traits::need_contiguous) { ceph::buffer::ptr tmp; auto t = p; if constexpr (denc_traits::bounded) { size_t element_size = 0; typename T::value_type v; denc_traits::bound_encode(v, element_size); t.copy_shallow(num * element_size, tmp); } else { t.copy_shallow(p.get_bl().length() - p.get_off(), tmp); } auto cp = std::cbegin(tmp); traits::decode_nohead(num, o, cp); p += cp.get_offset(); } else { traits::decode_nohead(num, o, p); } } } // ---------------------------------------------------------------- // DENC // These are some class methods we need to do the version and length // wrappers for DENC_{START,FINISH} for inter-version // interoperability. #define DENC_HELPERS \ /* bound_encode */ \ static void _denc_start(size_t& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ char **, uint32_t *) { \ p += 2 + 4; \ } \ static void _denc_finish(size_t& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ char **, uint32_t *) { } \ /* 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(); \ } \ static void _denc_finish(::ceph::buffer::list::contiguous_appender& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ 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; \ } \ /* decode */ \ static void _denc_start(::ceph::buffer::ptr::const_iterator& p, \ __u8 *struct_v, \ __u8 *struct_compat, \ char **start_pos, \ uint32_t *struct_len) { \ denc(*struct_v, p); \ denc(*struct_compat, p); \ denc(*struct_len, p); \ *start_pos = const_cast(p.get_pos()); \ } \ static void _denc_finish(::ceph::buffer::ptr::const_iterator& p, \ __u8 *struct_v, __u8 *struct_compat, \ 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; \ } \ } // Helpers for versioning the encoding. These correspond to the // {ENCODE,DECODE}_{START,FINISH} macros. #define DENC_START(v, compat, p) \ __u8 struct_v = v; \ __u8 struct_compat = compat; \ char *_denc_pchar; \ uint32_t _denc_u32; \ _denc_start(p, &struct_v, &struct_compat, &_denc_pchar, &_denc_u32); \ do { #define DENC_FINISH(p) \ } while (false); \ _denc_finish(p, &struct_v, &struct_compat, &_denc_pchar, &_denc_u32); // ---------------------------------------------------------------------- // Helpers for writing a unified bound_encode/encode/decode // implementation that won't screw up buffer size estimations. #define DENC(Type, v, p) \ DENC_HELPERS \ void bound_encode(size_t& p) const { \ _denc_friend(*this, p); \ } \ void encode(::ceph::buffer::list::contiguous_appender& p) const { \ DENC_DUMP_PRE(Type); \ _denc_friend(*this, p); \ } \ void decode(::ceph::buffer::ptr::const_iterator& p) { \ _denc_friend(*this, p); \ } \ template \ friend std::enable_if_t || \ std::is_same_v> \ _denc_friend(T& v, P& p) #define DENC_FEATURED(Type, v, p, f) \ DENC_HELPERS \ void bound_encode(size_t& p, uint64_t f) const { \ _denc_friend(*this, p, f); \ } \ void encode(::ceph::buffer::list::contiguous_appender& p, uint64_t f) const { \ DENC_DUMP_PRE(Type); \ _denc_friend(*this, p, f); \ } \ void decode(::ceph::buffer::ptr::const_iterator& p, uint64_t f=0) { \ _denc_friend(*this, p, f); \ } \ template \ friend std::enable_if_t || \ std::is_same_v> \ _denc_friend(T& v, P& p, uint64_t f) #endif