]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
RGW - Add POSIX Driver
authorDaniel Gryniewicz <dang@redhat.com>
Tue, 24 Jan 2023 15:02:07 +0000 (10:02 -0500)
committerDaniel Gryniewicz <dang@redhat.com>
Thu, 14 Sep 2023 16:09:40 +0000 (12:09 -0400)
This is the MVP for a driver for RGW that operates on top of a POSIX
filesystem.  It supports get, put, list, copy, multipart, external
access via the filesystem itself, and ordered bucket listings via an
LRU-based cache.

Note that this is currently a Filter, indended to run on top of dbstore.
This is because it currently doesn't have any User implementation, so it
depends on dbstore's User.  Everything else is implemented in
POSIXDriver.  Once there is a User implementation, this will become a
Store, instead of a Filter.

Commit messages from bucket listing cache:

  rgw/posixdriver: recycle lmdb database handles as required

    While LMDB workflows often do not close/return database handles,
    ours continually reuses them.  This requires us to close each
    handle (atomically) when a cache entry is recycled.

  rgw/posixdriver: don't instantiate bucket cache entries from notify events

  rgw/posixdriver: incorporate lmdb-safe for now

    The current inclusion is based on https://github.com/Martchus/lmdb-safe,
    which is actively maintained but currently has some packaging issues the
    author has agreed to accept fixes for.

    For now, skip the submodule to save time and remove an external dependency.

  rgw/posixdriver: fix listing of cached, empty bucket

    * check lmdb enumeration result in all cases and w/better style
    * add unit test for enumeration of an empty cached directory

  rgw/posixdriver: nest lmdbs in a directory under the dbroot path to avoid cleanup issues

  rgw/posixdriver: refactor for posix integration

    * Derive BucketCache types as templates on a SAL driver and SAL
      bucket pair.

    * Integrate cache fills as callbacks into SAL layer (or mock, for
      tests)

    * Renaming and cleanups

  rgw/posixdriver: add bucket cache implementation and tests

    Adds free-standing cache of buckets and object names, with
    bucket names (and listing attributes, upcoming) managed in
    a hashed set of lmdb databases, which provides ordering and
    a high-performance listing cache.

    An framework for notification on new object creation (e.g.,
    outside S3 workflow) is provided, and a Linux implementation
    using inotify.

    FindLMDB.cmake taken with attribution and license.

  rgw/posixdriver: add zpp_bits serialization (FAST)

Signed-off-by: Daniel Gryniewicz <dang@redhat.com>
Signed-off-by: Ali Maredia <amaredia@redhat.com>
Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
30 files changed:
CMakeLists.txt
ceph.spec.in
cmake/modules/FindLMDB.cmake [new file with mode: 0644]
debian/control
src/common/cohort_lru.h
src/common/options/rgw.yaml.in
src/include/config-h.in.cmake
src/rgw/CMakeLists.txt
src/rgw/driver/posix/README.md [new file with mode: 0644]
src/rgw/driver/posix/bucket_cache.cpp [new file with mode: 0644]
src/rgw/driver/posix/bucket_cache.h [new file with mode: 0644]
src/rgw/driver/posix/lmdb-safe-global.h [new file with mode: 0644]
src/rgw/driver/posix/lmdb-safe.cc [new file with mode: 0644]
src/rgw/driver/posix/lmdb-safe.hh [new file with mode: 0644]
src/rgw/driver/posix/notify.cpp [new file with mode: 0644]
src/rgw/driver/posix/notify.h [new file with mode: 0644]
src/rgw/driver/posix/rgw_sal_posix.cc [new file with mode: 0644]
src/rgw/driver/posix/rgw_sal_posix.h [new file with mode: 0644]
src/rgw/driver/posix/unordered_dense.h [new file with mode: 0644]
src/rgw/driver/posix/zpp_bits.h [new file with mode: 0644]
src/rgw/driver/rados/rgw_sal_rados.cc
src/rgw/driver/rados/rgw_sal_rados.h
src/rgw/rgw_op.cc
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_sal.cc
src/rgw/rgw_sal_dbstore.cc
src/rgw/rgw_sal_dbstore.h
src/rgw/rgw_sal_store.h
src/test/rgw/CMakeLists.txt
src/test/rgw/test_posix_bucket_cache.cc [new file with mode: 0644]

index 78881cb0188d20ecfb30ac5552ba93919c65d583..2903e872c9e597585b58ab1ed126632582c7bb6f 100644 (file)
@@ -450,6 +450,7 @@ option(WITH_RADOSGW_DBSTORE "DBStore backend for RADOS Gateway" ON)
 option(WITH_RADOSGW_MOTR "CORTX-Motr backend for RADOS Gateway" OFF)
 option(WITH_RADOSGW_DAOS "DAOS backend for RADOS Gateway" OFF)
 option(WITH_RADOSGW_D4N "D4N wrapper for RADOS Gateway" ON)
+option(WITH_RADOSGW_POSIX "POSIX backend for Rados Gateway" ON)
 option(WITH_RADOSGW_SELECT_PARQUET "Support for s3 select on parquet objects" ON)
 option(WITH_RADOSGW_ARROW_FLIGHT "Build arrow flight when not using system-provided arrow" OFF)
 option(WITH_RADOSGW_BACKTRACE_LOGGING "Enable backtraces in rgw logs" OFF)
index 1fc998d014db4a1b4ee4ad1d83ad92ca511a58df..57ad8c4fdc543dfe1e587dc9d5242414c1b39a3f 100644 (file)
@@ -270,6 +270,7 @@ BuildRequires:      xfsprogs-devel
 BuildRequires: xmlstarlet
 BuildRequires: nasm
 BuildRequires: lua-devel
+BuildRequires:  lmdb-devel
 %if 0%{with seastar} || 0%{with jaeger}
 BuildRequires:  yaml-cpp-devel >= 0.6
 %endif
diff --git a/cmake/modules/FindLMDB.cmake b/cmake/modules/FindLMDB.cmake
new file mode 100644 (file)
index 0000000..743cc48
--- /dev/null
@@ -0,0 +1,61 @@
+# Copyright (c) 2014, The Monero Project
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+# 
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+#    conditions and the following disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+#    of conditions and the following disclaimer in the documentation and/or other
+#    materials provided with the distribution.
+# 
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+#    used to endorse or promote products derived from this software without specific
+#    prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+MESSAGE(STATUS "Looking for liblmdb")
+
+FIND_PATH(LMDB_INCLUDE_DIR
+  NAMES lmdb.h
+  PATH_SUFFIXES include/ include/lmdb/
+  PATHS "${PROJECT_SOURCE_DIR}"
+  ${LMDB_ROOT}
+  $ENV{LMDB_ROOT}
+  /usr/local/
+  /usr/
+)
+
+if(STATIC)
+  if(MINGW)
+    find_library(LMDB_LIBRARIES liblmdb.dll.a)
+  else()
+    find_library(LMDB_LIBRARIES liblmdb.a)
+  endif()
+else()
+  find_library(LMDB_LIBRARIES lmdb)
+endif()
+
+IF(LMDB_INCLUDE_DIR)
+  MESSAGE(STATUS "Found liblmdb include (lmdb.h) in ${LMDB_INCLUDE_DIR}")
+  IF(LMDB_LIBRARIES)
+    MESSAGE(STATUS "Found liblmdb library")
+    set(LMDB_INCLUDE ${LMDB_INCLUDE_DIR})
+    set(LMDB_LIBRARY ${LMDB_LIBRARIES})
+  ELSE()
+    MESSAGE(FATAL_ERROR "${BoldRed}Could not find liblmdb library, please make sure you have installed liblmdb or liblmdb-dev or the equivalent${ColourReset}")
+  ENDIF()
+ELSE()
+  MESSAGE(FATAL_ERROR "${BoldRed}Could not find liblmdb library, please make sure you have installed liblmdb or liblmdb-dev or the equivalent${ColourReset}")
+ENDIF()
index b17eb347d9a309ef6ca4a1a234a07840d5f21edd..476556685357ea6c753b4dc83ba874100c3d9c41 100644 (file)
@@ -65,6 +65,7 @@ Build-Depends: automake,
                libsqlite3-dev,
                libssl-dev,
                libtool,
+               liblmdb-dev,
                libudev-dev,
                libnl-genl-3-dev,
                libxml2-dev,
index b105c80ccd5376dca4a62f0958b45136968ae71c..af2baaa5c67bf061e480386bf2fdc00af8d21ea6 100644 (file)
 #include <boost/intrusive/list.hpp>
 #include <boost/intrusive/slist.hpp>
 
+#ifdef __CEPH__
+# include "include/ceph_assert.h"
+#else
+# include <assert.h>
+#endif
+
 #include "common/likely.h"
 
 #ifndef CACHE_LINE_SIZE
index 8928e853e64d5f422707157dd091e92d686ecd8d..dad1906a72cd219ce0dddfd7399b7669e62f0ae6 100644 (file)
@@ -3618,6 +3618,7 @@ options:
   - none
   - base
   - d4n
+  - posix
 - name: dbstore_db_dir
   type: str
   level: advanced
@@ -3705,6 +3706,51 @@ options:
   default: false
   services:
   - rgw
+- name: rgw_posix_base_path
+  type: str
+  level: advanced
+  desc: experimental Option to set base path for POSIX Driver
+  long_desc: Base path for the POSIX driver.  All operations are relative to this path.
+    Defaults to /tmp/rgw_posix_driver
+  default: /tmp/rgw_posix_driver
+  services:
+  - rgw
+- name: rgw_posix_database_root
+  type: str
+  level: advanced
+  desc: experimental Path to parent of POSIX Driver LMDB bucket listing cache
+  long_desc: Parent directory of LMDB bucket listing cache databases.
+  default: /var/lib/ceph/radosgw
+  services:
+  - rgw
+- name: rgw_posix_cache_max_buckets
+  type: int
+  level: advanced
+  desc: experimental Number of buckets to maintain in the ordered listing cache
+  default: 100
+  services:
+  - rgw
+- name: rgw_posix_cache_lanes
+  type: int
+  level: advanced
+  desc: experimental Number of lanes in cache LRU
+  default: 3
+  services:
+  - rgw
+- name: rgw_posix_cache_partitions
+  type: int
+  level: advanced
+  desc: experimental Number of partitions in cache AVL
+  default: 3
+  services:
+  - rgw
+- name: rgw_posix_cache_lmdb_count
+  type: int
+  level: advanced
+  desc: experimental Number of lmdb partitions in the ordered listing cache
+  default: 3
+  services:
+  - rgw
 - name: rgw_luarocks_location
   type: str
   level: advanced
index 6b2ac58a351914ca4e12427532787e85019fa954..f14a1f43a6005f7cbe6089ff03aaf0539064ca95 100644 (file)
 /* Backend CORTX-DAOS for Rados Gateway */
 #cmakedefine WITH_RADOSGW_DAOS
 
+/* Backend POSIX for Rados Gateway */
+#cmakedefine WITH_RADOSGW_POSIX
+
 /* Defined if std::map::merge() is supported */
 #cmakedefine HAVE_STDLIB_MAP_SPLICING
 
index 79bc05a4df13aa7d0ed70560cb281cc1ee733c49..6481970b2a9aec83a16937b213a90efa419b66b2 100644 (file)
@@ -227,6 +227,15 @@ if(WITH_RADOSGW_D4N)
   list(APPEND librgw_common_srcs driver/d4n/d4n_datacache.cc)
   list(APPEND librgw_common_srcs driver/d4n/rgw_sal_d4n.cc)
 endif()
+if(WITH_RADOSGW_POSIX)
+  #add_subdirectory(driver/posix)
+  find_package(LMDB REQUIRED)
+  add_compile_definitions(LMDB_SAFE_NO_CPP_UTILITIES)
+  list(APPEND librgw_common_srcs
+             driver/posix/rgw_sal_posix.cc
+             driver/posix/lmdb-safe.cc
+             driver/posix/notify.cpp)
+endif()
 if(WITH_JAEGER)
   list(APPEND librgw_common_srcs rgw_tracer.cc)
 endif()
@@ -236,7 +245,6 @@ if(WITH_RADOSGW_ARROW_FLIGHT)
   list(APPEND librgw_common_srcs rgw_flight.cc rgw_flight_frontend.cc)
 endif(WITH_RADOSGW_ARROW_FLIGHT)
 
-
 add_library(rgw_common STATIC ${librgw_common_srcs})
 
 include(CheckCXXCompilerFlag)
@@ -270,6 +278,7 @@ target_link_libraries(rgw_common
     ${EXPAT_LIBRARIES}
     ${ARROW_LIBRARIES}
     ${ARROW_FLIGHT_LIBRARIES}
+    ${LMDB_LIBRARIES}
     ${ALLOC_LIBS}
   PUBLIC
     ${LUA_LIBRARIES}
diff --git a/src/rgw/driver/posix/README.md b/src/rgw/driver/posix/README.md
new file mode 100644 (file)
index 0000000..02dc8df
--- /dev/null
@@ -0,0 +1,37 @@
+# POSIX Driver
+Standalone Rados Gateway (RGW) on a local POSIX filesystem (Experimental)
+
+
+## CMake Option
+Add below cmake option (enabled by default)
+
+    -DWITH_RADOSGW_POSIX=ON 
+
+
+## Build
+
+    cd build
+    ninja [vstart]
+
+
+## Running Test cluster
+Currently, POSIXDriver depends on DBStore for user storage.  This will change, eventually, but for now, it's run as a filter on top of DBStore.  Not that only users are stored in DBStore, the rest is in the POSIX filesystem.
+Edit ceph.conf to add below option
+
+    [client]
+        rgw backend store = dbstore
+        rgw config store = dbstore
+        rgw filter = posix
+
+Start vstart cluster
+
+    MON=0 OSD=0 MDS=0 MGR=0 RGW=1 ../src/vstart.sh -o rgw_backend_store=dbstore -o rgw_config_store=dbstore -o rgw_filter=posix -n -d
+
+The above vstart command brings up RGW server on POSIXDriver. It creates default zonegroup, zone and few default users (eg., testid) to be used for s3 operations.
+
+`radosgw-admin` can be used to create and remove other users, zonegroups and zones.
+
+By default, the directory exported is *'/tmp/rgw_posix_driver'*.   This can be changed with the `rgw_posix_base_path` option, either in ceph.conf or on the vstart command line above.
+
+The POSIXDriver keeps a LMDB based cache of directories, so that it can provide ordered listings.  This directory lives in `rgw_posix_database_root`, which by default is in *'/var/lib/ceph/radosgw'*
+
diff --git a/src/rgw/driver/posix/bucket_cache.cpp b/src/rgw/driver/posix/bucket_cache.cpp
new file mode 100644 (file)
index 0000000..9df08e4
--- /dev/null
@@ -0,0 +1,4 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+#include "bucket_cache.h"
+
diff --git a/src/rgw/driver/posix/bucket_cache.h b/src/rgw/driver/posix/bucket_cache.h
new file mode 100644 (file)
index 0000000..3cbca7c
--- /dev/null
@@ -0,0 +1,549 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+#pragma once
+
+#include <iostream>
+#include <lmdb.h>
+#include <memory>
+#include <tuple>
+#include <vector>
+#include <string>
+#include <string_view>
+#include <mutex>
+#include <shared_mutex>
+#include <condition_variable>
+#include <filesystem>
+#include <boost/intrusive/avl_set.hpp>
+#include "include/function2.hpp"
+#include "common/cohort_lru.h"
+#include "lmdb-safe.hh"
+#include "zpp_bits.h"
+#include "notify.h"
+#include <stdint.h>
+#include <time.h> // struct timespec
+#include <xxhash.h>
+
+#include "rgw_common.h"
+#include "rgw_sal.h"
+
+#include "fmt/format.h"
+
+namespace file::listing {
+
+namespace bi = boost::intrusive;
+namespace sf = std::filesystem; 
+
+typedef bi::link_mode<bi::safe_link> link_mode; /* XXX normal */
+typedef bi::avl_set_member_hook<link_mode> member_hook_t;
+
+template <typename D, typename B>
+struct BucketCache;
+
+template <typename D, typename B>
+struct BucketCacheEntry : public cohort::lru::Object
+{
+  using lock_guard = std::lock_guard<std::mutex>;
+  using unique_lock = std::unique_lock<std::mutex>;
+
+  static constexpr uint32_t FLAG_NONE     = 0x0000;
+  static constexpr uint32_t FLAG_FILLED   = 0x0001;
+  static constexpr uint32_t FLAG_DELETED  = 0x0002;
+  
+  static constexpr uint64_t seed = 8675309;
+
+  BucketCache<D, B>* bc;
+  std::string name;
+  std::shared_ptr<LMDBSafe::MDBEnv> env;
+  LMDBSafe::MDBDbi dbi;
+  uint64_t hk;
+  member_hook_t name_hook;
+
+  // XXX clean this up
+  std::mutex mtx; // XXX Adam's preferred shared mtx?
+  std::condition_variable cv;
+  uint32_t flags;
+
+public:
+  BucketCacheEntry(BucketCache<D, B>* bc, const std::string& name, uint64_t hk)
+    : bc(bc), name(name), hk(hk), flags(FLAG_NONE) {}
+
+  void set_env(std::shared_ptr<LMDBSafe::MDBEnv>& _env, LMDBSafe::MDBDbi& _dbi) {
+    env = _env;
+    dbi = _dbi;
+  }
+
+  inline bool deleted() const {
+    return flags & FLAG_DELETED;
+  }
+
+  class Factory : public cohort::lru::ObjectFactory
+  {
+  public:
+    BucketCache<D, B>* bc;
+    const std::string& name;
+    uint64_t hk;
+    uint32_t flags;
+
+    Factory() = delete;
+    Factory(BucketCache<D, B> *bc, const std::string& name)
+      : bc(bc), name(name), flags(FLAG_NONE) {
+      hk = XXH64(name.c_str(), name.length(), BucketCacheEntry::seed);
+    }
+
+    void recycle (cohort::lru::Object* o) override {
+      /* re-use an existing object */
+      o->~Object(); // call lru::Object virtual dtor
+      // placement new!
+      new (o) BucketCacheEntry(bc, name, hk);
+    }
+
+    cohort::lru::Object* alloc() override {
+      return new BucketCacheEntry(bc, name, hk);
+    }
+  }; /* Factory */
+
+  struct BucketCacheEntryLT
+  {
+    // for internal ordering
+    bool operator()(const BucketCacheEntry& lhs, const BucketCacheEntry& rhs) const
+      { return (lhs.name < rhs.name); }
+
+    // for external search by name
+    bool operator()(const std::string& k, const BucketCacheEntry& rhs) const
+      { return k < rhs.name; }
+
+    bool operator()(const BucketCacheEntry& lhs, const std::string& k) const
+      { return lhs.name < k; }
+  };
+
+  struct BucketCacheEntryEQ
+  {
+    bool operator()(const BucketCacheEntry& lhs, const BucketCacheEntry& rhs) const
+      { return (lhs.name == rhs.name); }
+
+    bool operator()(const std::string& k, const BucketCacheEntry& rhs) const
+      { return k == rhs.name; }
+
+    bool operator()(const BucketCacheEntry& lhs, const std::string& k) const
+      { return lhs.name == k; }
+  };
+
+  typedef cohort::lru::LRU<std::mutex> bucket_lru;
+
+  typedef bi::member_hook<BucketCacheEntry, member_hook_t, &BucketCacheEntry::name_hook> name_hook_t;
+  typedef bi::avltree<BucketCacheEntry, bi::compare<BucketCacheEntryLT>, name_hook_t> bucket_avl_t;
+  typedef cohort::lru::TreeX<BucketCacheEntry, bucket_avl_t, BucketCacheEntryLT, BucketCacheEntryEQ, std::string,
+                            std::mutex> bucket_avl_cache;
+
+  bool reclaim(const cohort::lru::ObjectFactory* newobj_fac) {
+    auto factory = dynamic_cast<const BucketCacheEntry<D, B>::Factory*>(newobj_fac);
+    if (factory == nullptr) {
+        return false;
+    }
+    { /* anon block */
+      /* in this case, we are being called from a context which holds
+       * A partition lock, and this may be still in use */
+      lock_guard{mtx};
+      if (! deleted()) {
+       flags |= FLAG_DELETED;
+       bc->recycle_count++;
+
+       //std::cout << fmt::format("reclaim {}!", name) << std::endl;
+       bc->un->remove_watch(name);
+#if 1
+       // depends on safe_link
+       if (! name_hook.is_linked()) {
+         // this should not happen!
+         abort();
+       }
+#endif
+       bc->cache.remove(hk, this, bucket_avl_cache::FLAG_NONE);
+
+       /* discard lmdb data associated with this bucket */
+       auto txn = env->getRWTransaction();
+       mdb_drop(*txn, dbi, 0);
+       txn->commit();
+       /* LMDB applications don't "normally" close database handles,
+        * but doing so (atomically) is supported, and we must as
+        * we continually recycle them */
+       mdb_dbi_close(*env, dbi); // return db handle
+      } /* ! deleted */
+    }
+    return true;
+} /* reclaim */
+
+}; /* BucketCacheEntry */
+
+using fill_cache_cb_t =
+  const fu2::unique_function<int(const DoutPrefixProvider* dpp,
+    rgw_bucket_dir_entry&) const>;
+
+using list_bucket_each_t =
+  const fu2::unique_function<bool(const rgw_bucket_dir_entry&) const>;
+
+template <typename D, typename B>
+struct BucketCache : public Notifiable
+{
+  using lock_guard = std::lock_guard<std::mutex>;
+  using unique_lock = std::unique_lock<std::mutex>;
+
+  D* driver;
+  std::string bucket_root;
+  uint32_t max_buckets;
+  std::atomic<uint64_t> recycle_count;
+  std::mutex mtx;
+
+  /* the bucket lru cache keeps track of the buckets whose listings are
+   * being cached in lmdb databases and updated from notify */
+  typename BucketCacheEntry<D, B>::bucket_lru lru;
+  typename BucketCacheEntry<D, B>::bucket_avl_cache cache;
+  sf::path rp;
+
+  /* the lmdb handle cache maintains a vector of lmdb environments,
+   * each supports 1 rw and unlimited ro transactions;  the materialized
+   * listing for each bucket is stored as a database in one of these
+   * environments, selected by a hash of the bucket name; a bucket's database
+   * is dropped/cleared whenever its entry is reclaimed from cache; the entire
+   * complex is cleared on restart to preserve consistency */
+  class Lmdbs
+  {
+    std::string database_root;
+    uint8_t lmdb_count;
+    std::vector<std::shared_ptr<LMDBSafe::MDBEnv>> envs;
+    sf::path dbp;
+
+  public:
+    Lmdbs(std::string& database_root, uint8_t lmdb_count)
+      : database_root(database_root), lmdb_count(lmdb_count),
+        dbp(database_root) {
+
+      /* create a root for lmdb directory partitions (if it doesn't
+       * exist already) */
+      sf::path safe_root_path{dbp / fmt::format("rgw_posix_lmdbs")};
+      sf::create_directory(safe_root_path);
+
+      /* purge cache completely */
+      for (const auto& dir_entry : sf::directory_iterator{safe_root_path}) {
+       sf::remove_all(dir_entry);
+      }
+
+      /* repopulate cache basis */
+      for (int ix = 0; ix < lmdb_count; ++ix) {
+       sf::path env_path{safe_root_path / fmt::format("part_{}", ix)};
+       sf::create_directory(env_path);
+       auto env = LMDBSafe::getMDBEnv(env_path.string().c_str(), 0 /* flags? */, 0600);
+       envs.push_back(env);
+      }
+    }
+
+    inline std::shared_ptr<LMDBSafe::MDBEnv>& get_sp_env(BucketCacheEntry<D, B>* bucket)  {
+      return envs[(bucket->hk % lmdb_count)];
+    }
+
+    inline LMDBSafe::MDBEnv& get_env(BucketCacheEntry<D, B>* bucket) {
+      return *(get_sp_env(bucket));
+    }
+
+    const std::string& get_root() const { return database_root; }
+  } lmdbs;
+
+  std::unique_ptr<Notify> un;
+
+public:
+  BucketCache(D* driver, std::string bucket_root, std::string database_root,
+             uint32_t max_buckets=100, uint8_t max_lanes=3,
+             uint8_t max_partitions=3, uint8_t lmdb_count=3)
+    : driver(driver), bucket_root(bucket_root), max_buckets(max_buckets),
+      lru(max_lanes, max_buckets/max_lanes),
+      cache(max_lanes, max_buckets/max_partitions),
+      rp(bucket_root),
+      lmdbs(database_root, lmdb_count),
+      un(Notify::factory(this, bucket_root))
+    {
+      if (! (sf::exists(rp) && sf::is_directory(rp))) {
+       std::cerr << fmt::format("{} bucket root {} invalid", __func__,
+                                bucket_root) << std::endl;
+       exit(1);
+      }
+
+      sf::path dp{database_root};
+      if (! (sf::exists(dp) && sf::is_directory(dp))) {
+       std::cerr << fmt::format("{} database root {} invalid", __func__,
+                                database_root) << std::endl;
+       exit(1);
+      }
+    }
+
+  static constexpr uint32_t FLAG_NONE     = 0x0000;
+  static constexpr uint32_t FLAG_CREATE   = 0x0001;
+  static constexpr uint32_t FLAG_LOCK     = 0x0002;
+
+  typedef std::tuple<BucketCacheEntry<D, B>*, uint32_t> GetBucketResult;
+
+  GetBucketResult get_bucket(const std::string& name, uint32_t flags)
+    {
+      /* this fn returns a bucket locked appropriately, having atomically
+       * found or inserted the required BucketCacheEntry in_avl*/
+      BucketCacheEntry<D, B>* b{nullptr};
+      typename BucketCacheEntry<D, B>::Factory fac(this, name);
+      typename BucketCacheEntry<D, B>::bucket_avl_cache::Latch lat;
+      uint32_t iflags{cohort::lru::FLAG_INITIAL};
+      GetBucketResult result{nullptr, 0};
+
+    retry:
+      b = cache.find_latch(fac.hk /* partition selector */,
+                          name /* key */, lat /* serializer */, BucketCacheEntry<D, B>::bucket_avl_cache::FLAG_LOCK);
+      /* LATCHED */
+      if (b) {
+       b->mtx.lock();
+       if (b->deleted() ||
+           ! lru.ref(b, cohort::lru::FLAG_INITIAL)) {
+         // lru ref failed
+         lat.lock->unlock();
+         b->mtx.unlock();
+         goto retry;
+       }
+       lat.lock->unlock();
+       /* LOCKED */
+      } else {
+       /* BucketCacheEntry not in cache */
+       if (! (flags & BucketCache<D, B>::FLAG_CREATE)) {
+         /* the caller does not want to instantiate a new cache
+          * entry (i.e., only wants to notify on an existing one) */
+         return result;
+       }
+       /* we need to create it */
+       b = static_cast<BucketCacheEntry<D, B>*>(
+         lru.insert(&fac, cohort::lru::Edge::MRU, iflags));
+       if (b) [[likely]] {
+         b->mtx.lock();
+
+         /* attach bucket to an lmdb partition and prepare it for i/o */
+         auto& env = lmdbs.get_sp_env(b);
+         auto dbi = env->openDB(b->name, MDB_CREATE);
+         b->set_env(env, dbi);
+
+         if (! (iflags & cohort::lru::FLAG_RECYCLE)) [[likely]] {
+           /* inserts at cached insert iterator, releasing latch */
+           cache.insert_latched(b, lat, BucketCacheEntry<D, B>::bucket_avl_cache::FLAG_UNLOCK);
+         } else {
+           /* recycle step invalidates Latch */
+           lat.lock->unlock(); /* !LATCHED */
+           cache.insert(fac.hk, b, BucketCacheEntry<D, B>::bucket_avl_cache::FLAG_NONE);
+         }
+         get<1>(result) |= BucketCache<D, B>::FLAG_CREATE;
+       } else {
+         /* XXX lru allocate failed? seems impossible--that would mean that
+          * fallback to the allocator also failed, and maybe we should abend */
+         lat.lock->unlock();
+         goto retry; /* !LATCHED */
+       }
+      } /* have BucketCacheEntry */
+
+      if (! (flags & BucketCache<D, B>::FLAG_LOCK)) {
+       b->mtx.unlock();
+      }
+      get<0>(result) = b;
+      return result;
+    } /* get_bucket */
+
+  static inline std::string concat_key(const rgw_obj_index_key& k) {
+    std::string k_str;
+    k_str.reserve(k.name.size() + k.instance.size());
+    k_str += k.name;
+    k_str += k.instance;
+    return k_str;
+  }
+
+  int fill(const DoutPrefixProvider* dpp, BucketCacheEntry<D, B>* bucket,
+           B* sal_bucket, uint32_t flags, optional_yield y) /* assert: LOCKED */
+  {
+      auto txn = bucket->env->getRWTransaction();
+
+      /* instruct the bucket provider to enumerate all entries,
+       * in any order */
+      auto rc = sal_bucket->fill_cache(dpp, y,
+       [&](const DoutPrefixProvider* dpp, rgw_bucket_dir_entry& bde) -> int {
+         auto concat_k = concat_key(bde.key);
+         std::string ser_data;
+         zpp::bits::out out(ser_data);
+         struct timespec ts{ceph::real_clock::to_timespec(bde.meta.mtime)};
+         auto errc =
+           out(bde.key.name, bde.key.instance, /* XXX bde.key.ns, */
+               bde.ver.pool, bde.ver.epoch, bde.exists,
+               bde.meta.category, bde.meta.size, ts.tv_sec, ts.tv_nsec,
+               bde.meta.owner, bde.meta.owner_display_name, bde.meta.accounted_size,
+               bde.meta.storage_class, bde.meta.appendable, bde.meta.etag);
+         /*std::cout << fmt::format("fill: bde.key.name: {}", bde.key.name)
+           << std::endl;*/
+         if (errc.code != std::errc{0}) {
+           abort();
+           return 0; // XXX non-zero return?
+         }
+         txn->put(bucket->dbi, concat_k, ser_data);
+         //std::cout << fmt::format("{} {}", __func__, bde.key.name) << '\n';
+         return 0;
+       });
+
+      txn->commit();
+      bucket->flags |= BucketCacheEntry<D, B>::FLAG_FILLED;
+      un->add_watch(bucket->name, bucket);
+      return rc;
+    } /* fill */
+
+  int list_bucket(const DoutPrefixProvider* dpp, optional_yield y, B* sal_bucket,
+                 std::string marker, list_bucket_each_t each_func) {
+
+    using namespace LMDBSafe;
+
+    int rc __attribute__((unused)) = 0;
+    GetBucketResult gbr =
+      get_bucket(sal_bucket->get_name(),
+                BucketCache<D, B>::FLAG_LOCK | BucketCache<D, B>::FLAG_CREATE);
+    auto [b /* BucketCacheEntry */, flags] = gbr;
+    if (b /* XXX again, can this fail? */) {
+      if (! (b->flags & BucketCacheEntry<D, B>::FLAG_FILLED)) {
+       /* bulk load into lmdb cache */
+       rc = fill(dpp, b, sal_bucket, FLAG_NONE, y);
+      }
+      /* display them */
+      b->mtx.unlock();
+      /*! LOCKED */
+
+      auto txn = b->env->getROTransaction();
+      auto cursor=txn->getCursor(b->dbi);
+      MDBOutVal key, data;
+      bool again{true};
+
+      const auto proc_result = [&]() {
+       zpp::bits::errc errc{};
+       rgw_bucket_dir_entry bde{};
+       /* XXX we may not need to recover the cache key */
+       std::string_view svk __attribute__((unused)) =
+         key.get<string_view>(); // {name, instance, [ns]}
+       std::string_view svv = data.get<string_view>();
+       std::string ser_v{svv};
+       zpp::bits::in in_v(ser_v);
+       struct timespec ts;
+       errc =
+         in_v(bde.key.name, bde.key.instance, /* bde.key.ns, */
+              bde.ver.pool, bde.ver.epoch, bde.exists,
+              bde.meta.category, bde.meta.size, ts.tv_sec, ts.tv_nsec,
+              bde.meta.owner, bde.meta.owner_display_name, bde.meta.accounted_size,
+              bde.meta.storage_class, bde.meta.appendable, bde.meta.etag);
+       if (errc.code != std::errc{0}) {
+         abort();
+       }
+       bde.meta.mtime = ceph::real_clock::from_timespec(ts);
+       again = each_func(bde);
+      };
+
+      if (! marker.empty()) {
+       MDBInVal k(marker);
+       auto rc = cursor.lower_bound(k, key, data);
+       if (rc == MDB_NOTFOUND) {
+         /* no key sorts after k/marker, so there is nothing to do */
+         return 0;
+       }
+       proc_result();
+      } else {
+       /* position at start of index */
+       auto rc = cursor.get(key, data, MDB_FIRST);
+       if (rc == MDB_SUCCESS) {
+         proc_result();
+       }
+      }
+      while(cursor.get(key, data, MDB_NEXT) == MDB_SUCCESS) {
+       if (!again) {
+         return 0;
+       }
+       proc_result();
+      }
+      lru.unref(b, cohort::lru::FLAG_NONE);
+    } /* b */
+
+    return 0;
+  } /* list_bucket */
+
+  int notify(const std::string& bname, void* opaque,
+            const std::vector<Notifiable::Event>& evec) override {
+
+    using namespace LMDBSafe;
+
+    int rc{0};
+    GetBucketResult gbr = get_bucket(bname, BucketCache<D, B>::FLAG_LOCK);
+    auto [b /* BucketCacheEntry */, flags] = gbr;
+    if (b) {
+      unique_lock ulk{b->mtx, std::adopt_lock};
+      if ((b->name != bname) ||
+         (b != opaque) ||
+         (! (b->flags & BucketCacheEntry<D, B>::FLAG_FILLED))) {
+       /* do nothing */
+       return 0;
+      }
+      ulk.unlock();
+      auto txn = b->env->getRWTransaction();
+      for (const auto& ev : evec) {
+       using EventType = Notifiable::EventType;
+       /*
+       std::string_view nil{""};
+       std::cout << fmt::format("notify {} {}!",
+                                ev.name ? *ev.name : nil,
+                                uint32_t(ev.type))
+                                << std::endl; */
+       switch (ev.type)
+       {
+       case EventType::ADD:
+       {
+         rgw_bucket_dir_entry bde{};
+         bde.key.name = *ev.name;
+         /* XXX will need work (if not straight up magic) to have
+          * side loading support instance and ns */
+         auto concat_k = concat_key(bde.key);
+         rc = driver->mint_listing_entry(b->name, bde);
+         std::string ser_data;
+         zpp::bits::out out(ser_data);
+         struct timespec ts{ceph::real_clock::to_timespec(bde.meta.mtime)};
+         auto errc =
+           out(bde.key.name, bde.key.instance, /* XXX bde.key.ns, */
+               bde.ver.pool, bde.ver.epoch, bde.exists,
+               bde.meta.category, bde.meta.size, ts.tv_sec, ts.tv_nsec,
+               bde.meta.owner, bde.meta.owner_display_name, bde.meta.accounted_size,
+               bde.meta.storage_class, bde.meta.appendable, bde.meta.etag);
+         if (errc.code != std::errc{0}) {
+           abort();
+         }
+         txn->put(b->dbi, concat_k, ser_data);
+       }
+         break;
+       case EventType::REMOVE:
+       {
+         auto& ev_name = *ev.name;
+         txn->del(b->dbi, ev_name);
+       }
+         break;
+       [[unlikely]] case EventType::INVALIDATE:
+       {
+         /* yikes, cache blown */
+         ulk.lock();
+         mdb_drop(*txn, b->dbi, 0);
+         txn->commit();
+         b->flags &= ~BucketCacheEntry<D, B>::FLAG_FILLED;
+         return 0; /* don't process any more events in this batch */
+       }
+         break;
+       default:
+         /* unknown event */
+         break;
+       }
+      } /* all events */
+      txn->commit();
+      lru.unref(b, cohort::lru::FLAG_NONE);
+    } /* b */
+    return rc;
+  } /* notify */
+  
+}; /* BucketCache */
+
+} // namespace file::listing
diff --git a/src/rgw/driver/posix/lmdb-safe-global.h b/src/rgw/driver/posix/lmdb-safe-global.h
new file mode 100644 (file)
index 0000000..c5b2d37
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+MIT License
+
+Copyright (c) 2018 bert hubert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+*/
+#ifndef LMDB_SAFE_GLOBAL
+#define LMDB_SAFE_GLOBAL
+
+#ifndef LMDB_SAFE_NO_CPP_UTILITIES
+#include <c++utilities/application/global.h>
+#else
+#undef LMDB_SAFE_STATIC
+#define LMDB_SAFE_STATIC 1
+#endif
+
+#ifdef LMDB_SAFE_STATIC
+#define LMDB_SAFE_EXPORT
+#define LMDB_SAFE_IMPORT
+#else
+#define LMDB_SAFE_EXPORT CPP_UTILITIES_GENERIC_LIB_EXPORT
+#define LMDB_SAFE_IMPORT CPP_UTILITIES_GENERIC_LIB_IMPORT
+#endif
+
+/*!
+ * \def LMDB_SAFE_EXPORT
+ * \brief Marks the symbol to be exported by the lmdb-safe library.
+ */
+
+/*!
+ * \def LMDB_SAFE_IMPORT
+ * \brief Marks the symbol to be imported from the lmdb-safe library.
+ */
+
+#endif // LMDB_SAFE_GLOBAL
diff --git a/src/rgw/driver/posix/lmdb-safe.cc b/src/rgw/driver/posix/lmdb-safe.cc
new file mode 100644 (file)
index 0000000..389d016
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+MIT License
+
+Copyright (c) 2018 bert hubert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+*/
+#include "lmdb-safe.hh"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <cstring>
+#include <map>
+#include <memory>
+#include <mutex>
+
+using namespace std;
+
+namespace LMDBSafe {
+
+MDBDbi::MDBDbi(MDB_env *env, MDB_txn *txn, const string_view dbname, unsigned int flags)
+{
+    (void)env;
+    // A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
+
+    if (const auto rc = mdb_dbi_open(txn, dbname.empty() ? 0 : &dbname[0], flags, &d_dbi))
+        throw LMDBError("Unable to open named database: ", rc);
+
+    // Database names are keys in the unnamed database, and may be read but not written.
+}
+
+MDBEnv::MDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs)
+{
+    mdb_env_create(&d_env);
+    if (const auto rc = mdb_env_set_mapsize(d_env, 16ULL * 4096 * 244140ULL)) { // 4GB
+        throw LMDBError("Setting map size: ", rc);
+    }
+    // Various other options may also need to be set before opening the handle, e.g. mdb_env_set_mapsize(), mdb_env_set_maxreaders(), mdb_env_set_maxdbs(),
+    if (const auto rc = mdb_env_set_maxdbs(d_env, maxDBs)) {
+        throw LMDBError("Setting maxdbs: ", rc);
+    }
+
+    // we need MDB_NOTLS since we rely on its semantics
+    if (const auto rc = mdb_env_open(d_env, fname, flags | MDB_NOTLS, mode)) {
+        // If this function fails, mdb_env_close() must be called to discard the MDB_env handle.
+        mdb_env_close(d_env);
+        throw LMDBError("Unable to open database file " + std::string(fname) + ": ", rc);
+    }
+}
+
+void MDBEnv::incROTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    ++d_ROtransactionsOut[std::this_thread::get_id()];
+}
+
+void MDBEnv::decROTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    --d_ROtransactionsOut[std::this_thread::get_id()];
+}
+
+void MDBEnv::incRWTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    ++d_RWtransactionsOut[std::this_thread::get_id()];
+}
+
+void MDBEnv::decRWTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    --d_RWtransactionsOut[std::this_thread::get_id()];
+}
+
+int MDBEnv::getRWTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    return d_RWtransactionsOut[std::this_thread::get_id()];
+}
+int MDBEnv::getROTX()
+{
+    std::lock_guard<std::mutex> l(d_countmutex);
+    return d_ROtransactionsOut[std::this_thread::get_id()];
+}
+
+std::shared_ptr<MDBEnv> getMDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs)
+{
+    struct Value {
+        weak_ptr<MDBEnv> wp;
+        unsigned int flags;
+    };
+
+    static std::map<tuple<dev_t, ino_t>, Value> s_envs;
+    static std::mutex mut;
+
+    struct stat statbuf;
+    if (stat(fname, &statbuf)) {
+        if (errno != ENOENT)
+            throw LMDBError("Unable to stat prospective mdb database: " + string(strerror(errno)));
+        else {
+            std::lock_guard<std::mutex> l(mut);
+            auto fresh = std::make_shared<MDBEnv>(fname, flags, mode, maxDBs);
+            if (stat(fname, &statbuf))
+                throw LMDBError("Unable to stat prospective mdb database: " + string(strerror(errno)));
+            auto key = std::tie(statbuf.st_dev, statbuf.st_ino);
+            s_envs[key] = { fresh, flags };
+            return fresh;
+        }
+    }
+
+    std::lock_guard<std::mutex> l(mut);
+    auto key = std::tie(statbuf.st_dev, statbuf.st_ino);
+    auto iter = s_envs.find(key);
+    if (iter != s_envs.end()) {
+        auto sp = iter->second.wp.lock();
+        if (sp) {
+            if (iter->second.flags != flags)
+                throw LMDBError("Can't open mdb with differing flags");
+
+            return sp;
+        } else {
+            s_envs.erase(iter); // useful if make_shared fails
+        }
+    }
+
+    auto fresh = std::make_shared<MDBEnv>(fname, flags, mode, maxDBs);
+    s_envs[key] = { fresh, flags };
+
+    return fresh;
+}
+
+MDBDbi MDBEnv::openDB(const string_view dbname, unsigned int flags)
+{
+    unsigned int envflags;
+    mdb_env_get_flags(d_env, &envflags);
+    /*
+    This function must not be called from multiple concurrent transactions in the same process. A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
+  */
+    std::lock_guard<std::mutex> l(d_openmut);
+
+    if (!(envflags & MDB_RDONLY)) {
+        auto rwt = getRWTransaction();
+        MDBDbi ret = rwt->openDB(dbname, flags);
+        rwt->commit();
+        return ret;
+    }
+
+    MDBDbi ret;
+    {
+        auto rwt = getROTransaction();
+        ret = rwt->openDB(dbname, flags);
+    }
+    return ret;
+}
+
+MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn)
+    : MDBROTransactionImpl(parent, txn)
+
+{
+}
+
+MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags)
+{
+    MDB_txn *result;
+    if (env->getRWTX())
+        throw LMDBError("Duplicate RW transaction");
+
+    for (int tries = 0; tries < 3; ++tries) { // it might happen twice, who knows
+        if (int rc = mdb_txn_begin(env->d_env, parent, flags, &result)) {
+            if (rc == MDB_MAP_RESIZED && tries < 2) {
+                // "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED.
+                // call mdb_env_set_mapsize with a size of zero to adopt the new size."
+                mdb_env_set_mapsize(env->d_env, 0);
+                continue;
+            }
+            throw LMDBError("Unable to start RW transaction: ", rc);
+        }
+        break;
+    }
+    env->incRWTX();
+    return result;
+}
+
+MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, unsigned int flags)
+    : MDBRWTransactionImpl(parent, openRWTransaction(parent, nullptr, flags))
+{
+}
+
+MDBRWTransactionImpl::~MDBRWTransactionImpl()
+{
+    MDBRWTransactionImpl::abort();
+}
+
+void MDBRWTransactionImpl::commit()
+{
+    closeRORWCursors();
+    if (!d_txn) {
+        return;
+    }
+
+    if (const auto rc = mdb_txn_commit(d_txn)) {
+        throw LMDBError("Committing transaction: ", rc);
+    }
+    environment().decRWTX();
+    d_txn = nullptr;
+}
+
+void MDBRWTransactionImpl::abort()
+{
+    closeRORWCursors();
+    if (!d_txn) {
+        return;
+    }
+
+    mdb_txn_abort(d_txn);
+    // prevent the RO destructor from cleaning up the transaction itself
+    environment().decRWTX();
+    d_txn = nullptr;
+}
+
+MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn)
+    : d_parent(parent)
+    , d_cursors()
+    , d_txn(txn)
+{
+}
+
+MDB_txn *MDBROTransactionImpl::openROTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags)
+{
+    if (env->getRWTX())
+        throw LMDBError("Duplicate RO transaction");
+
+    /*
+    A transaction and its cursors must only be used by a single thread, and a thread may only have a single transaction at a time. If MDB_NOTLS is in use, this does not apply to read-only transactions. */
+    MDB_txn *result = nullptr;
+    for (int tries = 0; tries < 3; ++tries) { // it might happen twice, who knows
+        if (const auto rc = mdb_txn_begin(env->d_env, parent, MDB_RDONLY | flags, &result)) {
+            if (rc == MDB_MAP_RESIZED && tries < 2) {
+                // "If the mapsize is increased by another process (..) mdb_txn_begin() will return MDB_MAP_RESIZED.
+                // call mdb_env_set_mapsize with a size of zero to adopt the new size."
+                mdb_env_set_mapsize(env->d_env, 0);
+                continue;
+            }
+            throw LMDBError("Unable to start RO transaction: ", rc);
+        }
+        break;
+    }
+    env->incROTX();
+
+    return result;
+}
+
+void MDBROTransactionImpl::closeROCursors()
+{
+    // we need to move the vector away to ensure that the cursors don’t mess with our iteration.
+    std::vector<MDBROCursor *> buf;
+    std::swap(d_cursors, buf);
+    for (auto &cursor : buf) {
+        cursor->close();
+    }
+}
+
+MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, unsigned int flags)
+    : MDBROTransactionImpl(parent, openROTransaction(parent, nullptr, flags))
+{
+}
+
+MDBROTransactionImpl::~MDBROTransactionImpl()
+{
+    // this is safe because C++ will not call overrides of virtual methods in destructors.
+    MDBROTransactionImpl::commit();
+}
+
+void MDBROTransactionImpl::abort()
+{
+    closeROCursors();
+    // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
+    if (d_txn) {
+        d_parent->decROTX();
+        mdb_txn_abort(d_txn); // this appears to work better than abort for r/o database opening
+        d_txn = nullptr;
+    }
+}
+
+void MDBROTransactionImpl::commit()
+{
+    closeROCursors();
+    // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
+    if (d_txn) {
+        d_parent->decROTX();
+        if (const auto rc = mdb_txn_commit(d_txn)) { // this appears to work better than abort for r/o database opening
+            throw LMDBError("Error committing transaction: ", rc);
+        }
+        d_txn = nullptr;
+    }
+}
+
+void MDBRWTransactionImpl::clear(MDB_dbi dbi)
+{
+    if (const auto rc = mdb_drop(d_txn, dbi, 0)) {
+        throw LMDBError("Error clearing database: ", rc);
+    }
+}
+
+MDBRWCursor MDBRWTransactionImpl::getRWCursor(const MDBDbi &dbi)
+{
+    MDB_cursor *cursor;
+    ;
+    if (const auto rc = mdb_cursor_open(d_txn, dbi, &cursor)) {
+        throw LMDBError("Error creating RO cursor: ", rc);
+    }
+    return MDBRWCursor(d_rw_cursors, cursor);
+}
+
+MDBRWCursor MDBRWTransactionImpl::getCursor(const MDBDbi &dbi)
+{
+    return getRWCursor(dbi);
+}
+
+MDBRWTransaction MDBRWTransactionImpl::getRWTransaction()
+{
+    MDB_txn *txn;
+    if (const auto rc = mdb_txn_begin(environment(), *this, 0, &txn)) {
+        throw LMDBError("Failed to start child transaction: ", rc);
+    }
+    // we need to increase the counter here because commit/abort on the child transaction will decrease it
+    environment().incRWTX();
+    return MDBRWTransaction(new MDBRWTransactionImpl(&environment(), txn));
+}
+
+MDBROTransaction MDBRWTransactionImpl::getROTransaction()
+{
+    return getRWTransaction();
+}
+
+MDBROTransaction MDBEnv::getROTransaction()
+{
+    return MDBROTransaction(new MDBROTransactionImpl(this));
+}
+MDBRWTransaction MDBEnv::getRWTransaction()
+{
+    return MDBRWTransaction(new MDBRWTransactionImpl(this));
+}
+
+void MDBRWTransactionImpl::closeRWCursors()
+{
+    decltype(d_rw_cursors) buf;
+    std::swap(d_rw_cursors, buf);
+    for (auto &cursor : buf) {
+        cursor->close();
+    }
+}
+
+MDBROCursor MDBROTransactionImpl::getCursor(const MDBDbi &dbi)
+{
+    return getROCursor(dbi);
+}
+
+MDBROCursor MDBROTransactionImpl::getROCursor(const MDBDbi &dbi)
+{
+    MDB_cursor *cursor;
+    if (const auto rc = mdb_cursor_open(d_txn, dbi, &cursor)) {
+        throw LMDBError("Error creating RO cursor: ", rc);
+    }
+    return MDBROCursor(d_cursors, cursor);
+}
+
+} // namespace LMDBSafe
diff --git a/src/rgw/driver/posix/lmdb-safe.hh b/src/rgw/driver/posix/lmdb-safe.hh
new file mode 100644 (file)
index 0000000..807ad21
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+MIT License
+
+Copyright (c) 2018 bert hubert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+*/
+#pragma once
+
+#include "lmdb-safe-global.h"
+
+#include <lmdb.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <string>
+#include <thread>
+#include <vector>
+
+/*!
+ * \brief The LMDBSafe namespace contains all classes/types contained by the lmdb-safe and
+ *        lmdb-typed libraries.
+ * \remarks
+ * - Error strategy: Anything that "should never happen" turns into an exception. But things
+ *   like "duplicate entry" or "no such key" are for you to deal with.
+ * - Thread safety: We are as safe as LMDB. You can talk to MDBEnv from as many threads as you
+ *   want.
+ */
+namespace LMDBSafe {
+
+// apple compiler somehow has string_view even in c++11!
+#ifdef __cpp_lib_string_view
+using std::string_view;
+#else
+#include <boost/version.hpp>
+#if BOOST_VERSION > 105400
+#include <boost/utility/string_view.hpp>
+using boost::string_view;
+#else
+#include <boost/utility/string_ref.hpp>
+using string_view = boost::string_ref;
+#endif
+#endif
+
+/*!
+ * \brief The LMDBError class is thrown when an error happens.
+ */
+class LMDB_SAFE_EXPORT LMDBError : public std::runtime_error {
+public:
+    explicit LMDBError(const std::string &error) noexcept
+        : std::runtime_error(error)
+        , ec(0)
+    {
+    }
+
+    explicit LMDBError(const std::string &context, int error) noexcept
+        : std::runtime_error(context + mdb_strerror(error))
+        , ec(error)
+    {
+    }
+
+    const int ec;
+};
+
+/*!
+ * \brief The MDBDbi class is our only 'value type' object as 1) a dbi is actually an integer
+ *        and 2) per LMDB documentation, we never close it.
+ */
+class LMDB_SAFE_EXPORT MDBDbi {
+public:
+    MDBDbi()
+    {
+        d_dbi = std::numeric_limits<decltype(d_dbi)>::max();
+    }
+    explicit MDBDbi(MDB_env *env, MDB_txn *txn, string_view dbname, unsigned int flags);
+
+    operator const MDB_dbi &() const
+    {
+        return d_dbi;
+    }
+
+    MDB_dbi d_dbi;
+};
+
+class MDBRWTransactionImpl;
+class MDBROTransactionImpl;
+using MDBROTransaction = std::unique_ptr<MDBROTransactionImpl>;
+using MDBRWTransaction = std::unique_ptr<MDBRWTransactionImpl>;
+
+/*!
+ * \brief The MDBEnv class is a handle to an MDB environment.
+ */
+class LMDB_SAFE_EXPORT MDBEnv {
+public:
+    MDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs = 10);
+
+    /*!
+     * \brief Closes the MDB environment.
+     * \remarks Only a single thread may call this function. All transactions, databases, and cursors must already be closed
+     *          before calling this function.
+     */
+    ~MDBEnv()
+    {
+        mdb_env_close(d_env);
+    }
+
+    MDBDbi openDB(const string_view dbname, unsigned int flags);
+
+    MDBRWTransaction getRWTransaction();
+    MDBROTransaction getROTransaction();
+
+    operator MDB_env *&()
+    {
+        return d_env;
+    }
+    MDB_env *d_env;
+
+    int getRWTX();
+    void incRWTX();
+    void decRWTX();
+    int getROTX();
+    void incROTX();
+    void decROTX();
+
+private:
+    std::mutex d_openmut;
+    std::mutex d_countmutex;
+    std::map<std::thread::id, int> d_RWtransactionsOut;
+    std::map<std::thread::id, int> d_ROtransactionsOut;
+};
+
+/*!
+ * \brief Opens an MDB environment for the specified database file.
+ */
+LMDB_SAFE_EXPORT std::shared_ptr<MDBEnv> getMDBEnv(const char *fname, unsigned int flags, mdb_mode_t mode, MDB_dbi maxDBs = 128);
+
+/*!
+ * \brief The MDBOutVal struct is the handle to an MDB value used as output.
+ */
+struct LMDB_SAFE_EXPORT MDBOutVal {
+    operator MDB_val &()
+    {
+        return d_mdbval;
+    }
+
+    template <class T, typename std::enable_if<std::is_arithmetic<T>::value, T>::type * = nullptr> const T get()
+    {
+        T ret;
+        if (d_mdbval.mv_size != sizeof(T))
+            throw LMDBError("MDB data has wrong length for type");
+
+        memcpy(&ret, d_mdbval.mv_data, sizeof(T));
+        return ret;
+    }
+
+    template <class T, typename std::enable_if<std::is_class<T>::value, T>::type * = nullptr> T get() const;
+
+    template <class T> T get_struct() const
+    {
+        T ret;
+        if (d_mdbval.mv_size != sizeof(T))
+            throw LMDBError("MDB data has wrong length for type");
+
+        memcpy(&ret, d_mdbval.mv_data, sizeof(T));
+        return ret;
+    }
+
+    template <class T> const T *get_struct_ptr() const
+    {
+        if (d_mdbval.mv_size != sizeof(T))
+            throw LMDBError("MDB data has wrong length for type");
+
+        return reinterpret_cast<const T *>(d_mdbval.mv_data);
+    }
+
+    MDB_val d_mdbval;
+};
+
+template <> inline std::string MDBOutVal::get<std::string>() const
+{
+    return std::string(static_cast<char *>(d_mdbval.mv_data), d_mdbval.mv_size);
+}
+
+template <> inline string_view MDBOutVal::get<string_view>() const
+{
+    return string_view(static_cast<char *>(d_mdbval.mv_data), d_mdbval.mv_size);
+}
+
+/*!
+ * \brief The MDBInVal struct is the handle to an MDB value used as input.
+ */
+class LMDB_SAFE_EXPORT MDBInVal {
+public:
+    MDBInVal(const MDBOutVal &rhs)
+    {
+        d_mdbval = rhs.d_mdbval;
+    }
+
+    template <class T, typename std::enable_if<std::is_arithmetic<T>::value, T>::type * = nullptr> MDBInVal(T i)
+    {
+        memcpy(&d_memory[0], &i, sizeof(i));
+        d_mdbval.mv_size = sizeof(T);
+        d_mdbval.mv_data = d_memory;
+        ;
+    }
+
+    MDBInVal(const char *s)
+    {
+        d_mdbval.mv_size = strlen(s);
+        d_mdbval.mv_data = static_cast<void *>(const_cast<char *>(s));
+    }
+
+    MDBInVal(string_view v)
+    {
+        d_mdbval.mv_size = v.size();
+        d_mdbval.mv_data = static_cast<void *>(const_cast<char *>(v.data()));
+    }
+
+    MDBInVal(const std::string &v)
+    {
+        d_mdbval.mv_size = v.size();
+        d_mdbval.mv_data = static_cast<void *>(const_cast<char *>(v.data()));
+    }
+
+    template <typename T> static MDBInVal fromStruct(const T &t)
+    {
+        MDBInVal ret;
+        ret.d_mdbval.mv_size = sizeof(T);
+        ret.d_mdbval.mv_data = static_cast<void *>(&const_cast<T &>(t));
+        return ret;
+    }
+
+    operator MDB_val &()
+    {
+        return d_mdbval;
+    }
+    MDB_val d_mdbval;
+
+private:
+    MDBInVal()
+    {
+    }
+    char d_memory[sizeof(double)];
+};
+
+class MDBROCursor;
+
+/*!
+ * \brief The MDBROTransactionImpl class wraps read operations.
+ */
+class LMDB_SAFE_EXPORT MDBROTransactionImpl {
+protected:
+    MDBROTransactionImpl(MDBEnv *parent, MDB_txn *txn);
+
+private:
+    static MDB_txn *openROTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags = 0);
+
+    MDBEnv *d_parent;
+    std::vector<MDBROCursor *> d_cursors;
+
+protected:
+    MDB_txn *d_txn;
+
+    void closeROCursors();
+
+public:
+    explicit MDBROTransactionImpl(MDBEnv *parent, unsigned int flags = 0);
+
+    MDBROTransactionImpl(const MDBROTransactionImpl &src) = delete;
+    MDBROTransactionImpl &operator=(const MDBROTransactionImpl &src) = delete;
+
+    // The move constructor/operator cannot be made safe due to Object Slicing with MDBRWTransaction.
+    MDBROTransactionImpl(MDBROTransactionImpl &&rhs) = delete;
+    MDBROTransactionImpl &operator=(MDBROTransactionImpl &&rhs) = delete;
+
+    virtual ~MDBROTransactionImpl();
+
+    virtual void abort();
+    virtual void commit();
+
+    int get(MDB_dbi dbi, const MDBInVal &key, MDBOutVal &val)
+    {
+        if (!d_txn)
+            throw LMDBError("Attempt to use a closed RO transaction for get");
+
+        const auto rc = mdb_get(d_txn, dbi, const_cast<MDB_val *>(&key.d_mdbval), &val.d_mdbval);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Getting data: ", rc);
+
+        return rc;
+    }
+
+    int get(MDB_dbi dbi, const MDBInVal &key, string_view &val)
+    {
+        MDBOutVal out;
+        int rc = get(dbi, key, out);
+        if (!rc)
+            val = out.get<string_view>();
+        return rc;
+    }
+
+    // this is something you can do, readonly
+    MDBDbi openDB(string_view dbname, unsigned int flags)
+    {
+        return MDBDbi(d_parent->d_env, d_txn, dbname, flags);
+    }
+
+    MDBROCursor getCursor(const MDBDbi &);
+    MDBROCursor getROCursor(const MDBDbi &);
+
+    operator MDB_txn *()
+    {
+        return d_txn;
+    }
+
+    inline operator bool() const
+    {
+        return d_txn;
+    }
+
+    inline MDBEnv &environment()
+    {
+        return *d_parent;
+    }
+};
+
+/*!
+ * \brief The MDBGenCursor class represents a MDB_cursor handle.
+ * \remarks
+ * - A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends.
+ *   It can be reused with mdb_cursor_renew() before finally closing it.
+ * - "If the parent transaction commits, the cursor must not be used again."
+ */
+template <class Transaction, class T> class MDBGenCursor {
+private:
+    std::vector<T *> *d_registry;
+    MDB_cursor *d_cursor;
+
+public:
+    MDBGenCursor()
+        : d_registry(nullptr)
+        , d_cursor(nullptr)
+    {
+    }
+
+    MDBGenCursor(std::vector<T *> &registry, MDB_cursor *cursor)
+        : d_registry(&registry)
+        , d_cursor(cursor)
+    {
+        registry.emplace_back(static_cast<T *>(this));
+    }
+
+private:
+    void move_from(MDBGenCursor *src)
+    {
+        if (!d_registry) {
+            return;
+        }
+
+        auto iter = std::find(d_registry->begin(), d_registry->end(), src);
+        if (iter != d_registry->end()) {
+            *iter = static_cast<T *>(this);
+        } else {
+            d_registry->emplace_back(static_cast<T *>(this));
+        }
+    }
+
+public:
+    MDBGenCursor(const MDBGenCursor &src) = delete;
+
+    MDBGenCursor(MDBGenCursor &&src) noexcept
+        : d_registry(src.d_registry)
+        , d_cursor(src.d_cursor)
+    {
+        move_from(&src);
+        src.d_registry = nullptr;
+        src.d_cursor = nullptr;
+    }
+
+    MDBGenCursor &operator=(const MDBGenCursor &src) = delete;
+
+    MDBGenCursor &operator=(MDBGenCursor &&src) noexcept
+    {
+        d_registry = src.d_registry;
+        d_cursor = src.d_cursor;
+        move_from(&src);
+        src.d_registry = nullptr;
+        src.d_cursor = nullptr;
+        return *this;
+    }
+
+    ~MDBGenCursor()
+    {
+        close();
+    }
+
+public:
+    int get(MDBOutVal &key, MDBOutVal &data, MDB_cursor_op op)
+    {
+        const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Unable to get from cursor: ", rc);
+        return rc;
+    }
+
+    int find(const MDBInVal &in, MDBOutVal &key, MDBOutVal &data)
+    {
+        key.d_mdbval = in.d_mdbval;
+        const auto rc = mdb_cursor_get(d_cursor, const_cast<MDB_val *>(&key.d_mdbval), &data.d_mdbval, MDB_SET);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Unable to find from cursor: ", rc);
+        return rc;
+    }
+
+    int lower_bound(const MDBInVal &in, MDBOutVal &key, MDBOutVal &data)
+    {
+        key.d_mdbval = in.d_mdbval;
+
+        const auto rc = mdb_cursor_get(d_cursor, const_cast<MDB_val *>(&key.d_mdbval), &data.d_mdbval, MDB_SET_RANGE);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Unable to lower_bound from cursor: ", rc);
+        return rc;
+    }
+
+    int nextprev(MDBOutVal &key, MDBOutVal &data, MDB_cursor_op op)
+    {
+        const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Unable to prevnext from cursor: ", rc);
+        return rc;
+    }
+
+    int next(MDBOutVal &key, MDBOutVal &data)
+    {
+        return nextprev(key, data, MDB_NEXT);
+    }
+
+    int prev(MDBOutVal &key, MDBOutVal &data)
+    {
+        return nextprev(key, data, MDB_PREV);
+    }
+
+    int currentlast(MDBOutVal &key, MDBOutVal &data, MDB_cursor_op op)
+    {
+        const auto rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Unable to next from cursor: ", rc);
+        return rc;
+    }
+
+    int current(MDBOutVal &key, MDBOutVal &data)
+    {
+        return currentlast(key, data, MDB_GET_CURRENT);
+    }
+    int last(MDBOutVal &key, MDBOutVal &data)
+    {
+        return currentlast(key, data, MDB_LAST);
+    }
+    int first(MDBOutVal &key, MDBOutVal &data)
+    {
+        return currentlast(key, data, MDB_FIRST);
+    }
+
+    operator MDB_cursor *()
+    {
+        return d_cursor;
+    }
+
+    operator bool() const
+    {
+        return d_cursor;
+    }
+
+    void close()
+    {
+        if (d_registry) {
+            auto iter = std::find(d_registry->begin(), d_registry->end(), static_cast<T *>(this));
+            if (iter != d_registry->end()) {
+                d_registry->erase(iter);
+            }
+            d_registry = nullptr;
+        }
+        if (d_cursor) {
+            mdb_cursor_close(d_cursor);
+            d_cursor = nullptr;
+        }
+    }
+};
+
+/*!
+ * \brief The MDBROCursor class represents a read-only cursor.
+ */
+class LMDB_SAFE_EXPORT MDBROCursor : public MDBGenCursor<MDBROTransactionImpl, MDBROCursor> {
+public:
+    MDBROCursor() = default;
+    using MDBGenCursor<MDBROTransactionImpl, MDBROCursor>::MDBGenCursor;
+    MDBROCursor(const MDBROCursor &src) = delete;
+    MDBROCursor(MDBROCursor &&src) = default;
+    MDBROCursor &operator=(const MDBROCursor &src) = delete;
+    MDBROCursor &operator=(MDBROCursor &&src) = default;
+    ~MDBROCursor() = default;
+};
+
+class MDBRWCursor;
+
+/*!
+ * \brief The MDBRWTransactionImpl class wraps write operations.
+ */
+class LMDB_SAFE_EXPORT MDBRWTransactionImpl : public MDBROTransactionImpl {
+protected:
+    MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn);
+
+private:
+    static MDB_txn *openRWTransaction(MDBEnv *env, MDB_txn *parent, unsigned int flags);
+
+private:
+    std::vector<MDBRWCursor *> d_rw_cursors;
+
+    void closeRWCursors();
+    inline void closeRORWCursors()
+    {
+        closeROCursors();
+        closeRWCursors();
+    }
+
+public:
+    explicit MDBRWTransactionImpl(MDBEnv *parent, unsigned int flags = 0);
+
+    MDBRWTransactionImpl(const MDBRWTransactionImpl &rhs) = delete;
+    MDBRWTransactionImpl(MDBRWTransactionImpl &&rhs) = delete;
+    MDBRWTransactionImpl &operator=(const MDBRWTransactionImpl &rhs) = delete;
+    MDBRWTransactionImpl &operator=(MDBRWTransactionImpl &&rhs) = delete;
+
+    ~MDBRWTransactionImpl() override;
+
+    void commit() override;
+    void abort() override;
+
+    void clear(MDB_dbi dbi);
+
+    void put(MDB_dbi dbi, const MDBInVal &key, const MDBInVal &val, unsigned int flags = 0)
+    {
+        if (!d_txn)
+            throw LMDBError("Attempt to use a closed RW transaction for put");
+        if (const auto rc = mdb_put(d_txn, dbi, const_cast<MDB_val *>(&key.d_mdbval), const_cast<MDB_val *>(&val.d_mdbval), flags))
+            throw LMDBError("Putting data: ", rc);
+    }
+
+    int del(MDBDbi &dbi, const MDBInVal &key, const MDBInVal &val)
+    {
+        const auto rc = mdb_del(d_txn, dbi, const_cast<MDB_val *>(&key.d_mdbval), const_cast<MDB_val *>(&val.d_mdbval));
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Deleting data: ", rc);
+        return rc;
+    }
+
+    int del(MDBDbi &dbi, const MDBInVal &key)
+    {
+        const auto rc = mdb_del(d_txn, dbi, const_cast<MDB_val *>(&key.d_mdbval), 0);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Deleting data: ", rc);
+        return rc;
+    }
+
+    int get(MDBDbi &dbi, const MDBInVal &key, MDBOutVal &val)
+    {
+        if (!d_txn)
+            throw LMDBError("Attempt to use a closed RW transaction for get");
+
+        const auto rc = mdb_get(d_txn, dbi, const_cast<MDB_val *>(&key.d_mdbval), &val.d_mdbval);
+        if (rc && rc != MDB_NOTFOUND)
+            throw LMDBError("Getting data: ", rc);
+        return rc;
+    }
+
+    int get(MDBDbi &dbi, const MDBInVal &key, string_view &val)
+    {
+        MDBOutVal out;
+        const auto rc = get(dbi, key, out);
+        if (!rc)
+            val = out.get<string_view>();
+        return rc;
+    }
+
+    MDBDbi openDB(string_view dbname, unsigned int flags)
+    {
+        return MDBDbi(environment().d_env, d_txn, dbname, flags);
+    }
+
+    MDBRWCursor getRWCursor(const MDBDbi &);
+    MDBRWCursor getCursor(const MDBDbi &);
+
+    MDBRWTransaction getRWTransaction();
+    MDBROTransaction getROTransaction();
+};
+
+/*!
+ * \brief The MDBRWCursor class implements RW operations based on MDBGenCursor.
+ * \remarks
+ * - "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise
+ *   be closed when its transaction ends." This is a problem for us since it may means we are closing
+ *   the cursor twice, which is bad.
+ */
+class LMDB_SAFE_EXPORT MDBRWCursor : public MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor> {
+public:
+    MDBRWCursor() = default;
+    using MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>::MDBGenCursor;
+    MDBRWCursor(const MDBRWCursor &src) = delete;
+    MDBRWCursor(MDBRWCursor &&src) = default;
+    MDBRWCursor &operator=(const MDBRWCursor &src) = delete;
+    MDBRWCursor &operator=(MDBRWCursor &&src) = default;
+    ~MDBRWCursor() = default;
+
+    void put(const MDBOutVal &key, const MDBInVal &data)
+    {
+        if (const auto rc = mdb_cursor_put(*this, const_cast<MDB_val *>(&key.d_mdbval), const_cast<MDB_val *>(&data.d_mdbval), MDB_CURRENT))
+            throw LMDBError("Putting data via mdb_cursor_put: ", rc);
+    }
+
+    void put(const MDBOutVal &key, const MDBOutVal &data, unsigned int flags = 0)
+    {
+        if (const auto rc = mdb_cursor_put(*this, const_cast<MDB_val *>(&key.d_mdbval), const_cast<MDB_val *>(&data.d_mdbval), flags))
+            throw LMDBError("Putting data via mdb_cursor_put: ", rc);
+    }
+
+    void del(unsigned int flags = 0)
+    {
+        if (const auto rc = mdb_cursor_del(*this, flags))
+            throw LMDBError("Deleting data via mdb_cursor_del: ", rc);
+    }
+};
+
+} // namespace LMDBSafe
diff --git a/src/rgw/driver/posix/notify.cpp b/src/rgw/driver/posix/notify.cpp
new file mode 100644 (file)
index 0000000..8030caa
--- /dev/null
@@ -0,0 +1,21 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+#include "notify.h"
+#ifdef linux
+#include <sys/inotify.h>
+#endif
+
+namespace file::listing {
+
+  std::unique_ptr<Notify> Notify::factory(Notifiable* n, const std::string& bucket_root)
+  {
+#ifdef __linux__
+    return std::unique_ptr<Notify>(new Inotify(n, bucket_root));
+#else
+#error currently, rgw posix driver requires inotify
+#endif /* linux */
+    return nullptr;
+  } /* Notify::factory */
+
+} // namespace file::listing
diff --git a/src/rgw/driver/posix/notify.h b/src/rgw/driver/posix/notify.h
new file mode 100644 (file)
index 0000000..9f6088a
--- /dev/null
@@ -0,0 +1,255 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <memory>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+#include <optional>
+#include <filesystem>
+#include <limits>
+#include <cstdlib>
+#include "unordered_dense.h"
+#include <unistd.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/inotify.h>
+#include <sys/eventfd.h>
+#endif
+#include <fmt/format.h>
+
+namespace file::listing {
+
+  namespace sf = std::filesystem;
+
+  class Notifiable
+  {
+  public:
+    enum class EventType : uint8_t
+    {
+      ADD = 0,
+      REMOVE,
+      INVALIDATE
+    };
+
+    struct Event
+    {
+      EventType type;
+      std::optional<std::string_view> name;
+
+      Event(EventType type, std::optional<std::string_view> name) noexcept
+       : type(type), name(name)
+       {}
+
+      Event(Event&& rhs) noexcept
+       : type(rhs.type), name(rhs.name)
+       {}
+    };
+
+    virtual ~Notifiable() {};
+
+    virtual int notify(const std::string&, void*, const std::vector<Event>&) = 0;
+  };
+
+  class Notify
+  {
+    Notifiable* n;
+    sf::path rp;
+
+    Notify(Notifiable* n, const std::string& bucket_root)
+      : n(n), rp(bucket_root)
+      {}
+
+    friend class Inotify;
+  public:
+    static std::unique_ptr<Notify> factory(Notifiable* n, const std::string& bucket_root);
+    
+    virtual int add_watch(const std::string& dname, void* opaque) = 0;
+    virtual int remove_watch(const std::string& dname) = 0;
+    virtual ~Notify()
+      {}
+  }; /* Notify */
+
+#ifdef __linux__
+  class Inotify : public Notify
+  {
+    static constexpr uint32_t rd_size = 8192;
+    static constexpr uint32_t aw_mask = IN_ALL_EVENTS &
+      ~(IN_MOVE_SELF|IN_OPEN|IN_ACCESS|IN_ATTRIB|IN_CLOSE_WRITE|IN_CLOSE_NOWRITE|IN_MODIFY|IN_DELETE_SELF);
+
+    static constexpr uint64_t sig_shutdown = std::numeric_limits<uint64_t>::max() - 0xdeadbeef;
+
+    class WatchRecord
+    {
+    public:
+      int wd;
+      std::string name;
+      void* opaque;
+    public:
+      WatchRecord(int wd, const std::string& name, void* opaque) noexcept
+       : wd(wd), name(name), opaque(opaque)
+       {}
+
+      WatchRecord(WatchRecord&& wr) noexcept
+       : wd(wr.wd), name(wr.name), opaque(wr.opaque)
+       {}
+
+      WatchRecord& operator=(WatchRecord&& wr) {
+       wd = wr.wd;
+       name = std::move(wr.name);
+       opaque = wr.opaque;
+       return *this;
+      }
+    }; /* WatchRecord */
+
+    using wd_callback_map_t = ankerl::unordered_dense::map<int, WatchRecord>;
+    using wd_remove_map_t = ankerl::unordered_dense::map<std::string, int>;
+
+    int wfd, efd;
+    std::thread thrd;
+    wd_callback_map_t wd_callback_map;
+    wd_remove_map_t wd_remove_map;
+    bool shutdown{false};
+
+    class AlignedBuf
+    {
+      char* m;
+    public:
+      AlignedBuf() {
+       m = static_cast<char*>(aligned_alloc(__alignof__(struct inotify_event), rd_size));
+       if (! m) [[unlikely]] {
+         std::cerr << fmt::format("{} buffer allocation failure", __func__) << std::endl;
+         abort();
+       }
+      }
+      ~AlignedBuf() {
+       std::free(m);
+      }
+      char* get() {
+       return m;
+      }
+    }; /* AlignedBuf */
+    
+    void ev_loop() {
+      std::unique_ptr<AlignedBuf> up_buf = std::make_unique<AlignedBuf>();
+      struct inotify_event* event;
+      char* buf = up_buf.get()->get();
+      ssize_t len;
+      int npoll;
+
+      nfds_t nfds{2};
+      struct pollfd fds[2] = {{wfd, POLLIN}, {efd, POLLIN}};
+
+    restart:
+      while(! shutdown) {
+       npoll = poll(fds, nfds, -1); /* for up to 10 fds, poll is fast as epoll */
+       if (shutdown) {
+         return;
+       }
+       if (npoll == -1) {
+         if (errno == EINTR) {
+           continue;
+         }
+         // XXX
+       }
+       if (npoll > 0) {
+         len = read(wfd, buf, rd_size);
+         if (len == -1) {
+           continue; // hopefully, was EAGAIN
+         }
+         std::vector<Notifiable::Event> evec;
+         for (char* ptr = buf; ptr < buf + len;
+              ptr += sizeof(struct inotify_event) + event->len) {
+           event = reinterpret_cast<struct inotify_event*>(ptr);
+           const auto& it = wd_callback_map.find(event->wd);
+           //std::cout << fmt::format("event! {}", event->name) << std::endl;
+           if (it == wd_callback_map.end()) [[unlikely]] {
+             /* non-destructive race, it happens */
+             continue;
+           }
+           const auto& wr = it->second;
+           if (event->mask & IN_Q_OVERFLOW) [[unlikely]] {
+             /* cache blown, invalidate */
+             evec.clear();
+             evec.emplace_back(Notifiable::Event(Notifiable::EventType::INVALIDATE, std::nullopt));
+             n->notify(wr.name, wr.opaque, evec);
+             goto restart;
+           } else {
+             if ((event->mask & IN_CREATE) ||
+                 (event->mask & IN_MOVED_TO)) {
+               /* new object in dir */
+               evec.emplace_back(Notifiable::Event(Notifiable::EventType::ADD, event->name));
+             } else if ((event->mask & IN_DELETE) ||
+                        (event->mask & IN_MOVED_FROM)) {
+               /* object removed from dir */
+               evec.emplace_back(Notifiable::Event(Notifiable::EventType::REMOVE, event->name));
+             }
+           } /* !overflow */
+           if (evec.size() > 0) {
+             n->notify(wr.name, wr.opaque, evec);
+           }
+         } /* events */
+       } /* n > 0 */
+      }
+    } /* ev_loop */
+
+    Inotify(Notifiable* n, const std::string& bucket_root)
+      : Notify(n, bucket_root),
+       thrd(&Inotify::ev_loop, this)
+      {
+       wfd = inotify_init1(IN_NONBLOCK);
+       if (wfd == -1) {
+         std::cerr << fmt::format("{} inotify_init1 failed with {}", __func__, wfd) << std::endl;
+         exit(1);
+       }
+       efd = eventfd(0, EFD_NONBLOCK);
+      }
+
+    void signal_shutdown() {
+      uint64_t msg{sig_shutdown};
+      (void) write(efd, &msg, sizeof(uint64_t));
+    }
+
+    friend class Notify;
+  public:
+    virtual int add_watch(const std::string& dname, void* opaque) override {
+      sf::path wp{rp / dname};
+      int wd = inotify_add_watch(wfd, wp.c_str(), aw_mask);
+      if (wd == -1) {
+       std::cerr << fmt::format("{} inotify_add_watch {} failed with {}", __func__, dname, wd) << std::endl;
+      } else {
+       wd_callback_map.insert(wd_callback_map_t::value_type(wd, WatchRecord(wd, dname, opaque)));
+       wd_remove_map.insert(wd_remove_map_t::value_type(dname, wd));
+      }
+      return wd;
+    }
+
+    virtual int remove_watch(const std::string& dname) override {
+      int r{0};
+      const auto& elt = wd_remove_map.find(dname);
+      if (elt != wd_remove_map.end()) {
+       auto& wd = elt->second;
+       r = inotify_rm_watch(wfd, wd);
+       if (r == -1) {
+         std::cerr << fmt::format("{} inotify_rm_watch {} failed with {}", __func__, dname, wd) << std::endl;
+       }
+       wd_callback_map.erase(wd);
+       wd_remove_map.erase(std::string(dname));
+      }
+      return r;
+    }
+
+    virtual ~Inotify() {
+      shutdown = true;
+      signal_shutdown();
+      thrd.join();
+    }
+  };
+#endif /* linux */
+
+} // namespace file::listing
diff --git a/src/rgw/driver/posix/rgw_sal_posix.cc b/src/rgw/driver/posix/rgw_sal_posix.cc
new file mode 100644 (file)
index 0000000..036d02c
--- /dev/null
@@ -0,0 +1,3096 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright contributors to the Ceph project
+ *
+ * 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 "rgw_sal_posix.h"
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+#include "rgw_multi.h"
+#include "rgw_acl_s3.h"
+#include "include/scope_guard.h"
+
+#define dout_subsys ceph_subsys_rgw
+#define dout_context g_ceph_context
+
+namespace rgw { namespace sal {
+
+const int64_t READ_SIZE = 8 * 1024;
+const std::string ATTR_PREFIX = "user.X-RGW-";
+#define RGW_POSIX_ATTR_BUCKET_INFO "POSIX-Bucket-Info"
+#define RGW_POSIX_ATTR_MPUPLOAD "POSIX-Multipart-Upload"
+#define RGW_POSIX_ATTR_OWNER "POSIX-Owner"
+const std::string mp_ns = "multipart";
+const std::string MP_OBJ_PART_PFX = "part-";
+const std::string MP_OBJ_PART_FMT = "{:0>5}";
+const std::string MP_OBJ_HEAD_NAME = MP_OBJ_PART_PFX + "00000";
+
+static int decode_policy(CephContext* cct,
+                         bufferlist& bl,
+                         RGWAccessControlPolicy* policy)
+{
+  auto iter = bl.cbegin();
+  try {
+    policy->decode(iter);
+  } catch (buffer::error& err) {
+    ldout(cct, 0) << "ERROR: could not decode policy, caught buffer::error" << dendl;
+    return -EIO;
+  }
+  if (cct->_conf->subsys.should_gather<ceph_subsys_rgw, 15>()) {
+    ldout(cct, 15) << __func__ << " POSIX Read AccessControlPolicy";
+    RGWAccessControlPolicy_S3* s3policy = static_cast<RGWAccessControlPolicy_S3 *>(policy);
+    s3policy->to_xml(*_dout);
+    *_dout << dendl;
+  }
+  return 0;
+}
+
+static int rgw_op_get_bucket_policy_from_attr(const DoutPrefixProvider* dpp,
+                                             POSIXDriver* driver,
+                                             User* user,
+                                             Attrs& bucket_attrs,
+                                             RGWAccessControlPolicy* policy,
+                                             optional_yield y)
+{
+  auto aiter = bucket_attrs.find(RGW_ATTR_ACL);
+
+  if (aiter != bucket_attrs.end()) {
+    int ret = decode_policy(driver->ctx(), aiter->second, policy);
+    if (ret < 0)
+      return ret;
+  } else {
+    ldout(driver->ctx(), 0) << "WARNING: couldn't find acl header for bucket, generating default" << dendl;
+    /* object exists, but policy is broken */
+    int r = user->load_user(dpp, y);
+    if (r < 0)
+      return r;
+
+    policy->create_default(user->get_id(), user->get_display_name());
+  }
+  return 0;
+}
+
+static inline bool get_attr(Attrs& attrs, const char* name, bufferlist& bl)
+{
+  auto iter = attrs.find(name);
+  if (iter == attrs.end()) {
+    return false;
+  }
+
+  bl = iter->second;
+  return true;
+}
+
+static inline rgw_obj_key decode_obj_key(const char* fname)
+{
+  std::string dname, oname, ns;
+  dname = url_decode(fname);
+  rgw_obj_key::parse_index_key(dname, &oname, &ns);
+  rgw_obj_key key(oname, std::string(), ns);
+  return key;
+}
+
+static inline ceph::real_time from_statx_timestamp(const struct statx_timestamp& xts)
+{
+  struct timespec ts{xts.tv_sec, xts.tv_nsec};
+  return ceph::real_clock::from_timespec(ts);
+}
+
+static inline void bucket_statx_save(struct statx& stx, RGWBucketEnt& ent, ceph::real_time& mtime)
+{
+  mtime = ceph::real_clock::from_time_t(stx.stx_mtime.tv_sec);
+  ent.creation_time = ceph::real_clock::from_time_t(stx.stx_btime.tv_sec);
+  // TODO Calculate size of bucket (or save it somewhere?)
+  //ent.size = stx.stx_size;
+  //ent.size_rounded = stx.stx_blocks * 512;
+}
+
+static inline int copy_dir_fd(int old_fd)
+{
+  return openat(old_fd, ".", O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+}
+
+static int get_x_attrs(optional_yield y, const DoutPrefixProvider* dpp, int fd,
+                      Attrs& attrs, const std::string& display)
+{
+  char namebuf[64 * 1024]; // Max list size supported on linux
+  ssize_t buflen;
+  int ret;
+
+  buflen = flistxattr(fd, namebuf, sizeof(namebuf));
+  if (buflen < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not list attributes for " << display << ": "
+      << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  char *keyptr = namebuf;
+  while (buflen > 0) {
+    std::string value;
+    ssize_t vallen, keylen;
+    char* vp;
+
+    keylen = strlen(keyptr) + 1;
+    std::string key(keyptr);
+    std::string::size_type prefixloc = key.find(ATTR_PREFIX);
+
+    if (prefixloc == std::string::npos) {
+      /* Not one of our attributes */
+      buflen -= keylen;
+      keyptr += keylen;
+      continue;
+    }
+
+    /* Make a key that has just the attribute name */
+    key.erase(prefixloc, ATTR_PREFIX.length());
+
+    vallen = fgetxattr(fd, keyptr, nullptr, 0);
+    if (vallen < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not get attribute " << keyptr << " for " << display << ": " << cpp_strerror(ret) << dendl;
+      return -ret;
+    } else if (vallen == 0) {
+      /* No attribute value for this name */
+      buflen -= keylen;
+      keyptr += keylen;
+      continue;
+    }
+
+    value.reserve(vallen + 1);
+    vp = &value[0];
+
+    vallen = fgetxattr(fd, keyptr, vp, vallen);
+    if (vallen < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not get attribute " << keyptr << " for " << display << ": " << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    bufferlist bl;
+    bl.append(vp, vallen);
+    attrs.emplace(std::move(key), std::move(bl)); /* key and bl are r-value refs */
+
+    buflen -= keylen;
+    keyptr += keylen;
+  }
+
+  return 0;
+}
+
+static int write_x_attr(const DoutPrefixProvider* dpp, optional_yield y, int fd,
+                       const std::string& key, bufferlist& value,
+                       const std::string& display)
+{
+  int ret;
+  std::string attrname;
+
+  attrname = ATTR_PREFIX + key;
+
+  ret = fsetxattr(fd, attrname.c_str(), value.c_str(), value.length(), 0);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not write attribute " << attrname << " for " << display << ": " << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  return 0;
+}
+
+static int delete_directory(int parent_fd, const char* dname, bool delete_children,
+                    const DoutPrefixProvider* dpp)
+{
+  int ret;
+  int dir_fd = -1;
+  DIR *dir;
+  struct dirent *entry;
+
+  dir_fd = openat(parent_fd, dname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+  if (dir_fd < 0) {
+    dir_fd = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open subdir " << dname << ": "
+                      << cpp_strerror(dir_fd) << dendl;
+    return -dir_fd;
+  }
+
+  dir = fdopendir(dir_fd);
+  if (dir == NULL) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open bucket " << dname
+                      << " for listing: " << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  errno = 0;
+  while ((entry = readdir(dir)) != NULL) {
+    struct statx stx;
+
+    if ((entry->d_name[0] == '.' && entry->d_name[1] == '\0') ||
+        (entry->d_name[0] == '.' && entry->d_name[1] == '.' &&
+         entry->d_name[2] == '\0')) {
+      /* Skip . and .. */
+      errno = 0;
+      continue;
+    }
+
+    std::string_view d_name = entry->d_name;
+    bool is_mp = d_name.starts_with("." + mp_ns);
+    if (!is_mp && !delete_children) {
+      return -ENOTEMPTY;
+    }
+
+    ret = statx(dir_fd, entry->d_name, AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not stat object " << entry->d_name
+                        << ": " << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    if (S_ISDIR(stx.stx_mode)) {
+      /* Recurse */
+      ret = delete_directory(dir_fd, entry->d_name, true, dpp);
+      if (ret < 0) {
+        return ret;
+      }
+
+      continue;
+    }
+
+    /* Otherwise, unlink */
+    ret = unlinkat(dir_fd, entry->d_name, 0);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not remove file " << entry->d_name
+                        << ": " << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+  }
+
+  ret = unlinkat(parent_fd, dname, AT_REMOVEDIR);
+  if (ret < 0) {
+    ret = errno;
+    if (errno != ENOENT) {
+      ldpp_dout(dpp, 0) << "ERROR: could not remove bucket " << dname << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+  }
+
+  return 0;
+}
+
+int POSIXDriver::initialize(CephContext *cct, const DoutPrefixProvider *dpp)
+{
+  FilterDriver::initialize(cct, dpp);
+
+  base_path = g_conf().get_val<std::string>("rgw_posix_base_path");
+
+  ldpp_dout(dpp, 20) << "Initializing POSIX driver: " << base_path << dendl;
+
+  /* ordered listing cache */
+  bucket_cache.reset(
+    new BucketCache(
+      this, base_path,
+      g_conf().get_val<std::string>("rgw_posix_database_root"),
+      g_conf().get_val<int64_t>("rgw_posix_cache_max_buckets"),
+      g_conf().get_val<int64_t>("rgw_posix_cache_lanes"),
+      g_conf().get_val<int64_t>("rgw_posix_cache_partitions"),
+      g_conf().get_val<int64_t>("rgw_posix_cache_lmdb_count")));
+
+  root_fd = openat(-1, base_path.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+  if (root_fd == -1) {
+    int err = errno;
+    if (err == ENOTDIR) {
+      ldpp_dout(dpp, 0) << " ERROR: base path (" << base_path
+       << "): was not a directory." << dendl;
+      return -err;
+    } else if (err == ENOENT) {
+      err = mkdir(base_path.c_str(), S_IRWXU);
+      if (err < 0) {
+       err = errno;
+       ldpp_dout(dpp, 0) << " ERROR: could not create base path ("
+         << base_path << "): " << cpp_strerror(err) << dendl;
+       return -err;
+      }
+      root_fd = ::open(base_path.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+    }
+  }
+  if (root_fd == -1) {
+    int err = errno;
+    ldpp_dout(dpp, 0) << " ERROR: could not open base path ("
+      << base_path << "): " << cpp_strerror(err) << dendl;
+    return -err;
+  }
+
+  ldpp_dout(dpp, 20) << "SUCCESS" << dendl;
+  return 0;
+}
+
+std::unique_ptr<User> POSIXDriver::get_user(const rgw_user &u)
+{
+  std::unique_ptr<User> user = next->get_user(u);
+
+  return std::make_unique<POSIXUser>(std::move(user), this);
+}
+
+int POSIXDriver::get_user_by_access_key(const DoutPrefixProvider* dpp, const std::string& key, optional_yield y, std::unique_ptr<User>* user)
+{
+  std::unique_ptr<User> nu;
+  int ret;
+
+  ret = next->get_user_by_access_key(dpp, key, y, &nu);
+  if (ret != 0)
+    return ret;
+
+  User* u = new POSIXUser(std::move(nu), this);
+  user->reset(u);
+  return 0;
+}
+
+int POSIXDriver::get_user_by_email(const DoutPrefixProvider* dpp, const std::string& email, optional_yield y, std::unique_ptr<User>* user)
+{
+  std::unique_ptr<User> nu;
+  int ret;
+
+  ret = next->get_user_by_email(dpp, email, y, &nu);
+  if (ret != 0)
+    return ret;
+
+  User* u = new POSIXUser(std::move(nu), this);
+  user->reset(u);
+  return 0;
+}
+
+int POSIXDriver::get_user_by_swift(const DoutPrefixProvider* dpp, const std::string& user_str, optional_yield y, std::unique_ptr<User>* user)
+{
+  std::unique_ptr<User> nu;
+  int ret;
+
+  ret = next->get_user_by_swift(dpp, user_str, y, &nu);
+  if (ret != 0)
+    return ret;
+
+  User* u = new POSIXUser(std::move(nu), this);
+  user->reset(u);
+  return 0;
+}
+
+std::unique_ptr<Object> POSIXDriver::get_object(const rgw_obj_key& k)
+{
+  return std::make_unique<POSIXObject>(this, k);
+}
+
+int POSIXDriver::get_bucket(const DoutPrefixProvider* dpp, User* u, const rgw_bucket& b, std::unique_ptr<Bucket>* bucket, optional_yield y)
+{
+  int ret;
+  Bucket* bp;
+
+  bp = new POSIXBucket(this, root_fd, b, u);
+  ret = bp->load_bucket(dpp, y);
+  if (ret < 0) {
+    delete bp;
+    return ret;
+  }
+
+  bucket->reset(bp);
+  return 0;
+}
+
+int POSIXDriver::get_bucket(User* u, const RGWBucketInfo& i, std::unique_ptr<Bucket>* bucket)
+{
+  Bucket* bp;
+
+  bp = new POSIXBucket(this, root_fd, i, u);
+  /* Don't need to fetch the bucket info, use the provided one */
+
+  bucket->reset(bp);
+  return 0;
+}
+
+int POSIXDriver::get_bucket(const DoutPrefixProvider* dpp, User* u, const std::string& tenant, const std::string& name, std::unique_ptr<Bucket>* bucket, optional_yield y)
+{
+  rgw_bucket b;
+
+  b.tenant = tenant;
+  b.name = name;
+
+  return get_bucket(dpp, u, b, bucket, y);
+}
+
+std::string POSIXDriver::zone_unique_trans_id(const uint64_t unique_num)
+{
+  char buf[41]; /* 2 + 21 + 1 + 16 (timestamp can consume up to 16) + 1 */
+  time_t timestamp = time(NULL);
+
+  snprintf(buf, sizeof(buf), "tx%021llx-%010llx",
+           (unsigned long long)unique_num,
+           (unsigned long long)timestamp);
+
+  return std::string(buf);
+}
+std::unique_ptr<Writer> POSIXDriver::get_append_writer(const DoutPrefixProvider *dpp,
+                                 optional_yield y,
+                                 rgw::sal::Object* _head_obj,
+                                 const rgw_user& owner,
+                                 const rgw_placement_rule *ptail_placement_rule,
+                                 const std::string& unique_tag,
+                                 uint64_t position,
+                                 uint64_t *cur_accounted_size)
+{
+  std::unique_ptr<Writer> writer = next->get_append_writer(dpp, y, _head_obj,
+                                                          owner, ptail_placement_rule,
+                                                          unique_tag, position,
+                                                          cur_accounted_size);
+
+  return std::make_unique<FilterWriter>(std::move(writer), std::move(_head_obj));
+}
+
+std::unique_ptr<Writer> POSIXDriver::get_atomic_writer(const DoutPrefixProvider *dpp,
+                                 optional_yield y,
+                                 rgw::sal::Object* _head_obj,
+                                 const rgw_user& owner,
+                                 const rgw_placement_rule *ptail_placement_rule,
+                                 uint64_t olh_epoch,
+                                 const std::string& unique_tag)
+{
+
+  return std::make_unique<POSIXAtomicWriter>(dpp, y, _head_obj, this, owner, ptail_placement_rule, olh_epoch, unique_tag);
+}
+
+void POSIXDriver::finalize(void)
+{
+  next->finalize();
+}
+
+void POSIXDriver::register_admin_apis(RGWRESTMgr* mgr)
+{
+  return next->register_admin_apis(mgr);
+}
+
+std::unique_ptr<Notification> POSIXDriver::get_notification(rgw::sal::Object* obj,
+                             rgw::sal::Object* src_obj, struct req_state* s,
+                             rgw::notify::EventType event_type, optional_yield y,
+                             const std::string* object_name)
+{
+  return next->get_notification(obj, src_obj, s, event_type, y, object_name);
+}
+
+std::unique_ptr<Notification> POSIXDriver::get_notification(const DoutPrefixProvider* dpp,
+                              rgw::sal::Object* obj, rgw::sal::Object* src_obj,
+                              rgw::notify::EventType event_type,
+                              rgw::sal::Bucket* _bucket,
+                              std::string& _user_id, std::string& _user_tenant,
+                              std::string& _req_id, optional_yield y)
+{
+  return next->get_notification(dpp, obj, src_obj, event_type, _bucket, _user_id, _user_tenant, _req_id, y);
+}
+
+int POSIXDriver::close()
+{
+  if (root_fd < 0) {
+    return 0;
+  }
+
+  ::close(root_fd);
+  root_fd = -1;
+
+  return 0;
+}
+
+int POSIXUser::list_buckets(const DoutPrefixProvider* dpp, const std::string& marker,
+                            const std::string& end_marker, uint64_t max,
+                            bool need_stats, BucketList &buckets, optional_yield y)
+{
+  DIR* dir;
+  struct dirent* entry;
+  int dfd;
+  int ret;
+
+  buckets.clear();
+
+  /* it's not sufficient to dup(root_fd), as as the new fd would share
+   * the file position of root_fd */
+  dfd = copy_dir_fd(driver->get_root_fd());
+  if (dfd == -1) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open root to list buckets: "
+      << cpp_strerror(ret) << dendl;
+    return -errno;
+  }
+
+  dir = fdopendir(dfd);
+  if (dir == NULL) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open root to list buckets: "
+      << cpp_strerror(ret) << dendl;
+    close(dfd);
+    return -ret;
+  }
+
+  auto cleanup_guard = make_scope_guard(
+    [&dir]
+      {
+       closedir(dir);
+       // dfd is also closed
+      }
+    );
+
+  errno = 0;
+  while ((entry = readdir(dir)) != NULL) {
+    struct statx stx;
+
+    ret = statx(driver->get_root_fd(), entry->d_name, AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not stat object " << entry->d_name << ": "
+       << cpp_strerror(ret) << dendl;
+      buckets.clear();
+      return -ret;
+    }
+
+    if (!S_ISDIR(stx.stx_mode)) {
+      /* Not a bucket, skip it */
+      errno = 0;
+      continue;
+    }
+    if (entry->d_name[0] == '.') {
+      /* Skip dotfiles */
+      errno = 0;
+      continue;
+    }
+
+    /* TODO Use stat_to_ent */
+    //RGWBucketEnt ent;
+    //ent.bucket.name = decode_name(entry->d_name);
+    //bucket_statx_save(stx, ent, mtime);
+    RGWBucketInfo info;
+    info.bucket.name = url_decode(entry->d_name);
+    info.owner.id = std::to_string(stx.stx_uid); // TODO convert to owner name
+    info.creation_time = from_statx_timestamp(stx.stx_btime);
+
+    std::unique_ptr<rgw::sal::Bucket> bucket;
+    ret = driver->get_bucket(this, info, &bucket);
+    if (ret < 0) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket " << info.bucket << ": "
+       << cpp_strerror(ret) << dendl;
+      buckets.clear();
+      return -ret;
+    }
+
+    buckets.add(std::move(bucket));
+
+    errno = 0;
+  }
+  ret = errno;
+  if (ret != 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not list buckets for " << get_display_name() << ": "
+      << cpp_strerror(ret) << dendl;
+    buckets.clear();
+    return -ret;
+  }
+
+  return 0;
+}
+
+int POSIXUser::create_bucket(const DoutPrefixProvider* dpp,
+                             const rgw_bucket& b,
+                             const std::string& zonegroup_id,
+                             rgw_placement_rule& placement_rule,
+                             std::string& swift_ver_location,
+                             const RGWQuotaInfo * pquota_info,
+                             const RGWAccessControlPolicy& policy,
+                             Attrs& attrs,
+                             RGWBucketInfo& binfo,
+                             obj_version& ep_objv,
+                             bool exclusive,
+                             bool obj_lock_enabled,
+                             bool* existed,
+                             req_info& req_info,
+                             std::unique_ptr<Bucket>* bucket_out,
+                             optional_yield y)
+{
+  /* Check for existence */
+  {
+    std::unique_ptr<rgw::sal::Bucket> bucket;
+
+    int ret = driver->get_bucket(dpp, this, b, &bucket, y);
+    if (ret >= 0) {
+      *existed = true;
+      // Bucket exists.  Check owner comparison
+      if (bucket->get_info().owner.compare(this->get_id()) != 0) {
+       return -EEXIST;
+      }
+      // Don't allow changes to ACL policy
+      RGWAccessControlPolicy old_policy(driver->ctx());
+      ret = rgw_op_get_bucket_policy_from_attr(
+          dpp, driver, this, bucket->get_attrs(), &old_policy, y);
+      if (ret >= 0 && old_policy != policy) {
+        bucket_out->swap(bucket);
+        return -EEXIST;
+      }
+    } else {
+      *existed = false;
+    }
+  }
+
+  binfo.bucket = b;
+  binfo.owner = get_id();
+  binfo.zonegroup = zonegroup_id;
+  binfo.placement_rule = placement_rule;
+  binfo.swift_ver_location = swift_ver_location;
+  binfo.swift_versioning = (!swift_ver_location.empty());
+  binfo.requester_pays = false;
+  binfo.creation_time = ceph::real_clock::now();
+  if (pquota_info) {
+    binfo.quota = *pquota_info;
+  }
+
+  POSIXBucket* fb = new POSIXBucket(driver, driver->get_root_fd(), binfo, this);
+
+  int ret = fb->set_attrs(attrs);
+  if (ret < 0) {
+    delete fb;
+    return  ret;
+  }
+
+  ret = fb->create(dpp, y, existed);
+  if (ret < 0) {
+    delete fb;
+    return  ret;
+  }
+
+  bucket_out->reset(fb);
+  return 0;
+}
+
+int POSIXUser::read_attrs(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  return next->read_attrs(dpp, y);
+}
+
+int POSIXUser::merge_and_store_attrs(const DoutPrefixProvider* dpp,
+                                     Attrs& new_attrs, optional_yield y)
+{
+  return next->merge_and_store_attrs(dpp, new_attrs, y);
+}
+
+int POSIXUser::load_user(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  return next->load_user(dpp, y);
+}
+
+int POSIXUser::store_user(const DoutPrefixProvider* dpp, optional_yield y, bool exclusive, RGWUserInfo* old_info)
+{
+  return next->store_user(dpp, y, exclusive, old_info);
+}
+
+int POSIXUser::remove_user(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  return next->remove_user(dpp, y);
+}
+
+std::unique_ptr<Object> POSIXBucket::get_object(const rgw_obj_key& k)
+{
+  return std::make_unique<POSIXObject>(driver, k, this);
+}
+
+int POSIXObject::fill_bde(const DoutPrefixProvider *dpp, optional_yield y, rgw_bucket_dir_entry& bde)
+{
+    std::unique_ptr<User> owner;
+    (void)get_owner(dpp, y, &owner);
+
+    get_key().get_index_key(&bde.key);
+    bde.ver.pool = 1;
+    bde.ver.epoch = 1;
+    bde.exists = true;
+    bde.meta.category = RGWObjCategory::Main;
+    bde.meta.size = get_obj_size();
+    bde.meta.mtime = get_mtime();
+    if (owner) {
+      bde.meta.owner = owner->get_id().to_str();
+      bde.meta.owner_display_name = owner->get_display_name();
+    } else {
+      bde.meta.owner = "unknown";
+      bde.meta.owner_display_name = "unknown";
+    }
+    bde.meta.accounted_size = get_obj_size();
+    bde.meta.storage_class = RGW_STORAGE_CLASS_STANDARD;
+    bde.meta.appendable = true;
+    bufferlist etag_bl;
+    if (rgw::sal::get_attr(get_attrs(), RGW_ATTR_ETAG, etag_bl)) {
+      bde.meta.etag = etag_bl.to_str();
+    }
+
+    return 0;
+}
+
+int POSIXDriver::mint_listing_entry(const std::string &bname,
+                                    rgw_bucket_dir_entry &bde) {
+    std::unique_ptr<Bucket> b;
+    std::unique_ptr<Object> obj;
+    POSIXObject *pobj;
+    int ret;
+
+    ret = get_bucket(nullptr, nullptr, std::string(), bname, &b, null_yield);
+    if (ret < 0)
+      return ret;
+
+    obj = b->get_object(decode_obj_key(bde.key.name.c_str()));
+    pobj = static_cast<POSIXObject *>(obj.get());
+
+    if (!pobj->exists(nullptr)) {
+      ret = errno;
+      return -ret;
+    }
+
+    ret = pobj->get_obj_attrs(null_yield, nullptr);
+    if (ret < 0)
+      return ret;
+
+    ret = pobj->fill_bde(nullptr, null_yield, bde);
+    if (ret < 0)
+      return ret;
+
+    return 0;
+}
+int POSIXBucket::fill_cache(const DoutPrefixProvider* dpp, optional_yield y,
+                           fill_cache_cb_t cb)
+{
+  int ret = for_each(dpp, [this, &cb, &dpp, &y](const char* name) {
+    int ret;
+    std::unique_ptr<Object> obj;
+    POSIXObject* pobj;
+
+    if (name[0] == '.') {
+      /* Skip dotfiles */
+      return 0;
+    }
+
+    obj = get_object(decode_obj_key(name));
+    pobj = static_cast<POSIXObject*>(obj.get());
+
+    if (!pobj->exists(dpp)) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not stat object " << name << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    ret = pobj->get_obj_attrs(y, dpp);
+    if (ret < 0)
+      return ret;
+
+    rgw_bucket_dir_entry bde{};
+    ret = pobj->fill_bde(dpp, y, bde);
+    if (ret < 0)
+      return ret;
+
+    cb(dpp, bde);
+
+    return 0;
+  });
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not list bucket " << get_name() << ": "
+      << cpp_strerror(ret) << dendl;
+    return ret;
+  }
+
+  return 0;
+}
+
+// TODO  marker and other params
+int POSIXBucket::list(const DoutPrefixProvider* dpp, ListParams& params,
+                     int max, ListResults& results, optional_yield y)
+{
+  int count{0};
+  bool in_prefix{false};
+  // Names in the cache are in OID format
+  {
+    rgw_obj_key key(params.marker);
+    params.marker = key.get_oid();
+    key.set(params.prefix);
+    params.prefix = key.get_oid();
+  }
+  // Names are url_encoded, so encode prefix and delimiter
+  // Names seem to not be url_encoded in cache
+  //params.prefix = url_encode(params.prefix);
+  //params.delim = url_encode(params.delim);
+  if (max <= 0) {
+    return 0;
+  }
+
+  int ret = driver->get_bucket_cache()->list_bucket(
+    dpp, y, this, params.marker.name, [&](const rgw_bucket_dir_entry& bde) -> bool
+      {
+       std::string ns;
+       // bde.key can be encoded with the namespace.  Decode it here
+        if (!params.marker.empty() && params.marker == bde.key.name) {
+         // Skip marker
+         return true;
+       }
+       if (!params.prefix.empty()) {
+         // We have a prefix, only match
+          if (!bde.key.name.starts_with(params.prefix)) {
+            // Prefix doesn't match; skip
+           if (in_prefix) {
+              return false;
+            }
+            return true;
+          }
+         // Prefix matches
+         if (params.delim.empty()) {
+           // No delimiter, add matches
+            results.next_marker.set(bde.key);
+            results.objs.push_back(bde);
+           count++;
+           if (count >= max) {
+              results.is_truncated = true;
+             return false;
+           }
+           return true;
+          }
+          auto delim_pos = bde.key.name.find(params.delim, params.prefix.size());
+          if (delim_pos == std::string_view::npos) {
+           // Straight prefix match
+            results.next_marker.set(bde.key);
+            results.objs.push_back(bde);
+           count++;
+           if (count >= max) {
+              results.is_truncated = true;
+             return false;
+           }
+           return true;
+         }
+          std::string prefix_key =
+              bde.key.name.substr(0, delim_pos + params.delim.length());
+         rgw_obj_key::parse_raw_oid(prefix_key, &results.next_marker);
+         // Use results.next_marker.name for prefix_key, since it's been decoded
+          if (!results.common_prefixes.contains(results.next_marker.name)) {
+            results.common_prefixes[results.next_marker.name] = true;
+            count++; // Count will be checked when we exit prefix
+            if (in_prefix) {
+              // We've hit the next prefix entry.  Check count
+              if (count >= max) {
+                results.is_truncated = true;
+                // Time to stop
+                return false;
+             }
+            }
+          }
+          in_prefix = true;
+          return true;
+        }
+        if (!params.delim.empty()) {
+         // Delimiter, but no prefix
+         auto delim_pos = bde.key.name.find(params.delim) ;
+          if (delim_pos == std::string_view::npos) {
+           // Delimiter doesn't match, insert
+            results.next_marker.set(bde.key);
+            results.objs.push_back(bde);
+           count++;
+           if (count >= max) {
+              results.is_truncated = true;
+             return false;
+           }
+           return true;
+          }
+          std::string prefix_key =
+              bde.key.name.substr(0, delim_pos + params.delim.length());
+          if (!params.marker.empty() && params.marker == prefix_key) {
+            // Skip marker
+            return true;
+          }
+         std::string decoded_key;
+         rgw_obj_key::parse_index_key(prefix_key, &decoded_key, &ns);
+          if (!results.common_prefixes.contains(decoded_key)) {
+           if (in_prefix) {
+             // New prefix, check the count
+             count++;
+              if (count >= max) {
+                results.is_truncated = true;
+                return false;
+              }
+            }
+           in_prefix = true;
+            results.common_prefixes[decoded_key] = true;
+           // Fallthrough
+          }
+         results.next_marker.name = decoded_key;
+         return true;
+        }
+
+        results.next_marker.set(bde.key);
+        results.objs.push_back(bde);
+        count++;
+        if (count >= max) {
+          results.is_truncated = true;
+          return false;
+        }
+        return true;
+    });
+
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not list bucket " << get_name() << ": "
+      << cpp_strerror(ret) << dendl;
+    results.objs.clear();
+    return ret;
+  }
+
+  return 0;
+}
+
+int POSIXBucket::merge_and_store_attrs(const DoutPrefixProvider* dpp,
+                                       Attrs& new_attrs, optional_yield y)
+{
+  for (auto& it : new_attrs) {
+         attrs[it.first] = it.second;
+  }
+
+  return write_attrs(dpp, y);
+}
+
+int POSIXBucket::remove_bucket(const DoutPrefixProvider* dpp,
+                               bool delete_children,
+                               bool forward_to_master,
+                               req_info* req_info,
+                               optional_yield y)
+{
+  return delete_directory(parent_fd, get_fname().c_str(),
+                         delete_children, dpp);
+}
+
+int POSIXBucket::remove_bucket_bypass_gc(int concurrent_max,
+                                        bool keep_index_consistent,
+                                        optional_yield y,
+                                        const DoutPrefixProvider *dpp)
+{
+  return remove_bucket(dpp, true, false, nullptr, y);
+}
+
+int POSIXBucket::load_bucket(const DoutPrefixProvider* dpp, optional_yield y,
+                             bool get_stats)
+{
+  int ret;
+
+  if (get_name()[0] == '.') {
+    /* Skip dotfiles */
+    return -ERR_INVALID_OBJECT_NAME;
+  }
+  ret = stat(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  bucket_statx_save(stx, ent, mtime);
+  info.creation_time = ent.creation_time;
+
+  if (owner) {
+    info.owner = owner->get_id();
+  }
+
+  ret = open(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+  get_x_attrs(y, dpp, dir_fd, attrs, get_name());
+
+  bufferlist bl;
+  if (get_attr(attrs, RGW_POSIX_ATTR_BUCKET_INFO, bl)) {
+    // Proper bucket with saved info
+    try {
+      auto bufit = bl.cbegin();
+      decode(info, bufit);
+    } catch (buffer::error &err) {
+      ldout(driver->ctx(), 0) << "ERROR: " << __func__ << ": failed to decode " RGW_POSIX_ATTR_BUCKET_INFO " attr" << dendl;
+      return -EINVAL;
+    }
+    // info isn't stored in attrs
+    attrs.erase(RGW_POSIX_ATTR_BUCKET_INFO);
+  } else {
+    // TODO dang: fake info up (UID to owner conversion?)
+  }
+
+  info.creation_time = ent.creation_time;
+
+  return 0;
+}
+
+int POSIXBucket::set_acl(const DoutPrefixProvider* dpp,
+                        RGWAccessControlPolicy& acl,
+                        optional_yield y)
+{
+  bufferlist aclbl;
+
+  acls = acl;
+  acl.encode(aclbl);
+
+  attrs[RGW_ATTR_ACL] = aclbl;
+  info.owner = acl.get_owner().get_id();
+
+  return write_attrs(dpp, y);
+}
+
+int POSIXBucket::read_stats(const DoutPrefixProvider *dpp,
+                           const bucket_index_layout_generation& idx_layout,
+                           int shard_id, std::string* bucket_ver, std::string* master_ver,
+                           std::map<RGWObjCategory, RGWStorageStats>& stats,
+                           std::string* max_marker, bool* syncstopped)
+{
+  return 0;
+}
+
+int POSIXBucket::read_stats_async(const DoutPrefixProvider *dpp,
+                                 const bucket_index_layout_generation& idx_layout,
+                                 int shard_id, RGWGetBucketStats_CB* ctx)
+{
+  return 0;
+}
+
+int POSIXBucket::sync_user_stats(const DoutPrefixProvider *dpp, optional_yield y)
+{
+  return 0;
+}
+
+int POSIXBucket::update_container_stats(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  /* Force re-stat */
+  stat_done = false;
+  int ret = stat(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  bucket_statx_save(stx, ent, mtime);
+  info.creation_time = ent.creation_time;
+  ent.count = 0;
+  ent.size = 0;
+
+  // TODO dang: store size/count in attributes
+  ret = for_each(dpp, [this, &dpp](const char* name) {
+    int ret;
+    struct statx lstx;
+
+    if (name[0] == '.') {
+      /* Skip dotfiles */
+      return 0;
+    }
+
+    ret = statx(dir_fd, name, AT_SYMLINK_NOFOLLOW, STATX_ALL, &lstx);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not stat object " << name << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    if (S_ISREG(lstx.stx_mode) || S_ISDIR(lstx.stx_mode)) {
+      ent.count++;
+      ent.size += lstx.stx_size;
+    }
+
+    return 0;
+  });
+
+  return 0;
+}
+
+int POSIXBucket::check_bucket_shards(const DoutPrefixProvider* dpp, optional_yield y)
+{
+      return 0;
+}
+
+int POSIXBucket::chown(const DoutPrefixProvider* dpp, User& new_user, optional_yield y)
+{
+  /* TODO map user to UID/GID, and change it */
+  return 0;
+}
+
+int POSIXBucket::put_info(const DoutPrefixProvider* dpp, bool exclusive, ceph::real_time _mtime, optional_yield y)
+{
+  mtime = _mtime;
+
+  struct timespec ts[2];
+  ts[0].tv_nsec = UTIME_OMIT;
+  ts[1] = ceph::real_clock::to_timespec(mtime);
+  int ret = utimensat(parent_fd, get_fname().c_str(), ts, AT_SYMLINK_NOFOLLOW);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not set mtime on bucket " << get_name() << ": "
+      << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  return write_attrs(dpp, y);
+}
+
+int POSIXBucket::write_attrs(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  int ret = open(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  // Bucket info is stored as an attribute, but on in attrs[]
+  bufferlist bl;
+  encode(info, bl);
+  ret = write_x_attr(dpp, y, dir_fd, RGW_POSIX_ATTR_BUCKET_INFO, bl, get_name());
+  if (ret < 0) {
+    return ret;
+  }
+
+  for (auto& it : attrs) {
+    ret = write_x_attr(dpp, y, dir_fd, it.first, it.second, get_name());
+    if (ret < 0) {
+      return ret;
+    }
+  }
+  return 0;
+}
+
+int POSIXBucket::check_empty(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  DIR* dir;
+  struct dirent* entry;
+  int ret;
+
+  ret = open(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  dir = fdopendir(dir_fd);
+  if (dir == NULL) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open bucket " << get_name() << " for listing: "
+      << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  errno = 0;
+  while ((entry = readdir(dir)) != NULL) {
+    if (entry->d_name[0] != '.') {
+      return -ENOTEMPTY;
+    }
+    if (entry->d_name[1] == '.' || entry->d_name[1] == '\0') {
+      continue;
+    }
+  }
+  return 0;
+}
+
+int POSIXBucket::check_quota(const DoutPrefixProvider *dpp, RGWQuota& quota, uint64_t obj_size,
+                               optional_yield y, bool check_size_only)
+{
+    return 0;
+}
+
+int POSIXBucket::try_refresh_info(const DoutPrefixProvider* dpp, ceph::real_time* pmtime, optional_yield y)
+{
+  int ret = update_container_stats(dpp, y);
+  if (ret < 0) {
+    return ret;
+  }
+
+  *pmtime = mtime;
+
+  ret = open(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+  get_x_attrs(y, dpp, dir_fd, attrs, get_name());
+
+  return 0;
+}
+
+int POSIXBucket::read_usage(const DoutPrefixProvider *dpp, uint64_t start_epoch,
+                           uint64_t end_epoch, uint32_t max_entries,
+                           bool* is_truncated, RGWUsageIter& usage_iter,
+                           std::map<rgw_user_bucket, rgw_usage_log_entry>& usage)
+{
+  return 0;
+}
+
+int POSIXBucket::trim_usage(const DoutPrefixProvider *dpp, uint64_t start_epoch, uint64_t end_epoch, optional_yield y)
+{
+  return 0;
+}
+
+int POSIXBucket::remove_objs_from_index(const DoutPrefixProvider *dpp, std::list<rgw_obj_index_key>& objs_to_unlink)
+{
+  return 0;
+}
+
+int POSIXBucket::check_index(const DoutPrefixProvider *dpp, std::map<RGWObjCategory, RGWStorageStats>& existing_stats, std::map<RGWObjCategory, RGWStorageStats>& calculated_stats)
+{
+  return 0;
+}
+
+int POSIXBucket::rebuild_index(const DoutPrefixProvider *dpp)
+{
+  return 0;
+}
+
+int POSIXBucket::set_tag_timeout(const DoutPrefixProvider *dpp, uint64_t timeout)
+{
+  return 0;
+}
+
+int POSIXBucket::purge_instance(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  return 0;
+}
+
+std::unique_ptr<MultipartUpload> POSIXBucket::get_multipart_upload(
+                                 const std::string& oid,
+                                 std::optional<std::string> upload_id,
+                                 ACLOwner owner, ceph::real_time mtime)
+{
+  return std::make_unique<POSIXMultipartUpload>(driver, this, oid, upload_id, owner, mtime);
+}
+
+int POSIXBucket::list_multiparts(const DoutPrefixProvider *dpp,
+                                 const std::string& prefix,
+                                 std::string& marker,
+                                 const std::string& delim,
+                                 const int& max_uploads,
+                                 std::vector<std::unique_ptr<MultipartUpload>>& uploads,
+                                 std::map<std::string, bool> *common_prefixes,
+                                 bool *is_truncated, optional_yield y)
+{
+  //std::vector<std::unique_ptr<MultipartUpload>> nup;
+  //int ret;
+//
+  //ret = next->list_multiparts(dpp, prefix, marker, delim, max_uploads, nup,
+                             //common_prefixes, is_truncated);
+  //if (ret < 0)
+    //return ret;
+//
+  //for (auto& ent : nup) {
+    //uploads.emplace_back(std::make_unique<POSIXMultipartUpload>(std::move(ent), this, driver));
+  //}
+
+  return 0;
+}
+
+int POSIXBucket::abort_multiparts(const DoutPrefixProvider* dpp, CephContext* cct, optional_yield y)
+{
+  return 0;
+}
+
+int POSIXBucket::create(const DoutPrefixProvider* dpp, optional_yield y, bool* existed)
+{
+  int ret = mkdirat(parent_fd, get_fname().c_str(), S_IRWXU);
+  if (ret < 0) {
+    ret = errno;
+    if (ret != EEXIST) {
+      if (dpp)
+       ldpp_dout(dpp, 0) << "ERROR: could not create bucket " << get_name() << ": "
+         << cpp_strerror(ret) << dendl;
+      return -ret;
+    } else if (existed != nullptr) {
+      *existed = true;
+    }
+    return ret;
+  }
+
+  return write_attrs(dpp, y);
+}
+
+std::string POSIXBucket::get_fname()
+{
+  std::string name;
+
+  if (ns)
+    name = "." + *ns + "_" + url_encode(get_name(), true);
+  else
+    name = url_encode(get_name(), true);
+
+  return name;
+}
+
+int POSIXBucket::get_shadow_bucket(const DoutPrefixProvider* dpp, optional_yield y,
+                                  const std::string& ns,
+                                  const std::string& tenant, const std::string& name,
+                                  bool create, std::unique_ptr<POSIXBucket>* shadow)
+{
+  std::optional<std::string> ons{std::nullopt};
+  int ret;
+  POSIXBucket* bp;
+  rgw_bucket b;
+
+  b.tenant = tenant;
+  b.name = name;
+
+  if (!ns.empty()) {
+    ons = ns;
+  }
+
+  open(dpp);
+
+  bp = new POSIXBucket(driver, dir_fd, b, owner, ons);
+  ret = bp->load_bucket(dpp, y);
+  if (ret == -ENOENT && create) {
+    /* Create it if it doesn't exist */
+    ret = bp->create(dpp, y, nullptr);
+  }
+  if (ret < 0) {
+    delete bp;
+    return ret;
+  }
+
+  shadow->reset(bp);
+  return 0;
+}
+
+template <typename F>
+int POSIXBucket::for_each(const DoutPrefixProvider* dpp, const F& func)
+{
+  DIR* dir;
+  struct dirent* entry;
+  int ret;
+
+  ret = open(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  dir = fdopendir(dir_fd);
+  if (dir == NULL) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open bucket " << get_name() << " for listing: "
+      << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  rewinddir(dir);
+
+  while ((entry = readdir(dir)) != NULL) {
+    int r = func(entry->d_name);
+    if (r < 0) {
+      ret = r;
+    }
+  }
+
+  if (ret == -EAGAIN) {
+    /* Limit reached */
+    ret = 0;
+  }
+  return ret;
+}
+
+int POSIXBucket::open(const DoutPrefixProvider* dpp)
+{
+  if (dir_fd >= 0) {
+    return 0;
+  }
+
+  int ret = openat(parent_fd, get_fname().c_str(),
+                  O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open bucket " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  dir_fd = ret;
+
+  return 0;
+}
+
+// This is for renaming a shadow bucket to a MP object.  It won't work work for a normal bucket
+int POSIXBucket::rename(const DoutPrefixProvider* dpp, optional_yield y, Object* target_obj)
+{
+  POSIXObject *to = static_cast<POSIXObject*>(target_obj);
+  POSIXBucket *tb = static_cast<POSIXBucket*>(target_obj->get_bucket());
+  std::string src_fname = get_fname();
+  std::string dst_fname = to->get_fname();
+  int flags = 0;
+
+  if (to->exists(dpp)) {
+    flags = RENAME_EXCHANGE;
+  }
+  // swap
+  int ret = renameat2(tb->get_dir_fd(dpp), src_fname.c_str(), tb->get_dir_fd(dpp), dst_fname.c_str(), flags);
+  if(ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: renameat2 for shadow object could not finish: "
+       << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  // Update saved bucket info
+  info.bucket.name = to->get_name();
+  bufferlist bl;
+  encode(info, bl);
+  ret = write_x_attr(dpp, y, dir_fd, RGW_POSIX_ATTR_BUCKET_INFO, bl, get_name());
+  if (ret < 0) {
+    return ret;
+  }
+
+  // Delete old one (could be file or directory)
+  struct statx stx;
+  ret = statx(parent_fd, src_fname.c_str(), AT_SYMLINK_NOFOLLOW,
+                 STATX_ALL, &stx);
+  if (ret < 0) {
+    ret = errno;
+    if (ret == ENOENT) {
+      return 0;
+    }
+    ldpp_dout(dpp, 0) << "ERROR: could not stat object " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  if (S_ISREG(stx.stx_mode)) {
+    ret = unlinkat(parent_fd, src_fname.c_str(), 0);
+  } else if (S_ISDIR(stx.stx_mode)) {
+    ret = delete_directory(parent_fd, src_fname.c_str(), true, dpp);
+  }
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not remove old file " << get_name()
+                      << ": " << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  return 0;
+}
+
+int POSIXBucket::close()
+{
+  if (dir_fd < 0) {
+    return 0;
+  }
+
+  ::close(dir_fd);
+  dir_fd = -1;
+
+  return 0;
+}
+
+int POSIXBucket::stat(const DoutPrefixProvider* dpp)
+{
+  if (stat_done) {
+    return 0;
+  }
+
+  int ret = statx(parent_fd, get_fname().c_str(), AT_SYMLINK_NOFOLLOW,
+                 STATX_ALL, &stx);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not stat bucket " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+  if (!S_ISDIR(stx.stx_mode)) {
+    /* Not a bucket */
+    return -EINVAL;
+  }
+
+  stat_done = true;
+  return 0;
+}
+
+/* This is a shadow bucket.  Copy it into a new shadow bucket in the destination
+ * bucket */
+int POSIXBucket::copy(const DoutPrefixProvider *dpp, optional_yield y,
+                      POSIXBucket* db, POSIXObject* dest)
+{
+  std::unique_ptr<POSIXBucket> dsb;
+
+  // Delete the target, in case it's not a multipart
+  int ret = dest->delete_object(dpp, y);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not remove dest object "
+                      << dest->get_name() << dendl;
+    return ret;
+  }
+
+  ret = db->get_shadow_bucket(dpp, y, std::string(), std::string(), dest->get_fname(), true, &dsb);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not create shadow bucket " << dest->get_name()
+                      << " in bucket " << db->get_name() << dendl;
+    return ret;
+  }
+
+  ret = for_each(dpp, [this, &dsb, &dpp, &y](const char *name) {
+    int ret;
+    std::unique_ptr<Object> sobj;
+    POSIXObject* sop;
+    std::unique_ptr<Object> dobj;
+    POSIXObject* dop;
+
+    if (name[0] == '.') {
+      /* Skip dotfiles */
+      return 0;
+    }
+
+    sobj = this->get_object(decode_obj_key(name));
+    sop = static_cast<POSIXObject*>(sobj.get());
+    if (!sop->exists(dpp)) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not stat object " << name << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+    ret = sop->open(dpp, true);
+    if (ret < 0) {
+      ldpp_dout(dpp, 0) << "ERROR: could not open source object " << get_name()
+                        << dendl;
+      return ret;
+    }
+
+    dobj = dsb->get_object(decode_obj_key(name));
+    dop = static_cast<POSIXObject*>(dobj.get());
+
+    return sop->copy(dpp, y, this, dsb.get(), dop);
+  });
+
+  return ret;
+}
+
+int POSIXObject::delete_object(const DoutPrefixProvider* dpp,
+                               optional_yield y,
+                               bool prevent_versioning)
+{
+  POSIXBucket *b = static_cast<POSIXBucket*>(get_bucket());
+  if (!b) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket for " << get_name() << dendl;
+      return -EINVAL;
+  }
+
+  int ret = stat(dpp);
+  if (ret < 0) {
+      if (ret == -ENOENT) {
+       // Nothing to do
+       return 0;
+      }
+      return ret;
+  }
+
+  if (!b->versioned()) {
+    if (shadow) {
+      ret = shadow->remove_bucket(dpp, true, false, nullptr, y);
+      if (ret < 0) {
+       return ret;
+      }
+      shadow.reset(nullptr);
+    }
+
+    int ret = unlinkat(b->get_dir_fd(dpp), get_fname().c_str(), 0);
+    if (ret < 0) {
+      ret = errno;
+      if (errno != ENOENT) {
+        ldpp_dout(dpp, 0) << "ERROR: could not remove object " << get_name()
+                          << ": " << cpp_strerror(ret) << dendl;
+        return -ret;
+      }
+    }
+    return 0;
+  }
+
+  // Versioned directory.  Need to remove all objects matching
+  b->for_each(dpp, [this, &dpp, &b](const char* name) {
+    int ret;
+    std::string_view vname(name);
+
+    if (vname.find(get_fname().c_str()) != std::string_view::npos) {
+      ret = unlinkat(b->get_dir_fd(dpp), name, 0);
+      if (ret < 0) {
+        ret = errno;
+        if (errno != ENOENT) {
+          ldpp_dout(dpp, 0) << "ERROR: could not remove object " << name
+                            << ": " << cpp_strerror(ret) << dendl;
+          return -ret;
+        }
+      }
+    }
+    return 0;
+  });
+
+  return 0;
+}
+
+int POSIXObject::copy_object(User* user,
+                              req_info* info,
+                              const rgw_zone_id& source_zone,
+                              rgw::sal::Object* dest_object,
+                              rgw::sal::Bucket* dest_bucket,
+                              rgw::sal::Bucket* src_bucket,
+                              const rgw_placement_rule& dest_placement,
+                              ceph::real_time* src_mtime,
+                              ceph::real_time* mtime,
+                              const ceph::real_time* mod_ptr,
+                              const ceph::real_time* unmod_ptr,
+                              bool high_precision_time,
+                              const char* if_match,
+                              const char* if_nomatch,
+                              AttrsMod attrs_mod,
+                              bool copy_if_newer,
+                              Attrs& attrs,
+                              RGWObjCategory category,
+                              uint64_t olh_epoch,
+                              boost::optional<ceph::real_time> delete_at,
+                              std::string* version_id,
+                              std::string* tag,
+                              std::string* etag,
+                              void (*progress_cb)(off_t, void *),
+                              void* progress_data,
+                              const DoutPrefixProvider* dpp,
+                              optional_yield y)
+{
+  int ret;
+  POSIXBucket *db = static_cast<POSIXBucket*>(dest_bucket);
+  POSIXBucket *sb = static_cast<POSIXBucket*>(src_bucket);
+  POSIXObject *dobj = static_cast<POSIXObject*>(dest_object);
+
+  if (!db || !sb) {
+    ldpp_dout(dpp, 0) << "ERROR: could not get bucket to copy " << get_name()
+                      << dendl;
+    return -EINVAL;
+  }
+
+  // Source must exist, and we need to know if it's a shadow obj
+  if (!exists(dpp)) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not stat object " << get_name() << ": "
+                      << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  if (shadow) {
+    return shadow->copy(dpp, y, db, dobj);
+  } else {
+    return copy(dpp, y, sb, db, dobj);
+  }
+}
+
+int POSIXObject::get_obj_state(const DoutPrefixProvider* dpp, RGWObjState **pstate, optional_yield y, bool follow_olh)
+{
+  int ret = stat(dpp);
+  if (ret < 0) {
+    return ret;
+  }
+  *pstate = &state;
+
+  return 0;
+}
+
+int POSIXObject::set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs,
+                            Attrs* delattrs, optional_yield y)
+{
+  if (delattrs) {
+    for (auto& it : *delattrs) {
+      state.attrset.erase(it.first);
+    }
+  }
+  if (setattrs) {
+    for (auto& it : *setattrs) {
+      state.attrset[it.first] = it.second;
+    }
+  }
+
+  for (auto& it : state.attrset) {
+         int ret = write_attr(dpp, y, it.first, it.second);
+         if (ret < 0) {
+           return ret;
+         }
+  }
+  return 0;
+}
+
+int POSIXObject::get_obj_attrs(optional_yield y, const DoutPrefixProvider* dpp,
+                                rgw_obj* target_obj)
+{
+  int ret = open(dpp, false);
+  if (ret < 0) {
+    return ret;
+  }
+
+  return get_x_attrs(y, dpp, obj_fd, state.attrset, get_name());
+}
+
+int POSIXObject::modify_obj_attrs(const char* attr_name, bufferlist& attr_val,
+                               optional_yield y, const DoutPrefixProvider* dpp)
+{
+  state.attrset[attr_name] = attr_val;
+  return write_attr(dpp, y, attr_name, attr_val);
+}
+
+int POSIXObject::delete_obj_attrs(const DoutPrefixProvider* dpp, const char* attr_name,
+                               optional_yield y)
+{
+  state.attrset.erase(attr_name);
+
+  int ret = open(dpp, true);
+  if (ret < 0) {
+    return ret;
+  }
+
+  ret = fremovexattr(obj_fd, attr_name);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not remover attribute " << attr_name << " for " << get_name() << ": " << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  return 0;
+}
+
+bool POSIXObject::is_expired()
+{
+  bufferlist bl;
+  if (get_attr(state.attrset, RGW_ATTR_DELETE_AT, bl)) {
+    utime_t delete_at;
+    try {
+      auto bufit = bl.cbegin();
+      decode(delete_at, bufit);
+    } catch (buffer::error& err) {
+      ldout(driver->ctx(), 0) << "ERROR: " << __func__ << ": failed to decode " RGW_ATTR_DELETE_AT " attr" << dendl;
+      return false;
+    }
+
+    if (delete_at <= ceph_clock_now() && !delete_at.is_zero()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void POSIXObject::gen_rand_obj_instance_name()
+{
+  enum { OBJ_INSTANCE_LEN = 32 };
+  char buf[OBJ_INSTANCE_LEN + 1];
+
+  gen_rand_alphanumeric_no_underscore(driver->ctx(), buf, OBJ_INSTANCE_LEN);
+  state.obj.key.set_instance(buf);
+}
+
+std::unique_ptr<MPSerializer> POSIXObject::get_serializer(const DoutPrefixProvider *dpp, const std::string& lock_name)
+{
+  return std::make_unique<MPPOSIXSerializer>(dpp, driver, this, lock_name);
+}
+
+int MPPOSIXSerializer::try_lock(const DoutPrefixProvider *dpp, utime_t dur, optional_yield y)
+{
+  if (!obj->exists(dpp)) {
+    return -ENOENT;
+  }
+
+  return 0;
+}
+
+int POSIXObject::transition(Bucket* bucket,
+                           const rgw_placement_rule& placement_rule,
+                           const real_time& mtime,
+                           uint64_t olh_epoch,
+                           const DoutPrefixProvider* dpp,
+                           optional_yield y)
+{
+  return -ERR_NOT_IMPLEMENTED;
+}
+
+int POSIXObject::transition_to_cloud(Bucket* bucket,
+                          rgw::sal::PlacementTier* tier,
+                          rgw_bucket_dir_entry& o,
+                          std::set<std::string>& cloud_targets,
+                          CephContext* cct,
+                          bool update_object,
+                          const DoutPrefixProvider* dpp,
+                          optional_yield y)
+{
+  return -ERR_NOT_IMPLEMENTED;
+}
+
+bool POSIXObject::placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2)
+{
+  return (r1 == r2);
+}
+
+int POSIXObject::dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f)
+{
+    return 0;
+}
+
+int POSIXObject::swift_versioning_restore(bool& restored,
+                                      const DoutPrefixProvider* dpp, optional_yield y)
+{
+  return 0;
+}
+
+int POSIXObject::swift_versioning_copy(const DoutPrefixProvider* dpp,
+                                   optional_yield y)
+{
+  return 0;
+}
+
+int POSIXObject::omap_get_vals_by_keys(const DoutPrefixProvider *dpp, const std::string& oid,
+                                         const std::set<std::string>& keys,
+                                         Attrs* vals)
+{
+  /* TODO Figure out omap */
+  return 0;
+}
+
+int POSIXObject::omap_set_val_by_key(const DoutPrefixProvider *dpp, const std::string& key, bufferlist& val,
+                                       bool must_exist, optional_yield y)
+{
+  /* TODO Figure out omap */
+  return 0;
+}
+
+int POSIXObject::chown(User& new_user, const DoutPrefixProvider* dpp, optional_yield y)
+{
+  POSIXBucket *b = static_cast<POSIXBucket*>(get_bucket());
+  if (!b) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket for " << get_name() << dendl;
+      return -EINVAL;
+  }
+  /* TODO Get UID from user */
+  int uid = 0;
+  int gid = 0;
+
+  int ret = fchownat(b->get_dir_fd(dpp), get_fname().c_str(), uid, gid, AT_SYMLINK_NOFOLLOW);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not remove object " << get_name() << ": "
+      << cpp_strerror(ret) << dendl;
+    return -ret;
+    }
+
+  return 0;
+}
+
+int POSIXObject::stat(const DoutPrefixProvider* dpp)
+{
+  if (stat_done) {
+    return 0;
+  }
+
+  state.exists = false;
+  POSIXBucket *b = static_cast<POSIXBucket*>(get_bucket());
+  if (!b) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket for " << get_name() << dendl;
+      return -EINVAL;
+  }
+
+  int ret = statx(b->get_dir_fd(dpp), get_fname().c_str(), AT_SYMLINK_NOFOLLOW,
+                 STATX_ALL, &stx);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not stat object " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+  if (S_ISREG(stx.stx_mode)) {
+    /* Normal object */
+    state.accounted_size = state.size = stx.stx_size;
+    state.mtime = from_statx_timestamp(stx.stx_mtime);
+  } else if (S_ISDIR(stx.stx_mode)) {
+    /* multipart object */
+    /* Get the shadow bucket */
+    POSIXBucket* pb = static_cast<POSIXBucket*>(bucket);
+    ret = pb->get_shadow_bucket(dpp, null_yield, std::string(),
+                               std::string(), get_fname(), false, &shadow);
+    if (ret < 0) {
+      return ret;
+    }
+
+    state.mtime = from_statx_timestamp(stx.stx_mtime);
+    /* Add up size of parts */
+    uint64_t total_size{0};
+    int fd = shadow->get_dir_fd(dpp);
+    shadow->for_each(dpp, [this, &total_size, fd, &dpp](const char* name) {
+      int ret;
+      struct statx stx;
+      std::string sname = name;
+
+      if (sname.rfind(MP_OBJ_PART_PFX, 0) != 0) {
+       /* Skip non-parts */
+       return 0;
+      }
+
+      ret = statx(fd, name, AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx);
+      if (ret < 0) {
+       ret = errno;
+       ldpp_dout(dpp, 0) << "ERROR: could not stat object " << name << ": " << cpp_strerror(ret) << dendl;
+       return -ret;
+      }
+
+      if (!S_ISREG(stx.stx_mode)) {
+       /* Skip non-files */
+       return 0;
+      }
+
+      parts[name] = stx.stx_size;
+      total_size += stx.stx_size;
+      return 0;
+      });
+    state.accounted_size = state.size = total_size;
+  } else {
+    /* Not an object */
+    return -EINVAL;
+  }
+
+  stat_done = true;
+  state.exists = true;
+
+  return 0;
+}
+
+int POSIXObject::get_owner(const DoutPrefixProvider *dpp, optional_yield y, std::unique_ptr<User> *owner)
+{
+  bufferlist bl;
+  rgw_user u;
+  if (!rgw::sal::get_attr(get_attrs(), RGW_POSIX_ATTR_OWNER, bl)) {
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__
+        << ": No " RGW_POSIX_ATTR_OWNER " attr" << dendl;
+    return -EINVAL;
+  }
+
+  try {
+    auto bufit = bl.cbegin();
+    decode(u, bufit);
+  } catch (buffer::error &err) {
+    ldpp_dout(dpp, 0) << "ERROR: " << __func__
+        << ": failed to decode " RGW_POSIX_ATTR_OWNER " attr" << dendl;
+    return -EINVAL;
+  }
+
+  *owner = driver->get_user(u);
+  (*owner)->load_user(dpp, y);
+  return 0;
+}
+
+std::unique_ptr<Object::ReadOp> POSIXObject::get_read_op()
+{
+  return std::make_unique<POSIXReadOp>(this);
+}
+
+std::unique_ptr<Object::DeleteOp> POSIXObject::get_delete_op()
+{
+  return std::make_unique<POSIXDeleteOp>(this);
+}
+
+int POSIXObject::open(const DoutPrefixProvider* dpp, bool create, bool temp_file)
+{
+  if (obj_fd >= 0) {
+    return 0;
+  }
+
+  stat(dpp);
+
+  if (shadow) {
+    obj_fd = shadow->get_dir_fd(dpp);
+    return obj_fd;
+  }
+
+  POSIXBucket *b = static_cast<POSIXBucket*>(get_bucket());
+  if (!b) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket for " << get_name() << dendl;
+      return -EINVAL;
+  }
+
+  int ret, flags;
+  std::string path;
+
+  if(temp_file) {
+    flags = O_TMPFILE | O_RDWR;
+    path = ".";
+  } else {
+    flags = O_RDWR | O_NOFOLLOW;
+    if (create)
+      flags |= O_CREAT;
+    path = get_fname();
+  }
+  ret = openat(b->get_dir_fd(dpp), path.c_str(), flags, S_IRWXU);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not open object " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  obj_fd = ret;
+
+  return 0;
+}
+
+int POSIXObject::link_temp_file(const DoutPrefixProvider *dpp, optional_yield y)
+{
+  if (obj_fd < 0) {
+    return 0;
+  }
+
+  char temp_file_path[PATH_MAX];
+  // Only works on Linux - Non-portable
+  snprintf(temp_file_path, PATH_MAX,  "/proc/self/fd/%d", obj_fd);
+
+  POSIXBucket *b = static_cast<POSIXBucket*>(get_bucket());
+
+  if (!b) {
+      ldpp_dout(dpp, 0) << "ERROR: could not get bucket for " << get_name() << dendl;
+      return -EINVAL;
+  }
+
+  int ret = linkat(AT_FDCWD, temp_file_path, b->get_dir_fd(dpp), get_temp_fname().c_str(), AT_SYMLINK_FOLLOW);
+  if(ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: linkat for temp file could not finish: "
+       << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  // Delete the target, in case it's a multipart
+  ret = delete_object(dpp, y);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not remove dest object "
+                      << get_name() << dendl;
+    return ret;
+  }
+
+  ret = renameat(b->get_dir_fd(dpp), get_temp_fname().c_str(), b->get_dir_fd(dpp), get_fname().c_str());
+  if(ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: renameat for object could not finish: "
+       << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  return 0;
+}
+
+
+int POSIXObject::close()
+{
+  if (obj_fd < 0) {
+    return 0;
+  }
+
+  int ret = ::fsync(obj_fd);
+  if(ret < 0) {
+    return ret;
+  }
+
+  ret = ::close(obj_fd);
+  if(ret < 0) {
+    return ret;
+  }
+  obj_fd = -1;
+
+  return 0;
+}
+
+int POSIXObject::read(int64_t ofs, int64_t left, bufferlist& bl,
+                     const DoutPrefixProvider* dpp, optional_yield y)
+{
+  if (!shadow) {
+    // Normal file, just read it
+    int64_t len = std::min(left + 1, READ_SIZE);
+    ssize_t ret;
+
+    ret = lseek(obj_fd, ofs, SEEK_SET);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not seek object " << get_name() << " to "
+       << ofs << " :" << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    char read_buf[READ_SIZE];
+    ret = ::read(obj_fd, read_buf, len);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not read object " << get_name() << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    bl.append(read_buf, ret);
+
+    return ret;
+  }
+
+  // It's a multipart object, find the correct file, open it, and read it
+  std::string pname;
+  for (auto part : parts) {
+    if (ofs < part.second) {
+      pname = part.first;
+      break;
+    }
+
+    ofs -= part.second;
+  }
+
+  if (pname.empty()) {
+    // ofs is past the end
+    return 0;
+  }
+
+  POSIXObject* shadow_obj;
+  std::unique_ptr<rgw::sal::Object> obj = shadow->get_object(rgw_obj_key(pname));
+  shadow_obj = static_cast<POSIXObject*>(obj.get());
+  int ret = shadow_obj->open(dpp, false);
+  if (ret < 0) {
+    return ret;
+  }
+
+  return shadow_obj->read(ofs, left, bl, dpp, y);
+}
+
+int POSIXObject::write(int64_t ofs, bufferlist& bl, const DoutPrefixProvider* dpp,
+                      optional_yield y)
+{
+  if (shadow) {
+    // Can't write to a MP file
+    return -EINVAL;
+  }
+
+  int64_t left = bl.length();
+  char* curp = bl.c_str();
+  ssize_t ret;
+
+  ret = fchmod(obj_fd, S_IRUSR|S_IWUSR);
+  if(ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not change permissions on object " << get_name() << ": "
+                  << cpp_strerror(ret) << dendl;
+    return ret;
+  }
+
+
+  ret = lseek(obj_fd, ofs, SEEK_SET);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not seek object " << get_name() << " to "
+      << ofs << " :" << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  while (left > 0) {
+    ret = ::write(obj_fd, curp, left);
+    if (ret < 0) {
+      ret = errno;
+      ldpp_dout(dpp, 0) << "ERROR: could not write object " << get_name() << ": "
+       << cpp_strerror(ret) << dendl;
+      return -ret;
+    }
+
+    curp += ret;
+    left -= ret;
+  }
+
+  return 0;
+}
+
+int POSIXObject::write_attr(const DoutPrefixProvider* dpp, optional_yield y, const std::string& key, bufferlist& value)
+{
+  int ret;
+  std::string attrname;
+
+  ret = open(dpp, true);
+  if (ret < 0) {
+    return ret;
+  }
+
+  return write_x_attr(dpp, y, obj_fd, key, value, get_name());
+}
+
+int POSIXObject::POSIXReadOp::prepare(optional_yield y, const DoutPrefixProvider* dpp)
+{
+  int ret = source->stat(dpp);
+  if (ret < 0)
+    return ret;
+
+  ret = source->get_obj_attrs(y, dpp);
+  if (ret < 0)
+    return ret;
+
+  bufferlist etag_bl;
+  if (!rgw::sal::get_attr(source->get_attrs(), RGW_ATTR_ETAG, etag_bl)) {
+    /* Sideloaded file.  Generate necessary attributes. Only done once. */
+    int ret = source->generate_attrs(dpp, y);
+    if (ret < 0) {
+       ldpp_dout(dpp, 0) << " ERROR: could not generate attrs for " << source->get_name() << " error: " << cpp_strerror(ret) << dendl;
+       return ret;
+    }
+  }
+
+  if (!rgw::sal::get_attr(source->get_attrs(), RGW_ATTR_ETAG, etag_bl)) {
+    return -EINVAL;
+  }
+
+#if 0 // WIP
+  if (params.mod_ptr || params.unmod_ptr) {
+    obj_time_weight src_weight;
+    src_weight.init(astate);
+    src_weight.high_precision = params.high_precision_time;
+
+    obj_time_weight dest_weight;
+    dest_weight.high_precision = params.high_precision_time;
+
+    if (params.mod_ptr && !params.if_nomatch) {
+      dest_weight.init(*params.mod_ptr, params.mod_zone_id, params.mod_pg_ver);
+      ldpp_dout(dpp, 10) << "If-Modified-Since: " << dest_weight << " Last-Modified: " << src_weight << dendl;
+      if (!(dest_weight < src_weight)) {
+        return -ERR_NOT_MODIFIED;
+      }
+    }
+
+    if (params.unmod_ptr && !params.if_match) {
+      dest_weight.init(*params.unmod_ptr, params.mod_zone_id, params.mod_pg_ver);
+      ldpp_dout(dpp, 10) << "If-UnModified-Since: " << dest_weight << " Last-Modified: " << src_weight << dendl;
+      if (dest_weight < src_weight) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    }
+  }
+#endif
+
+  if (params.mod_ptr || params.unmod_ptr) {
+    if (params.mod_ptr && !params.if_nomatch) {
+      ldpp_dout(dpp, 10) << "If-Modified-Since: " << *params.mod_ptr << " Last-Modified: " << source->get_mtime() << dendl;
+      if (!(*params.mod_ptr < source->get_mtime())) {
+        return -ERR_NOT_MODIFIED;
+      }
+    }
+
+    if (params.unmod_ptr && !params.if_match) {
+      ldpp_dout(dpp, 10) << "If-Modified-Since: " << *params.unmod_ptr << " Last-Modified: " << source->get_mtime() << dendl;
+      if (*params.unmod_ptr < source->get_mtime()) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    }
+  }
+
+  if (params.if_match) {
+    std::string if_match_str = rgw_string_unquote(params.if_match);
+    ldpp_dout(dpp, 10) << "If-Match: " << if_match_str << " ETAG: " << etag_bl.c_str() << dendl;
+
+    if (if_match_str.compare(0, etag_bl.length(), etag_bl.c_str(), etag_bl.length()) != 0) {
+      return -ERR_PRECONDITION_FAILED;
+    }
+  }
+  if (params.if_nomatch) {
+    std::string if_nomatch_str = rgw_string_unquote(params.if_nomatch);
+    ldpp_dout(dpp, 10) << "If-No-Match: " << if_nomatch_str << " ETAG: " << etag_bl.c_str() << dendl;
+    if (if_nomatch_str.compare(0, etag_bl.length(), etag_bl.c_str(), etag_bl.length()) == 0) {
+      return -ERR_NOT_MODIFIED;
+    }
+  }
+
+  if (params.lastmod) {
+    *params.lastmod = source->get_mtime();
+  }
+
+  return 0;
+}
+
+int POSIXObject::POSIXReadOp::read(int64_t ofs, int64_t end, bufferlist& bl,
+                                    optional_yield y, const DoutPrefixProvider* dpp)
+{
+  return source->read(ofs, end + 1, bl, dpp, y);
+}
+
+int POSIXObject::generate_attrs(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  int ret;
+
+  /* Generate an ETAG */
+  if (shadow) {
+    ret = generate_mp_etag(dpp, y);
+  } else {
+    ret = generate_etag(dpp, y);
+  }
+
+  return ret;
+}
+
+int POSIXObject::generate_mp_etag(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  int64_t count = 0;
+  char etag_buf[CEPH_CRYPTO_MD5_DIGESTSIZE];
+  char final_etag_str[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 16];
+  std::string etag;
+  bufferlist etag_bl;
+  MD5 hash;
+  // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes
+  hash.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+  int ret;
+  rgw::sal::Bucket::ListParams params;
+  rgw::sal::Bucket::ListResults results;
+
+  do {
+    static constexpr auto MAX_LIST_OBJS = 100u;
+    ret = shadow->list(dpp, params, MAX_LIST_OBJS, results, y);
+    if (ret < 0) {
+      return ret;
+    }
+    for (rgw_bucket_dir_entry& ent : results.objs) {
+      std::unique_ptr<rgw::sal::Object> obj;
+      POSIXObject* shadow_obj;
+
+      if (MP_OBJ_PART_PFX.compare(0, std::string::npos, ent.key.name,
+                                 MP_OBJ_PART_PFX.size() != 0)) {
+       // Skip non-parts
+       continue;
+      }
+
+      obj = shadow->get_object(rgw_obj_key(ent.key));
+      shadow_obj = static_cast<POSIXObject*>(obj.get());
+      ret = shadow_obj->get_obj_attrs(y, dpp);
+      if (ret < 0) {
+       return ret;
+      }
+      bufferlist etag_bl;
+      if (!get_attr(shadow_obj->get_attrs(), RGW_ATTR_ETAG, etag_bl)) {
+       // Generate part's etag
+       ret = shadow_obj->generate_etag(dpp, y);
+       if (ret < 0)
+         return ret;
+      }
+      if (!get_attr(shadow_obj->get_attrs(), RGW_ATTR_ETAG, etag_bl)) {
+       // Can't get etag.
+       return -EINVAL;
+      }
+      hex_to_buf(etag_bl.c_str(), etag_buf, CEPH_CRYPTO_MD5_DIGESTSIZE);
+      hash.Update((const unsigned char *)etag_buf, sizeof(etag_buf));
+      count++;
+    }
+  } while (results.is_truncated);
+
+  hash.Final((unsigned char *)etag_buf);
+
+  buf_to_hex((unsigned char *)etag_buf, sizeof(etag_buf), final_etag_str);
+  snprintf(&final_etag_str[CEPH_CRYPTO_MD5_DIGESTSIZE * 2],
+          sizeof(final_etag_str) - CEPH_CRYPTO_MD5_DIGESTSIZE * 2,
+           "-%lld", (long long)count);
+  etag = final_etag_str;
+  ldpp_dout(dpp, 10) << "calculated etag: " << etag << dendl;
+
+  etag_bl.append(etag);
+  (void)write_attr(dpp, y, RGW_ATTR_ETAG, etag_bl);
+  get_attrs().emplace(std::move(RGW_ATTR_ETAG), std::move(etag_bl));
+
+  return 0;
+}
+
+int POSIXObject::generate_etag(const DoutPrefixProvider* dpp, optional_yield y)
+{
+  int64_t left = get_obj_size();
+  int64_t cur_ofs = 0;
+  MD5 hash;
+  // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes
+  hash.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+  char calc_md5[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 1];
+  unsigned char m[CEPH_CRYPTO_MD5_DIGESTSIZE];
+
+  bufferlist etag_bl;
+
+  while (left > 0) {
+    bufferlist bl;
+    int len = read(cur_ofs, left, bl, dpp, y);
+    if (len < 0) {
+       ldpp_dout(dpp, 0) << " ERROR: could not read " << get_name() <<
+         " ofs: " << cur_ofs << " error: " << cpp_strerror(len) << dendl;
+       return len;
+    } else if (len == 0) {
+      /* Done */
+      break;
+    }
+    hash.Update((const unsigned char *)bl.c_str(), bl.length());
+
+    left -= len;
+    cur_ofs += len;
+  }
+
+  hash.Final(m);
+  buf_to_hex(m, CEPH_CRYPTO_MD5_DIGESTSIZE, calc_md5);
+  etag_bl.append(calc_md5, sizeof(calc_md5));
+  (void)write_attr(dpp, y, RGW_ATTR_ETAG, etag_bl);
+  get_attrs().emplace(std::move(RGW_ATTR_ETAG), std::move(etag_bl));
+
+  return 0;
+}
+
+const std::string POSIXObject::get_fname()
+{
+  std::string fname = url_encode(get_obj().get_oid(), true);
+
+  if (!get_obj().key.get_ns().empty()) {
+    /* Namespaced objects are hidden */
+    fname.insert(0, 1, '.');
+  }
+
+  return fname;
+}
+
+void POSIXObject::gen_temp_fname()
+{
+  enum { RAND_SUFFIX_SIZE = 8 };
+  char buf[RAND_SUFFIX_SIZE + 1];
+
+  gen_rand_alphanumeric_no_underscore(driver->ctx(), buf, RAND_SUFFIX_SIZE);
+  temp_fname = "." + get_fname() + ".";
+  temp_fname.append(buf);
+}
+
+const std::string POSIXObject::get_temp_fname()
+{
+  return temp_fname;
+}
+
+int POSIXObject::POSIXReadOp::iterate(const DoutPrefixProvider* dpp, int64_t ofs,
+                                       int64_t end, RGWGetDataCB* cb, optional_yield y)
+{
+  int64_t left;
+  int64_t cur_ofs = ofs;
+
+  if (end < 0)
+    left = 0;
+  else
+    left = end - ofs + 1;
+
+  while (left > 0) {
+    bufferlist bl;
+    int len = source->read(cur_ofs, left, bl, dpp, y);
+    if (len < 0) {
+       ldpp_dout(dpp, 0) << " ERROR: could not read " << source->get_name() <<
+         " ofs: " << cur_ofs << " error: " << cpp_strerror(len) << dendl;
+       return len;
+    } else if (len == 0) {
+      /* Done */
+      break;
+    }
+
+    /* Read some */
+    int ret = cb->handle_data(bl, 0, len);
+    if (ret < 0) {
+       ldpp_dout(dpp, 0) << " ERROR: callback failed on " << source->get_name() << dendl;
+       return ret;
+    }
+
+    left -= len;
+    cur_ofs += len;
+  }
+
+  /* Doesn't seem to be anything needed from params */
+  return 0;
+}
+
+int POSIXObject::POSIXReadOp::get_attr(const DoutPrefixProvider* dpp, const char* name, bufferlist& dest, optional_yield y)
+{
+  if (!source->exists(dpp)) {
+    return -ENOENT;
+  }
+  if (source->get_obj_attrs(y, dpp) < 0) {
+    return -ENODATA;
+  }
+  if (!rgw::sal::get_attr(source->get_attrs(), name, dest)) {
+    return -ENODATA;
+  }
+
+  return 0;
+}
+
+int POSIXObject::POSIXDeleteOp::delete_obj(const DoutPrefixProvider* dpp,
+                                          optional_yield y)
+{
+  return source->delete_object(dpp, y, false);
+}
+
+int POSIXObject::copy(const DoutPrefixProvider *dpp, optional_yield y,
+                      POSIXBucket *sb, POSIXBucket *db, POSIXObject *dobj)
+{
+  off64_t scount = 0, dcount = 0;
+
+  int ret = open(dpp, false);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not open source object " << get_name()
+                      << dendl;
+    return ret;
+  }
+
+  // Delete the target, in case it's a multipart
+  ret = dobj->delete_object(dpp, y);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not remove dest object "
+                      << dobj->get_name() << dendl;
+    return ret;
+  }
+
+  ret = dobj->open(dpp, true);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not open dest object "
+                      << dobj->get_name() << dendl;
+    return ret;
+  }
+
+  ret = copy_file_range(obj_fd, &scount, dobj->get_fd(), &dcount, stx.stx_size, 0);
+  if (ret < 0) {
+    ret = errno;
+    ldpp_dout(dpp, 0) << "ERROR: could not copy object " << dobj->get_name()
+                      << ": " << cpp_strerror(ret) << dendl;
+    return -ret;
+  }
+
+  ret = get_obj_attrs(y, dpp);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not get attrs for source object "
+                      << get_name() << dendl;
+    return ret;
+  }
+
+  ret = dobj->set_obj_attrs(dpp, &get_attrs(), NULL, y);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << "ERROR: could not write attrs to dest object "
+                      << dobj->get_name() << dendl;
+    return ret;
+  }
+
+  return 0;
+}
+
+void POSIXMPObj::init_gen(POSIXDriver* driver, const std::string& _oid, ACLOwner& _owner)
+{
+  char buf[33];
+  std::string new_id = MULTIPART_UPLOAD_ID_PREFIX; /* v2 upload id */
+  /* Generate an upload ID */
+
+  gen_rand_alphanumeric(driver->ctx(), buf, sizeof(buf) - 1);
+  new_id.append(buf);
+  init(_oid, new_id, _owner);
+}
+
+int POSIXMultipartPart::load(const DoutPrefixProvider* dpp, optional_yield y,
+                            POSIXDriver* driver, rgw_obj_key& key)
+{
+  if (shadow) {
+    /* Already loaded */
+    return 0;
+  }
+
+  shadow = std::make_unique<POSIXObject>(driver, key, upload->get_shadow());
+
+  RGWObjState* pstate;
+  // Stat the shadow object to get things like size
+  int ret = shadow->get_obj_state(dpp, &pstate, y);
+  if (ret < 0) {
+    return ret;
+  }
+
+  ret = shadow->get_obj_attrs(y, dpp);
+  if (ret < 0) {
+    return ret;
+  }
+
+  auto ait = shadow->get_attrs().find(RGW_POSIX_ATTR_MPUPLOAD);
+  if (ait == shadow->get_attrs().end()) {
+    ldout(driver->ctx(), 0) << "ERROR: " << __func__ << ": Not a part: " << key << dendl;
+    return -EINVAL;
+  }
+
+  try {
+    auto bit = ait->second.cbegin();
+    decode(info, bit);
+  } catch (buffer::error& err) {
+    ldout(driver->ctx(), 0) << "ERROR: " << __func__ << ": failed to decode part info: " << key << dendl;
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+int POSIXMultipartUpload::load(bool create)
+{
+  if (!shadow) {
+    POSIXBucket* pb = static_cast<POSIXBucket*>(bucket);
+    return pb->get_shadow_bucket(nullptr, null_yield, mp_ns,
+                         std::string(), get_meta(), create, &shadow);
+  }
+
+  return 0;
+}
+
+std::unique_ptr<rgw::sal::Object> POSIXMultipartUpload::get_meta_obj()
+{
+  load();
+  if (!shadow) {
+    // This upload doesn't exist, but the API doesn't check this until it calls
+    // on the *serializer*. So make a fake object in the parent bucket that
+    // doesn't exist.  Put it in the MP namespace just in case.
+    return bucket->get_object(rgw_obj_key(get_meta(), std::string(), mp_ns));
+  }
+  return shadow->get_object(rgw_obj_key(get_meta(), std::string()));
+}
+
+int POSIXMultipartUpload::init(const DoutPrefixProvider *dpp, optional_yield y,
+                               ACLOwner& owner, rgw_placement_rule& dest_placement,
+                               rgw::sal::Attrs& attrs)
+{
+  int ret;
+
+  /* Create the shadow bucket */
+  ret = load(true);
+  if (ret < 0) {
+    ldpp_dout(dpp, 0) << " ERROR: could not get shadow bucket for mp upload "
+      << get_key() << dendl;
+    return ret;
+  }
+
+  /* Now create the meta object */
+  std::unique_ptr<rgw::sal::Object> meta_obj;
+
+  meta_obj = get_meta_obj();
+
+  mp_obj.upload_info.dest_placement = dest_placement;
+
+  bufferlist bl;
+  encode(mp_obj, bl);
+
+  attrs[RGW_POSIX_ATTR_MPUPLOAD] = bl;
+
+  return meta_obj->set_obj_attrs(dpp, &attrs, nullptr, y);
+}
+
+int POSIXMultipartUpload::list_parts(const DoutPrefixProvider *dpp, CephContext *cct,
+                                     int num_parts, int marker,
+                                     int *next_marker, bool *truncated, optional_yield y,
+                                     bool assume_unsorted)
+{
+  int ret;
+  int last_num = 0;
+
+  ret = load();
+  if (ret < 0) {
+    return ret;
+  }
+
+  rgw::sal::Bucket::ListParams params;
+  rgw::sal::Bucket::ListResults results;
+
+  params.prefix = MP_OBJ_PART_PFX;
+  params.marker = MP_OBJ_PART_PFX + fmt::format("{:0>5}", marker);
+
+  ret = shadow->list(dpp, params, num_parts + 1, results, y);
+  if (ret < 0) {
+    return ret;
+  }
+  for (rgw_bucket_dir_entry& ent : results.objs) {
+    std::unique_ptr<MultipartPart> part = std::make_unique<POSIXMultipartPart>(this);
+    POSIXMultipartPart* ppart = static_cast<POSIXMultipartPart*>(part.get());
+
+    rgw_obj_key key(ent.key);
+    ret = ppart->load(dpp, y, driver, key);
+    if (ret == 0) {
+      /* Skip anything that's not a part */
+      last_num = part->get_num();
+      parts[part->get_num()] = std::move(part);
+    }
+    if (parts.size() == (ulong)num_parts)
+      break;
+  }
+
+  if (truncated)
+    *truncated = results.is_truncated;
+
+  if (next_marker)
+    *next_marker = last_num;
+
+  return 0;
+}
+
+int POSIXMultipartUpload::abort(const DoutPrefixProvider *dpp, CephContext *cct, optional_yield y)
+{
+  int ret;
+
+  ret = load();
+  if (ret < 0) {
+    return ret;
+  }
+
+  shadow->remove_bucket(dpp, true, false, nullptr, y);
+
+  return 0;
+}
+
+int POSIXMultipartUpload::complete(const DoutPrefixProvider *dpp,
+                                   optional_yield y, CephContext* cct,
+                                   std::map<int, std::string>& part_etags,
+                                   std::list<rgw_obj_index_key>& remove_objs,
+                                   uint64_t& accounted_size, bool& compressed,
+                                   RGWCompressionInfo& cs_info, off_t& ofs,
+                                   std::string& tag, ACLOwner& owner,
+                                   uint64_t olh_epoch,
+                                   rgw::sal::Object* target_obj)
+{
+  char final_etag[CEPH_CRYPTO_MD5_DIGESTSIZE];
+  char final_etag_str[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 16];
+  std::string etag;
+  bufferlist etag_bl;
+  MD5 hash;
+  // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes
+  hash.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+  bool truncated;
+  int ret;
+
+  int total_parts = 0;
+  int handled_parts = 0;
+  int max_parts = 1000;
+  int marker = 0;
+  uint64_t min_part_size = cct->_conf->rgw_multipart_min_part_size;
+  auto etags_iter = part_etags.begin();
+  rgw::sal::Attrs attrs = target_obj->get_attrs();
+
+  do {
+    ret = list_parts(dpp, cct, max_parts, marker, &marker, &truncated, y);
+    if (ret == -ENOENT) {
+      ret = -ERR_NO_SUCH_UPLOAD;
+    }
+    if (ret < 0)
+      return ret;
+
+    total_parts += parts.size();
+    if (!truncated && total_parts != (int)part_etags.size()) {
+      ldpp_dout(dpp, 0) << "NOTICE: total parts mismatch: have: " << total_parts
+                      << " expected: " << part_etags.size() << dendl;
+      ret = -ERR_INVALID_PART;
+      return ret;
+    }
+
+    for (auto obj_iter = parts.begin(); etags_iter != part_etags.end() && obj_iter != parts.end(); ++etags_iter, ++obj_iter, ++handled_parts) {
+      POSIXMultipartPart* part = static_cast<rgw::sal::POSIXMultipartPart*>(obj_iter->second.get());
+      uint64_t part_size = part->get_size();
+      if (handled_parts < (int)part_etags.size() - 1 &&
+          part_size < min_part_size) {
+        ret = -ERR_TOO_SMALL;
+        return ret;
+      }
+
+      char petag[CEPH_CRYPTO_MD5_DIGESTSIZE];
+      if (etags_iter->first != (int)obj_iter->first) {
+        ldpp_dout(dpp, 0) << "NOTICE: parts num mismatch: next requested: "
+                        << etags_iter->first << " next uploaded: "
+                        << obj_iter->first << dendl;
+        ret = -ERR_INVALID_PART;
+        return ret;
+      }
+      std::string part_etag = rgw_string_unquote(etags_iter->second);
+      if (part_etag.compare(part->get_etag()) != 0) {
+        ldpp_dout(dpp, 0) << "NOTICE: etag mismatch: part: " << etags_iter->first
+                        << " etag: " << etags_iter->second << dendl;
+        ret = -ERR_INVALID_PART;
+        return ret;
+      }
+
+      hex_to_buf(part->get_etag().c_str(), petag,
+               CEPH_CRYPTO_MD5_DIGESTSIZE);
+      hash.Update((const unsigned char *)petag, sizeof(petag));
+
+      // Compression is not supported yet
+#if 0
+      RGWUploadPartInfo& obj_part = part->info;
+
+      bool part_compressed = (obj_part.cs_info.compression_type != "none");
+      if ((handled_parts > 0) &&
+          ((part_compressed != compressed) ||
+            (cs_info.compression_type != obj_part.cs_info.compression_type))) {
+          ldpp_dout(dpp, 0) << "ERROR: compression type was changed during multipart upload ("
+                           << cs_info.compression_type << ">>" << obj_part.cs_info.compression_type << ")" << dendl;
+          ret = -ERR_INVALID_PART;
+          return ret;
+      }
+
+      if (part_compressed) {
+        int64_t new_ofs; // offset in compression data for new part
+        if (cs_info.blocks.size() > 0)
+          new_ofs = cs_info.blocks.back().new_ofs + cs_info.blocks.back().len;
+        else
+          new_ofs = 0;
+        for (const auto& block : obj_part.cs_info.blocks) {
+          compression_block cb;
+          cb.old_ofs = block.old_ofs + cs_info.orig_size;
+          cb.new_ofs = new_ofs;
+          cb.len = block.len;
+          cs_info.blocks.push_back(cb);
+          new_ofs = cb.new_ofs + cb.len;
+        }
+        if (!compressed)
+          cs_info.compression_type = obj_part.cs_info.compression_type;
+        cs_info.orig_size += obj_part.cs_info.orig_size;
+        compressed = true;
+      }
+#endif
+
+      ofs += part->get_size();
+      accounted_size += part->get_size();
+    }
+  } while (truncated);
+  hash.Final((unsigned char *)final_etag);
+
+  buf_to_hex((unsigned char *)final_etag, sizeof(final_etag), final_etag_str);
+  snprintf(&final_etag_str[CEPH_CRYPTO_MD5_DIGESTSIZE * 2],
+          sizeof(final_etag_str) - CEPH_CRYPTO_MD5_DIGESTSIZE * 2,
+           "-%lld", (long long)part_etags.size());
+  etag = final_etag_str;
+  ldpp_dout(dpp, 10) << "calculated etag: " << etag << dendl;
+
+  etag_bl.append(etag);
+
+  attrs[RGW_ATTR_ETAG] = etag_bl;
+
+  if (compressed) {
+    // write compression attribute to full object
+    bufferlist tmp;
+    encode(cs_info, tmp);
+    attrs[RGW_ATTR_COMPRESSION] = tmp;
+  }
+
+  ret = shadow->merge_and_store_attrs(dpp, attrs, y);
+  if (ret < 0) {
+    return ret;
+  }
+
+  // Rename to target_obj
+  return shadow->rename(dpp, y, target_obj);
+}
+
+int POSIXMultipartUpload::get_info(const DoutPrefixProvider *dpp, optional_yield y,
+                                  rgw_placement_rule** rule, rgw::sal::Attrs* attrs)
+{
+  std::unique_ptr<rgw::sal::Object> meta_obj;
+  int ret;
+
+  if (!rule && !attrs) {
+    return 0;
+  }
+
+  if (attrs) {
+      meta_obj = get_meta_obj();
+      int ret = meta_obj->get_obj_attrs(y, dpp);
+      if (ret < 0) {
+       ldpp_dout(dpp, 0) << " ERROR: could not get meta object for mp upload "
+         << get_key() << dendl;
+       return ret;
+      }
+      *attrs = meta_obj->get_attrs();
+  }
+
+  if (rule) {
+    if (mp_obj.oid.empty()) {
+      if (!meta_obj) {
+       meta_obj = get_meta_obj();
+       ret = meta_obj->get_obj_attrs(y, dpp);
+       if (ret < 0) {
+         ldpp_dout(dpp, 0) << " ERROR: could not get meta object for mp upload "
+           << get_key() << dendl;
+         return ret;
+       }
+      }
+      bufferlist bl;
+      if (!get_attr(meta_obj->get_attrs(), RGW_POSIX_ATTR_MPUPLOAD, bl)) {
+       ldpp_dout(dpp, 0) << " ERROR: could not get meta object attrs for mp upload "
+         << get_key() << dendl;
+       return ret;
+      }
+      auto biter = bl.cbegin();
+      decode(mp_obj, biter);
+    }
+    *rule = &mp_obj.upload_info.dest_placement;
+  }
+
+  return 0;
+}
+
+std::unique_ptr<Writer> POSIXMultipartUpload::get_writer(
+                                 const DoutPrefixProvider *dpp,
+                                 optional_yield y,
+                                 rgw::sal::Object* _head_obj,
+                                 const rgw_user& owner,
+                                 const rgw_placement_rule *ptail_placement_rule,
+                                 uint64_t part_num,
+                                 const std::string& part_num_str)
+{
+  std::string fname = MP_OBJ_PART_PFX + fmt::format("{:0>5}", part_num);
+  rgw_obj_key part_key(fname);
+
+  load();
+
+  return std::make_unique<POSIXMultipartWriter>(dpp, y, shadow->clone(), part_key, driver,
+                                               owner, ptail_placement_rule, part_num);
+}
+
+int POSIXMultipartWriter::prepare(optional_yield y)
+{
+  return obj->open(dpp, true);
+}
+
+int POSIXMultipartWriter::process(bufferlist&& data, uint64_t offset)
+{
+  return obj->write(offset, data, dpp, null_yield);
+}
+
+int POSIXMultipartWriter::complete(size_t accounted_size, const std::string& etag,
+                       ceph::real_time *mtime, ceph::real_time set_mtime,
+                       std::map<std::string, bufferlist>& attrs,
+                       ceph::real_time delete_at,
+                       const char *if_match, const char *if_nomatch,
+                       const std::string *user_data,
+                       rgw_zone_set *zones_trace, bool *canceled,
+                       optional_yield y)
+{
+  int ret;
+  POSIXUploadPartInfo info;
+
+  if (if_match) {
+    if (strcmp(if_match, "*") == 0) {
+      // test the object is existing
+      if (!obj->exists(dpp)) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    } else {
+      bufferlist bl;
+      if (!get_attr(obj->get_attrs(), RGW_ATTR_ETAG, bl)) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+      if (strncmp(if_match, bl.c_str(), bl.length()) != 0) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    }
+  }
+
+  info.num = part_num;
+  info.etag = etag;
+  info.mtime = set_mtime;
+
+  bufferlist bl;
+  encode(info, bl);
+  attrs[RGW_POSIX_ATTR_MPUPLOAD] = bl;
+
+  for (auto& attr : attrs) {
+    ret = obj->write_attr(dpp, y, attr.first, attr.second);
+    if (ret < 0) {
+      ldpp_dout(dpp, 20) << "ERROR: failed writing attr " << attr.first << dendl;
+      return ret;
+    }
+  }
+
+  ret = obj->close();
+  if (ret < 0) {
+    ldpp_dout(dpp, 20) << "ERROR: failed closing file" << dendl;
+    return ret;
+  }
+
+  return 0;
+}
+
+int POSIXAtomicWriter::prepare(optional_yield y)
+{
+  obj.get_obj_attrs(y, dpp);
+  obj.close();
+  obj.gen_temp_fname();
+  return obj.open(dpp, true, true);
+}
+
+int POSIXAtomicWriter::process(bufferlist&& data, uint64_t offset)
+{
+  return obj.write(offset, data, dpp, null_yield);
+}
+
+int POSIXAtomicWriter::complete(size_t accounted_size, const std::string& etag,
+                       ceph::real_time *mtime, ceph::real_time set_mtime,
+                       std::map<std::string, bufferlist>& attrs,
+                       ceph::real_time delete_at,
+                       const char *if_match, const char *if_nomatch,
+                       const std::string *user_data,
+                       rgw_zone_set *zones_trace, bool *canceled,
+                       optional_yield y)
+{
+  int ret;
+
+  if (if_match) {
+    if (strcmp(if_match, "*") == 0) {
+      // test the object is existing
+      if (!obj.exists(dpp)) {
+       return -ERR_PRECONDITION_FAILED;
+      }
+    } else {
+      bufferlist bl;
+      if (!get_attr(obj.get_attrs(), RGW_ATTR_ETAG, bl)) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+      if (strncmp(if_match, bl.c_str(), bl.length()) != 0) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    }
+  }
+  if (if_nomatch) {
+    if (strcmp(if_nomatch, "*") == 0) {
+      // test the object is not existing
+      if (obj.exists(dpp)) {
+       return -ERR_PRECONDITION_FAILED;
+      }
+    } else {
+      bufferlist bl;
+      if (!get_attr(obj.get_attrs(), RGW_ATTR_ETAG, bl)) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+      if (strncmp(if_nomatch, bl.c_str(), bl.length()) == 0) {
+        return -ERR_PRECONDITION_FAILED;
+      }
+    }
+  }
+
+  bufferlist bl;
+  encode(owner, bl);
+  attrs[RGW_POSIX_ATTR_OWNER] = bl;
+
+  for (auto attr : attrs) {
+    ret = obj.write_attr(dpp, y, attr.first, attr.second);
+    if (ret < 0) {
+      ldpp_dout(dpp, 20) << "ERROR: POSIXAtomicWriter failed writing attr " << attr.first << dendl;
+      return ret;
+    }
+  }
+
+  ret = obj.link_temp_file(dpp, y);
+  if (ret < 0) {
+    ldpp_dout(dpp, 20) << "ERROR: POSIXAtomicWriter failed writing temp file" << dendl;
+    return ret;
+  }
+
+  ret = obj.close();
+  if (ret < 0) {
+    ldpp_dout(dpp, 20) << "ERROR: POSIXAtomicWriter failed closing file" << dendl;
+    return ret;
+  }
+
+  return 0;
+}
+
+} } // namespace rgw::sal
+
+extern "C" {
+
+rgw::sal::Driver* newPOSIXDriver(rgw::sal::Driver* next)
+{
+  rgw::sal::POSIXDriver* driver = new rgw::sal::POSIXDriver(next);
+
+  return driver;
+}
+
+}
diff --git a/src/rgw/driver/posix/rgw_sal_posix.h b/src/rgw/driver/posix/rgw_sal_posix.h
new file mode 100644 (file)
index 0000000..bd1285c
--- /dev/null
@@ -0,0 +1,688 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright contributors to the Ceph project
+ *
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "rgw_sal_filter.h"
+#include "rgw_sal_store.h"
+#include <memory>
+#include "common/dout.h"
+#include "bucket_cache.h"
+
+namespace rgw { namespace sal {
+
+class POSIXDriver;
+class POSIXBucket;
+class POSIXObject;
+
+using BucketCache = file::listing::BucketCache<POSIXDriver, POSIXBucket>;
+
+class POSIXDriver : public FilterDriver {
+private:
+
+  std::unique_ptr<BucketCache> bucket_cache;
+  std::string base_path;
+  int root_fd;
+
+public:
+  POSIXDriver(Driver* _next) : FilterDriver(_next)
+  { }
+  virtual ~POSIXDriver() { close(); }
+  virtual int initialize(CephContext *cct, const DoutPrefixProvider *dpp) override;
+  virtual std::unique_ptr<User> get_user(const rgw_user& u) override;
+  virtual int get_user_by_access_key(const DoutPrefixProvider* dpp, const
+                                    std::string& key, optional_yield y,
+                                    std::unique_ptr<User>* user) override;
+  virtual int get_user_by_email(const DoutPrefixProvider* dpp, const
+                               std::string& email, optional_yield y,
+                               std::unique_ptr<User>* user) override;
+  virtual int get_user_by_swift(const DoutPrefixProvider* dpp, const
+                               std::string& user_str, optional_yield y,
+                               std::unique_ptr<User>* user) override;
+  virtual std::unique_ptr<Object> get_object(const rgw_obj_key& k) override;
+  virtual int get_bucket(User* u, const RGWBucketInfo& i,
+                        std::unique_ptr<Bucket>* bucket) override;
+  virtual int get_bucket(const DoutPrefixProvider* dpp, User* u, const
+                        rgw_bucket& b, std::unique_ptr<Bucket>* bucket,
+                        optional_yield y) override;
+  virtual int get_bucket(const DoutPrefixProvider* dpp, User* u, const
+                        std::string& tenant, const std::string& name,
+                        std::unique_ptr<Bucket>* bucket, optional_yield y) override;
+  virtual std::string zone_unique_trans_id(const uint64_t unique_num) override;
+
+  virtual std::unique_ptr<Writer> get_append_writer(const DoutPrefixProvider *dpp,
+                                 optional_yield y,
+                                 rgw::sal::Object* _head_obj,
+                                 const rgw_user& owner,
+                                 const rgw_placement_rule *ptail_placement_rule,
+                                 const std::string& unique_tag,
+                                 uint64_t position,
+                                 uint64_t *cur_accounted_size) override;
+  virtual std::unique_ptr<Writer> get_atomic_writer(const DoutPrefixProvider *dpp,
+                                 optional_yield y,
+                                 rgw::sal::Object* _head_obj,
+                                 const rgw_user& owner,
+                                 const rgw_placement_rule *ptail_placement_rule,
+                                 uint64_t olh_epoch,
+                                 const std::string& unique_tag) override;
+
+  virtual void finalize(void) override;
+  virtual void register_admin_apis(RGWRESTMgr* mgr) override;
+
+  virtual std::unique_ptr<Notification> get_notification(rgw::sal::Object* obj,
+                                rgw::sal::Object* src_obj, struct req_state* s,
+                                rgw::notify::EventType event_type, optional_yield y,
+                                const std::string* object_name=nullptr) override;
+
+  virtual std::unique_ptr<Notification> get_notification(
+                                  const DoutPrefixProvider* dpp,
+                                  rgw::sal::Object* obj,
+                                  rgw::sal::Object* src_obj,
+                                  rgw::notify::EventType event_type,
+                                  rgw::sal::Bucket* _bucket,
+                                  std::string& _user_id,
+                                  std::string& _user_tenant,
+                                  std::string& _req_id,
+                                  optional_yield y) override;
+
+  /* Internal APIs */
+  int get_root_fd() { return root_fd; }
+  const std::string& get_base_path() const { return base_path; }
+  BucketCache* get_bucket_cache() { return bucket_cache.get(); }
+
+  int close();
+
+  /* called by BucketCache layer when a new object is discovered
+   * by inotify or similar */
+  int mint_listing_entry(
+    const std::string& bucket, rgw_bucket_dir_entry& bde /* OUT */);
+
+};
+
+class POSIXUser : public FilterUser {
+private:
+  POSIXDriver* driver;
+
+public:
+  POSIXUser(std::unique_ptr<User> _next, POSIXDriver* _driver) :
+    FilterUser(std::move(_next)),
+    driver(_driver) {}
+  virtual ~POSIXUser() = default;
+
+  virtual int list_buckets(const DoutPrefixProvider* dpp,
+                          const std::string& marker, const std::string& end_marker,
+                          uint64_t max, bool need_stats, BucketList& buckets,
+                          optional_yield y) override;
+  virtual int create_bucket(const DoutPrefixProvider* dpp,
+                            const rgw_bucket& b,
+                            const std::string& zonegroup_id,
+                            rgw_placement_rule& placement_rule,
+                            std::string& swift_ver_location,
+                            const RGWQuotaInfo* pquota_info,
+                            const RGWAccessControlPolicy& policy,
+                            Attrs& attrs,
+                            RGWBucketInfo& info,
+                            obj_version& ep_objv,
+                            bool exclusive,
+                            bool obj_lock_enabled,
+                            bool* existed,
+                            req_info& req_info,
+                            std::unique_ptr<Bucket>* bucket,
+                            optional_yield y) override;
+  virtual Attrs& get_attrs() override { return next->get_attrs(); }
+  virtual void set_attrs(Attrs& _attrs) override { next->set_attrs(_attrs); }
+  virtual int read_attrs(const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int merge_and_store_attrs(const DoutPrefixProvider* dpp, Attrs&
+                                   new_attrs, optional_yield y) override;
+  virtual int load_user(const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int store_user(const DoutPrefixProvider* dpp, optional_yield y, bool
+                        exclusive, RGWUserInfo* old_info = nullptr) override;
+  virtual int remove_user(const DoutPrefixProvider* dpp, optional_yield y) override;
+};
+
+class POSIXBucket : public StoreBucket {
+private:
+  POSIXDriver* driver;
+  int parent_fd{-1};
+  int dir_fd{-1};
+  struct statx stx;
+  bool stat_done{false};
+  RGWAccessControlPolicy acls;
+  std::optional<std::string> ns;
+
+public:
+  POSIXBucket(POSIXDriver *_dr, int _p_fd, const rgw_bucket& _b, User* _u, std::optional<std::string> _ns = std::nullopt)
+    : StoreBucket(_b, _u),
+    driver(_dr),
+    parent_fd(_p_fd),
+    acls(),
+    ns(_ns)
+    { }
+
+  POSIXBucket(POSIXDriver *_dr, int _p_fd, const RGWBucketEnt& _e, User* _u)
+    : StoreBucket(_e, _u),
+    driver(_dr),
+    parent_fd(_p_fd),
+    acls()
+    { }
+
+  POSIXBucket(POSIXDriver *_dr, int _p_fd, const RGWBucketInfo& _i, User* _u)
+    : StoreBucket(_i, _u),
+    driver(_dr),
+    parent_fd(_p_fd),
+    acls()
+    { }
+
+  POSIXBucket(const POSIXBucket& _b) :
+    StoreBucket(_b),
+    driver(_b.driver),
+    parent_fd(_b.parent_fd),
+    /* Don't want to copy dir_fd */
+    stx(_b.stx),
+    stat_done(_b.stat_done),
+    acls(_b.acls),
+    ns(_b.ns) {}
+
+  virtual ~POSIXBucket() { close(); }
+
+  virtual std::unique_ptr<Object> get_object(const rgw_obj_key& key) override;
+  virtual int list(const DoutPrefixProvider* dpp, ListParams&, int,
+                  ListResults&, optional_yield y) override;
+  virtual int merge_and_store_attrs(const DoutPrefixProvider* dpp,
+                                   Attrs& new_attrs, optional_yield y) override;
+  virtual int remove_bucket(const DoutPrefixProvider* dpp, bool delete_children,
+                           bool forward_to_master, req_info* req_info,
+                           optional_yield y) override;
+  virtual int remove_bucket_bypass_gc(int concurrent_max,
+                                     bool keep_index_consistent,
+                                     optional_yield y,
+                                     const DoutPrefixProvider *dpp) override;
+  virtual int load_bucket(const DoutPrefixProvider* dpp, optional_yield y,
+                         bool get_stats = false) override;
+  virtual RGWAccessControlPolicy& get_acl(void) override { return acls; }
+  virtual int set_acl(const DoutPrefixProvider* dpp, RGWAccessControlPolicy& acl,
+                     optional_yield y) override;
+  virtual int read_stats(const DoutPrefixProvider *dpp,
+                        const bucket_index_layout_generation& idx_layout,
+                        int shard_id, std::string* bucket_ver, std::string* master_ver,
+                        std::map<RGWObjCategory, RGWStorageStats>& stats,
+                        std::string* max_marker = nullptr,
+                        bool* syncstopped = nullptr) override;
+  virtual int read_stats_async(const DoutPrefixProvider *dpp,
+                              const bucket_index_layout_generation& idx_layout,
+                              int shard_id, RGWGetBucketStats_CB* ctx) override;
+  virtual int sync_user_stats(const DoutPrefixProvider *dpp, optional_yield y) override;
+  virtual int update_container_stats(const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int check_bucket_shards(const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int chown(const DoutPrefixProvider* dpp, User& new_user, optional_yield y) override;
+  virtual int put_info(const DoutPrefixProvider* dpp, bool exclusive,
+                      ceph::real_time mtime, optional_yield y) override;
+  virtual int check_empty(const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int check_quota(const DoutPrefixProvider *dpp, RGWQuota& quota, uint64_t obj_size, optional_yield y, bool check_size_only = false) override;
+  virtual int try_refresh_info(const DoutPrefixProvider* dpp, ceph::real_time* pmtime, optional_yield y) override;
+  virtual int read_usage(const DoutPrefixProvider *dpp, uint64_t start_epoch,
+                        uint64_t end_epoch, uint32_t max_entries, bool* is_truncated,
+                        RGWUsageIter& usage_iter, std::map<rgw_user_bucket, rgw_usage_log_entry>& usage) override;
+  virtual int trim_usage(const DoutPrefixProvider *dpp, uint64_t start_epoch, uint64_t end_epoch, optional_yield y) override;
+  virtual int remove_objs_from_index(const DoutPrefixProvider *dpp, std::list<rgw_obj_index_key>& objs_to_unlink) override;
+  virtual int check_index(const DoutPrefixProvider *dpp, std::map<RGWObjCategory, RGWStorageStats>& existing_stats, std::map<RGWObjCategory, RGWStorageStats>& calculated_stats) override;
+  virtual int rebuild_index(const DoutPrefixProvider *dpp) override;
+  virtual int set_tag_timeout(const DoutPrefixProvider *dpp, uint64_t timeout) override;
+  virtual int purge_instance(const DoutPrefixProvider* dpp, optional_yield y) override;
+
+  virtual std::unique_ptr<Bucket> clone() override {
+    return std::make_unique<POSIXBucket>(*this);
+  }
+
+  virtual std::unique_ptr<MultipartUpload> get_multipart_upload(
+                               const std::string& oid,
+                               std::optional<std::string> upload_id=std::nullopt,
+                               ACLOwner owner={}, ceph::real_time mtime=real_clock::now()) override;
+  virtual int list_multiparts(const DoutPrefixProvider *dpp,
+                             const std::string& prefix,
+                             std::string& marker,
+                             const std::string& delim,
+                             const int& max_uploads,
+                             std::vector<std::unique_ptr<MultipartUpload>>& uploads,
+                             std::map<std::string, bool> *common_prefixes,
+                             bool *is_truncated, optional_yield y) override;
+  virtual int abort_multiparts(const DoutPrefixProvider* dpp,
+                              CephContext* cct, optional_yield y) override;
+
+  /* Internal APIs */
+  int create(const DoutPrefixProvider *dpp, optional_yield y, bool* existed);
+  void set_stat(struct statx _stx) { stx = _stx; stat_done = true; }
+  int get_dir_fd(const DoutPrefixProvider *dpp) { open(dpp); return dir_fd; }
+  /* TODO dang Escape the bucket name for file use */
+  std::string get_fname();
+  int get_shadow_bucket(const DoutPrefixProvider* dpp, optional_yield y,
+                       const std::string& ns, const std::string& tenant,
+                       const std::string& name, bool create,
+                       std::unique_ptr<POSIXBucket>* shadow);
+  template <typename F>
+    int for_each(const DoutPrefixProvider* dpp, const F& func);
+  int open(const DoutPrefixProvider *dpp);
+  int close();
+  int rename(const DoutPrefixProvider* dpp, optional_yield y, Object* target_obj);
+  int copy(const DoutPrefixProvider *dpp, optional_yield y, POSIXBucket* db, POSIXObject* dobj);
+
+  /* integration w/bucket listing cache */
+  using fill_cache_cb_t = file::listing::fill_cache_cb_t;
+
+  /* enumerate all entries by callback, in any order */
+  int fill_cache(const DoutPrefixProvider* dpp, optional_yield y, fill_cache_cb_t cb);
+  
+private:
+  int stat(const DoutPrefixProvider *dpp);
+  int write_attrs(const DoutPrefixProvider *dpp, optional_yield y);
+}; /* POSIXBucket */
+
+class POSIXObject : public StoreObject {
+private:
+  POSIXDriver* driver;
+  RGWAccessControlPolicy acls;
+  int obj_fd{-1};
+  struct statx stx;
+  bool stat_done{false};
+  std::unique_ptr<rgw::sal::POSIXBucket> shadow;
+  std::string temp_fname;
+  std::map<std::string, int64_t> parts;
+
+public:
+  struct POSIXReadOp : ReadOp {
+    POSIXObject* source;
+
+    POSIXReadOp(POSIXObject* _source) :
+      source(_source) {}
+    virtual ~POSIXReadOp() = default;
+
+    virtual int prepare(optional_yield y, const DoutPrefixProvider* dpp) override;
+    virtual int read(int64_t ofs, int64_t left, bufferlist& bl, optional_yield y,
+                    const DoutPrefixProvider* dpp) override;
+    virtual int iterate(const DoutPrefixProvider* dpp, int64_t ofs, int64_t end,
+                       RGWGetDataCB* cb, optional_yield y) override;
+    virtual int get_attr(const DoutPrefixProvider* dpp, const char* name,
+                        bufferlist& dest, optional_yield y) override;
+  };
+
+  struct POSIXDeleteOp : DeleteOp {
+    POSIXObject* source;
+
+    POSIXDeleteOp(POSIXObject* _source) :
+      source(_source) {}
+    virtual ~POSIXDeleteOp() = default;
+
+    virtual int delete_obj(const DoutPrefixProvider* dpp, optional_yield y) override;
+  };
+
+  POSIXObject(POSIXDriver *_dr, const rgw_obj_key& _k)
+    : StoreObject(_k),
+    driver(_dr),
+    acls() {}
+
+  POSIXObject(POSIXDriver* _driver, const rgw_obj_key& _k, Bucket* _b) :
+    StoreObject(_k, _b),
+    driver(_driver),
+    acls() {}
+
+  POSIXObject(const POSIXObject& _o) :
+    StoreObject(_o),
+    driver(_o.driver) {}
+
+  virtual ~POSIXObject() { close(); }
+
+  virtual int delete_object(const DoutPrefixProvider* dpp,
+                           optional_yield y,
+                           bool prevent_versioning = false) override;
+  virtual int copy_object(User* user,
+               req_info* info, const rgw_zone_id& source_zone,
+               rgw::sal::Object* dest_object, rgw::sal::Bucket* dest_bucket,
+               rgw::sal::Bucket* src_bucket,
+               const rgw_placement_rule& dest_placement,
+               ceph::real_time* src_mtime, ceph::real_time* mtime,
+               const ceph::real_time* mod_ptr, const ceph::real_time* unmod_ptr,
+               bool high_precision_time,
+               const char* if_match, const char* if_nomatch,
+               AttrsMod attrs_mod, bool copy_if_newer, Attrs& attrs,
+               RGWObjCategory category, uint64_t olh_epoch,
+               boost::optional<ceph::real_time> delete_at,
+               std::string* version_id, std::string* tag, std::string* etag,
+               void (*progress_cb)(off_t, void *), void* progress_data,
+               const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual RGWAccessControlPolicy& get_acl(void) override { return acls; }
+  virtual int set_acl(const RGWAccessControlPolicy& acl) override { acls = acl; return 0; }
+  virtual int get_obj_state(const DoutPrefixProvider* dpp, RGWObjState **state, optional_yield y, bool follow_olh = true) override;
+  virtual int set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs,
+                           Attrs* delattrs, optional_yield y) override;
+  virtual int get_obj_attrs(optional_yield y, const DoutPrefixProvider* dpp,
+                           rgw_obj* target_obj = NULL) override;
+  virtual int modify_obj_attrs(const char* attr_name, bufferlist& attr_val,
+                              optional_yield y, const DoutPrefixProvider* dpp) override;
+  virtual int delete_obj_attrs(const DoutPrefixProvider* dpp, const char* attr_name,
+                              optional_yield y) override;
+  virtual bool is_expired() override;
+  virtual void gen_rand_obj_instance_name() override;
+  virtual std::unique_ptr<MPSerializer> get_serializer(const DoutPrefixProvider *dpp,
+                                                      const std::string& lock_name) override;
+  virtual int transition(Bucket* bucket,
+                        const rgw_placement_rule& placement_rule,
+                        const real_time& mtime,
+                        uint64_t olh_epoch,
+                        const DoutPrefixProvider* dpp,
+                        optional_yield y) override;
+  virtual int transition_to_cloud(Bucket* bucket,
+                        rgw::sal::PlacementTier* tier,
+                        rgw_bucket_dir_entry& o,
+                        std::set<std::string>& cloud_targets,
+                        CephContext* cct,
+                        bool update_object,
+                        const DoutPrefixProvider* dpp,
+                        optional_yield y) override;
+  virtual bool placement_rules_match(rgw_placement_rule& r1, rgw_placement_rule& r2) override;
+  virtual int dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f) override;
+  virtual int swift_versioning_restore(bool& restored,
+                                      const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual int swift_versioning_copy(const DoutPrefixProvider* dpp,
+                                   optional_yield y) override;
+  virtual std::unique_ptr<ReadOp> get_read_op() override;
+  virtual std::unique_ptr<DeleteOp> get_delete_op() override;
+  virtual int omap_get_vals_by_keys(const DoutPrefixProvider *dpp, const std::string& oid,
+                                   const std::set<std::string>& keys,
+                                   Attrs* vals) override;
+  virtual int omap_set_val_by_key(const DoutPrefixProvider *dpp, const std::string& key,
+                                 bufferlist& val, bool must_exist, optional_yield y) override;
+  virtual int chown(User& new_user, const DoutPrefixProvider* dpp, optional_yield y) override;
+  virtual std::unique_ptr<Object> clone() override {
+    return std::unique_ptr<Object>(new POSIXObject(*this));
+  }
+
+  int open(const DoutPrefixProvider *dpp, bool create, bool temp_file = false);
+  int close();
+  int write(int64_t ofs, bufferlist& bl, const DoutPrefixProvider* dpp, optional_yield y);
+  int write_attr(const DoutPrefixProvider* dpp, optional_yield y, const std::string& key, bufferlist& value);
+  int link_temp_file(const DoutPrefixProvider* dpp, optional_yield y);
+  void gen_temp_fname();
+  /* TODO dang Escape the object name for file use */
+  const std::string get_fname();
+  bool exists(const DoutPrefixProvider* dpp) { stat(dpp); return state.exists; }
+  int get_owner(const DoutPrefixProvider *dpp, optional_yield y, std::unique_ptr<User> *owner);
+  int copy(const DoutPrefixProvider *dpp, optional_yield y, POSIXBucket *sb,
+           POSIXBucket *db, POSIXObject *dobj);
+  int fill_bde(const DoutPrefixProvider *dpp, optional_yield y, rgw_bucket_dir_entry &bde);
+
+protected:
+  int read(int64_t ofs, int64_t end, bufferlist& bl, const DoutPrefixProvider* dpp, optional_yield y);
+  int generate_attrs(const DoutPrefixProvider* dpp, optional_yield y);
+  int get_fd() { return obj_fd; };
+private:
+  const std::string get_temp_fname();
+  int stat(const DoutPrefixProvider *dpp);
+  int generate_mp_etag(const DoutPrefixProvider* dpp, optional_yield y);
+  int generate_etag(const DoutPrefixProvider* dpp, optional_yield y);
+};
+
+struct POSIXMPObj {
+  std::string oid;
+  std::string upload_id;
+  ACLOwner owner;
+  multipart_upload_info upload_info;
+  std::string meta;
+
+  POSIXMPObj(POSIXDriver* driver, const std::string& _oid,
+            std::optional<std::string> _upload_id, ACLOwner& _owner) {
+    if (_upload_id && !_upload_id->empty()) {
+      init(_oid, *_upload_id, _owner);
+    } else if (!from_meta(_oid, _owner)) {
+      init_gen(driver, _oid, _owner);
+    }
+  }
+  void init(const std::string& _oid, const std::string& _upload_id, ACLOwner& _owner) {
+    if (_oid.empty()) {
+      clear();
+      return;
+    }
+    oid = _oid;
+    upload_id = _upload_id;
+    owner = _owner;
+    meta = oid;
+    if (!upload_id.empty())
+      meta += "." + upload_id;
+  }
+  void init_gen(POSIXDriver* driver, const std::string& _oid, ACLOwner& _owner);
+  bool from_meta(const std::string& meta, ACLOwner& _owner) {
+    int end_pos = meta.length();
+    int mid_pos = meta.rfind('.', end_pos - 1); // <key>.<upload_id>
+    if (mid_pos < 0)
+      return false;
+    oid = meta.substr(0, mid_pos);
+    upload_id = meta.substr(mid_pos + 1, end_pos - mid_pos - 1);
+    init(oid, upload_id, _owner);
+    return true;
+  }
+  void clear() {
+    oid = "";
+    meta = "";
+    upload_id = "";
+  }
+  void encode(bufferlist& bl) const {
+    ENCODE_START(1, 1, bl);
+    encode(oid, bl);
+    encode(upload_id, bl);
+    encode(owner, bl);
+    encode(upload_info, bl);
+    encode(meta, bl);
+    ENCODE_FINISH(bl);
+  }
+
+  void decode(bufferlist::const_iterator& bl) {
+    DECODE_START(1, bl);
+    decode(oid, bl);
+    decode(upload_id, bl);
+    decode(owner, bl);
+    decode(upload_info, bl);
+    decode(meta, bl);
+    DECODE_FINISH(bl);
+  }
+};
+WRITE_CLASS_ENCODER(POSIXMPObj)
+
+struct POSIXUploadPartInfo {
+  uint32_t num{0};
+  std::string etag;
+  ceph::real_time mtime;
+
+  void encode(bufferlist& bl) const {
+    ENCODE_START(1, 1, bl);
+    encode(num, bl);
+    encode(etag, bl);
+    encode(mtime, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::const_iterator& bl) {
+    DECODE_START(1, bl);
+    decode(num, bl);
+    decode(etag, bl);
+    decode(mtime, bl);
+    DECODE_FINISH(bl);
+  }
+};
+WRITE_CLASS_ENCODER(POSIXUploadPartInfo)
+
+class POSIXMultipartUpload;
+
+class POSIXMultipartPart : public StoreMultipartPart {
+protected:
+  POSIXUploadPartInfo info;
+  POSIXMultipartUpload* upload;
+  std::unique_ptr<rgw::sal::POSIXObject> shadow;
+
+public:
+  POSIXMultipartPart(POSIXMultipartUpload* _upload) :
+    upload(_upload) {}
+  virtual ~POSIXMultipartPart() = default;
+
+  virtual uint32_t get_num() { return info.num; }
+  virtual uint64_t get_size() { return shadow->get_obj_size(); }
+  virtual const std::string& get_etag() { return info.etag; }
+  virtual ceph::real_time& get_mtime() { return info.mtime; }
+
+  int load(const DoutPrefixProvider* dpp, optional_yield y, POSIXDriver* driver, rgw_obj_key& key);
+
+  friend class POSIXMultipartUpload;
+};
+
+class POSIXMultipartUpload : public StoreMultipartUpload {
+protected:
+  POSIXDriver* driver;
+  POSIXMPObj mp_obj;
+  ceph::real_time mtime;
+  std::unique_ptr<rgw::sal::POSIXBucket> shadow;
+
+public:
+  POSIXMultipartUpload(POSIXDriver* _driver, Bucket* _bucket, const std::string& _oid,
+                      std::optional<std::string> _upload_id, ACLOwner _owner,
+                      ceph::real_time _mtime) :
+    StoreMultipartUpload(_bucket), driver(_driver),
+    mp_obj(driver, _oid, _upload_id, _owner), mtime(_mtime) {}
+  virtual ~POSIXMultipartUpload() = default;
+
+  virtual const std::string& get_meta() const override { return mp_obj.meta; }
+  virtual const std::string& get_key() const override { return mp_obj.oid; }
+  virtual const std::string& get_upload_id() const override { return mp_obj.upload_id; }
+  virtual const ACLOwner& get_owner() const override { return mp_obj.owner; }
+  virtual ceph::real_time& get_mtime() override { return mtime; }
+  virtual std::unique_ptr<rgw::sal::Object> get_meta_obj() override;
+
+  virtual int init(const DoutPrefixProvider* dpp, optional_yield y, ACLOwner& owner, rgw_placement_rule& dest_placement, rgw::sal::Attrs& attrs) override;
+  virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                        int num_parts, int marker,
+                        int* next_marker, bool* truncated, optional_yield y,
+                        bool assume_unsorted = false) override;
+  virtual int abort(const DoutPrefixProvider* dpp, CephContext* cct, optional_yield y) override;
+  virtual int complete(const DoutPrefixProvider* dpp,
+                      optional_yield y, CephContext* cct,
+                      std::map<int, std::string>& part_etags,
+                      std::list<rgw_obj_index_key>& remove_objs,
+                      uint64_t& accounted_size, bool& compressed,
+                      RGWCompressionInfo& cs_info, off_t& ofs,
+                      std::string& tag, ACLOwner& owner,
+                      uint64_t olh_epoch,
+                      rgw::sal::Object* target_obj) override;
+  virtual int get_info(const DoutPrefixProvider *dpp, optional_yield y,
+                      rgw_placement_rule** rule, rgw::sal::Attrs* attrs) override;
+
+  virtual std::unique_ptr<Writer> get_writer(const DoutPrefixProvider *dpp,
+                         optional_yield y,
+                         rgw::sal::Object* _head_obj,
+                         const rgw_user& owner,
+                         const rgw_placement_rule *ptail_placement_rule,
+                         uint64_t part_num,
+                         const std::string& part_num_str) override;
+
+  POSIXBucket* get_shadow() { return shadow.get(); }
+private:
+  int load(bool create=false);
+};
+
+class POSIXAtomicWriter : public StoreWriter {
+private:
+  POSIXDriver* driver;
+  const rgw_user& owner;
+  const rgw_placement_rule *ptail_placement_rule;
+  uint64_t olh_epoch;
+  const std::string& unique_tag;
+  POSIXObject obj;
+
+public:
+  POSIXAtomicWriter(const DoutPrefixProvider *dpp,
+                    optional_yield y,
+                   rgw::sal::Object* _head_obj,
+                    POSIXDriver* _driver,
+                    const rgw_user& _owner,
+                    const rgw_placement_rule *_ptail_placement_rule,
+                    uint64_t _olh_epoch,
+                    const std::string& _unique_tag) :
+    StoreWriter(dpp, y),
+    driver(_driver),
+    owner(_owner),
+    ptail_placement_rule(_ptail_placement_rule),
+    olh_epoch(_olh_epoch),
+    unique_tag(_unique_tag),
+    obj(_driver, _head_obj->get_key(), _head_obj->get_bucket()) {}
+  virtual ~POSIXAtomicWriter() = default;
+
+  virtual int prepare(optional_yield y) override;
+  virtual int process(bufferlist&& data, uint64_t offset) override;
+  virtual int complete(size_t accounted_size, const std::string& etag,
+                       ceph::real_time *mtime, ceph::real_time set_mtime,
+                      std::map<std::string, bufferlist>& attrs,
+                      ceph::real_time delete_at,
+                      const char *if_match, const char *if_nomatch,
+                      const std::string *user_data,
+                      rgw_zone_set *zones_trace, bool *canceled,
+                      optional_yield y) override;
+};
+
+class POSIXMultipartWriter : public StoreWriter {
+private:
+  POSIXDriver* driver;
+  const rgw_user& owner;
+  const rgw_placement_rule *ptail_placement_rule;
+  uint64_t part_num;
+  std::unique_ptr<Bucket> shadow_bucket;
+  std::unique_ptr<POSIXObject> obj;
+
+public:
+  POSIXMultipartWriter(const DoutPrefixProvider *dpp,
+                    optional_yield y,
+                   std::unique_ptr<Bucket> _shadow_bucket,
+                    rgw_obj_key& _key,
+                    POSIXDriver* _driver,
+                    const rgw_user& _owner,
+                    const rgw_placement_rule *_ptail_placement_rule,
+                    uint64_t _part_num) :
+    StoreWriter(dpp, y),
+    driver(_driver),
+    owner(_owner),
+    ptail_placement_rule(_ptail_placement_rule),
+    part_num(_part_num),
+    shadow_bucket(std::move(_shadow_bucket)),
+    obj(std::make_unique<POSIXObject>(_driver, _key, shadow_bucket.get())) {}
+  virtual ~POSIXMultipartWriter() = default;
+
+  virtual int prepare(optional_yield y) override;
+  virtual int process(bufferlist&& data, uint64_t offset) override;
+  virtual int complete(size_t accounted_size, const std::string& etag,
+                       ceph::real_time *mtime, ceph::real_time set_mtime,
+                      std::map<std::string, bufferlist>& attrs,
+                      ceph::real_time delete_at,
+                      const char *if_match, const char *if_nomatch,
+                      const std::string *user_data,
+                      rgw_zone_set *zones_trace, bool *canceled,
+                      optional_yield y) override;
+
+};
+
+class MPPOSIXSerializer : public StoreMPSerializer {
+  POSIXObject* obj;
+
+public:
+  MPPOSIXSerializer(const DoutPrefixProvider *dpp, POSIXDriver* driver, POSIXObject* _obj, const std::string& lock_name) : obj(_obj) {}
+
+  virtual int try_lock(const DoutPrefixProvider *dpp, utime_t dur, optional_yield y) override;
+  virtual int unlock() override { return 0; }
+};
+
+} } // namespace rgw::sal
diff --git a/src/rgw/driver/posix/unordered_dense.h b/src/rgw/driver/posix/unordered_dense.h
new file mode 100644 (file)
index 0000000..faad051
--- /dev/null
@@ -0,0 +1,1584 @@
+///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////
+
+// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
+// Version 3.1.0
+// https://github.com/martinus/unordered_dense
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2022-2023 Martin Leitner-Ankerl <martin.ankerl@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef ANKERL_UNORDERED_DENSE_H
+#define ANKERL_UNORDERED_DENSE_H
+
+// see https://semver.org/spec/v2.0.0.html
+#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 3 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes
+#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality
+#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes
+
+// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/
+#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch
+#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch)
+#define ANKERL_UNORDERED_DENSE_NAMESPACE   \
+    ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \
+        ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH)
+
+#if defined(_MSVC_LANG)
+#    define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG
+#else
+#    define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus
+#endif
+
+#if defined(__GNUC__)
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#    define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__))
+#elif defined(_MSC_VER)
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#    define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop))
+#endif
+
+// exceptions
+#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
+#    define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1
+#else
+#    define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0
+#endif
+#ifdef _MSC_VER
+#    define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline)
+#else
+#    define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline))
+#endif
+
+#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L
+#    error ankerl::unordered_dense requires C++17 or higher
+#else
+#    include <array>            // for array
+#    include <cstdint>          // for uint64_t, uint32_t, uint8_t, UINT64_C
+#    include <cstring>          // for size_t, memcpy, memset
+#    include <functional>       // for equal_to, hash
+#    include <initializer_list> // for initializer_list
+#    include <iterator>         // for pair, distance
+#    include <limits>           // for numeric_limits
+#    include <memory>           // for allocator, allocator_traits, shared_ptr
+#    include <stdexcept>        // for out_of_range
+#    include <string>           // for basic_string
+#    include <string_view>      // for basic_string_view, hash
+#    include <tuple>            // for forward_as_tuple
+#    include <type_traits>      // for enable_if_t, declval, conditional_t, ena...
+#    include <utility>          // for forward, exchange, pair, as_const, piece...
+#    include <vector>           // for vector
+#    if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0
+#        include <cstdlib> // for abort
+#    endif
+
+#    define ANKERL_UNORDERED_DENSE_PMR 0 // NOLINT(cppcoreguidelines-macro-usage)
+#    if defined(__has_include)
+#        if __has_include(<memory_resource>)
+#            undef ANKERL_UNORDERED_DENSE_PMR
+#            define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage)
+#            define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \
+                std::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage)
+#            include <memory_resource>          // for polymorphic_allocator
+#        elif __has_include(<experimental/memory_resource>)
+#            undef ANKERL_UNORDERED_DENSE_PMR
+#            define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage)
+#            define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \
+                std::experimental::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage)
+#            include <experimental/memory_resource>           // for polymorphic_allocator
+#        endif
+#    endif
+
+#    if defined(_MSC_VER) && defined(_M_X64)
+#        include <intrin.h>
+#        pragma intrinsic(_umul128)
+#    endif
+
+#    if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
+#        define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1)   // NOLINT(cppcoreguidelines-macro-usage)
+#        define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage)
+#    else
+#        define ANKERL_UNORDERED_DENSE_LIKELY(x) (x)   // NOLINT(cppcoreguidelines-macro-usage)
+#        define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage)
+#    endif
+
+namespace ankerl::unordered_dense {
+inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE {
+
+namespace detail {
+
+#    if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS()
+
+// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
+// inlinings more difficult. Throws are also generally the slow path.
+[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() {
+    throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found");
+}
+[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() {
+    throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size");
+}
+[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() {
+    throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements");
+}
+
+#    else
+
+[[noreturn]] inline void on_error_key_not_found() {
+    abort();
+}
+[[noreturn]] inline void on_error_bucket_overflow() {
+    abort();
+}
+[[noreturn]] inline void on_error_too_many_elements() {
+    abort();
+}
+
+#    endif
+
+} // namespace detail
+
+// hash ///////////////////////////////////////////////////////////////////////
+
+// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash
+// No big-endian support (because different values on different machines don't matter),
+// hardcodes seed and the secret, reformattes the code, and clang-tidy fixes.
+namespace detail::wyhash {
+
+static inline void mum(uint64_t* a, uint64_t* b) {
+#    if defined(__SIZEOF_INT128__)
+    __uint128_t r = *a;
+    r *= *b;
+    *a = static_cast<uint64_t>(r);
+    *b = static_cast<uint64_t>(r >> 64U);
+#    elif defined(_MSC_VER) && defined(_M_X64)
+    *a = _umul128(*a, *b, b);
+#    else
+    uint64_t ha = *a >> 32U;
+    uint64_t hb = *b >> 32U;
+    uint64_t la = static_cast<uint32_t>(*a);
+    uint64_t lb = static_cast<uint32_t>(*b);
+    uint64_t hi{};
+    uint64_t lo{};
+    uint64_t rh = ha * hb;
+    uint64_t rm0 = ha * lb;
+    uint64_t rm1 = hb * la;
+    uint64_t rl = la * lb;
+    uint64_t t = rl + (rm0 << 32U);
+    auto c = static_cast<uint64_t>(t < rl);
+    lo = t + (rm1 << 32U);
+    c += static_cast<uint64_t>(lo < t);
+    hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c;
+    *a = lo;
+    *b = hi;
+#    endif
+}
+
+// multiply and xor mix function, aka MUM
+[[nodiscard]] static inline auto mix(uint64_t a, uint64_t b) -> uint64_t {
+    mum(&a, &b);
+    return a ^ b;
+}
+
+// read functions. WARNING: we don't care about endianness, so results are different on big endian!
+[[nodiscard]] static inline auto r8(const uint8_t* p) -> uint64_t {
+    uint64_t v{};
+    std::memcpy(&v, p, 8U);
+    return v;
+}
+
+[[nodiscard]] static inline auto r4(const uint8_t* p) -> uint64_t {
+    uint32_t v{};
+    std::memcpy(&v, p, 4);
+    return v;
+}
+
+// reads 1, 2, or 3 bytes
+[[nodiscard]] static inline auto r3(const uint8_t* p, size_t k) -> uint64_t {
+    return (static_cast<uint64_t>(p[0]) << 16U) | (static_cast<uint64_t>(p[k >> 1U]) << 8U) | p[k - 1];
+}
+
+[[maybe_unused]] [[nodiscard]] static inline auto hash(void const* key, size_t len) -> uint64_t {
+    static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f),
+                                              UINT64_C(0xe7037ed1a0b428db),
+                                              UINT64_C(0x8ebc6af09c88c6e3),
+                                              UINT64_C(0x589965cc75374cc3)};
+
+    auto const* p = static_cast<uint8_t const*>(key);
+    uint64_t seed = secret[0];
+    uint64_t a{};
+    uint64_t b{};
+    if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) {
+        if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) {
+            a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U));
+            b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U));
+        } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) {
+            a = r3(p, len);
+            b = 0;
+        } else {
+            a = 0;
+            b = 0;
+        }
+    } else {
+        size_t i = len;
+        if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) {
+            uint64_t see1 = seed;
+            uint64_t see2 = seed;
+            do {
+                seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed);
+                see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1);
+                see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2);
+                p += 48;
+                i -= 48;
+            } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48));
+            seed ^= see1 ^ see2;
+        }
+        while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) {
+            seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed);
+            i -= 16;
+            p += 16;
+        }
+        a = r8(p + i - 16);
+        b = r8(p + i - 8);
+    }
+
+    return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed));
+}
+
+[[nodiscard]] static inline auto hash(uint64_t x) -> uint64_t {
+    return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15));
+}
+
+} // namespace detail::wyhash
+
+template <typename T, typename Enable = void>
+struct hash {
+    auto operator()(T const& obj) const noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>())))
+        -> uint64_t {
+        return std::hash<T>{}(obj);
+    }
+};
+
+template <typename CharT>
+struct hash<std::basic_string<CharT>> {
+    using is_avalanching = void;
+    auto operator()(std::basic_string<CharT> const& str) const noexcept -> uint64_t {
+        return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size());
+    }
+};
+
+template <typename CharT>
+struct hash<std::basic_string_view<CharT>> {
+    using is_avalanching = void;
+    auto operator()(std::basic_string_view<CharT> const& sv) const noexcept -> uint64_t {
+        return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size());
+    }
+};
+
+template <class T>
+struct hash<T*> {
+    using is_avalanching = void;
+    auto operator()(T* ptr) const noexcept -> uint64_t {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr));
+    }
+};
+
+template <class T>
+struct hash<std::unique_ptr<T>> {
+    using is_avalanching = void;
+    auto operator()(std::unique_ptr<T> const& ptr) const noexcept -> uint64_t {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr.get()));
+    }
+};
+
+template <class T>
+struct hash<std::shared_ptr<T>> {
+    using is_avalanching = void;
+    auto operator()(std::shared_ptr<T> const& ptr) const noexcept -> uint64_t {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr.get()));
+    }
+};
+
+template <typename Enum>
+struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
+    using is_avalanching = void;
+    auto operator()(Enum e) const noexcept -> uint64_t {
+        using underlying = typename std::underlying_type_t<Enum>;
+        return detail::wyhash::hash(static_cast<underlying>(e));
+    }
+};
+
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#    define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T)                    \
+        template <>                                                      \
+        struct hash<T> {                                                 \
+            using is_avalanching = void;                                 \
+            auto operator()(T const& obj) const noexcept -> uint64_t {   \
+                return detail::wyhash::hash(static_cast<uint64_t>(obj)); \
+            }                                                            \
+        }
+
+#    if defined(__GNUC__) && !defined(__clang__)
+#        pragma GCC diagnostic push
+#        pragma GCC diagnostic ignored "-Wuseless-cast"
+#    endif
+// see https://en.cppreference.com/w/cpp/utility/hash
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char);
+#    if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t);
+#    endif
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long);
+ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long);
+
+#    if defined(__GNUC__) && !defined(__clang__)
+#        pragma GCC diagnostic pop
+#    endif
+
+// bucket_type //////////////////////////////////////////////////////////
+
+namespace bucket_type {
+
+struct standard {
+    static constexpr uint32_t dist_inc = 1U << 8U;             // skip 1 byte fingerprint
+    static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint
+
+    uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash
+    uint32_t m_value_idx;            // index into the m_values vector.
+};
+
+ANKERL_UNORDERED_DENSE_PACK(struct big {
+    static constexpr uint32_t dist_inc = 1U << 8U;             // skip 1 byte fingerprint
+    static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint
+
+    uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash
+    size_t m_value_idx;              // index into the m_values vector.
+});
+
+} // namespace bucket_type
+
+namespace detail {
+
+struct nonesuch {};
+
+template <class Default, class AlwaysVoid, template <class...> class Op, class... Args>
+struct detector {
+    using value_t = std::false_type;
+    using type = Default;
+};
+
+template <class Default, template <class...> class Op, class... Args>
+struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
+    using value_t = std::true_type;
+    using type = Op<Args...>;
+};
+
+template <template <class...> class Op, class... Args>
+using is_detected = typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
+
+template <template <class...> class Op, class... Args>
+constexpr bool is_detected_v = is_detected<Op, Args...>::value;
+
+template <typename T>
+using detect_avalanching = typename T::is_avalanching;
+
+template <typename T>
+using detect_is_transparent = typename T::is_transparent;
+
+template <typename T>
+using detect_iterator = typename T::iterator;
+
+template <typename T>
+using detect_reserve = decltype(std::declval<T&>().reserve(size_t{}));
+
+// enable_if helpers
+
+template <typename Mapped>
+constexpr bool is_map_v = !std::is_void_v<Mapped>;
+
+// clang-format off
+template <typename Hash, typename KeyEqual>
+constexpr bool is_transparent_v = is_detected_v<detect_is_transparent, Hash>&& is_detected_v<detect_is_transparent, KeyEqual>;
+// clang-format on
+
+template <typename From, typename To1, typename To2>
+constexpr bool is_neither_convertible_v = !std::is_convertible_v<From, To1> && !std::is_convertible_v<From, To2>;
+
+template <typename T>
+constexpr bool has_reserve = is_detected_v<detect_reserve, T>;
+
+// base type for map has mapped_type
+template <class T>
+struct base_table_type_map {
+    using mapped_type = T;
+};
+
+// base type for set doesn't have mapped_type
+struct base_table_type_set {};
+
+// This is it, the table. Doubles as map and set, and uses `void` for T when its used as a set.
+template <class Key,
+          class T, // when void, treat it as a set.
+          class Hash,
+          class KeyEqual,
+          class AllocatorOrContainer,
+          class Bucket>
+class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, base_table_type_set> {
+public:
+    using value_container_type = std::conditional_t<
+        is_detected_v<detect_iterator, AllocatorOrContainer>,
+        AllocatorOrContainer,
+        typename std::vector<typename std::conditional_t<is_map_v<T>, std::pair<Key, T>, Key>, AllocatorOrContainer>>;
+
+private:
+    using bucket_alloc =
+        typename std::allocator_traits<typename value_container_type::allocator_type>::template rebind_alloc<Bucket>;
+    using bucket_alloc_traits = std::allocator_traits<bucket_alloc>;
+
+    static constexpr uint8_t initial_shifts = 64 - 3; // 2^(64-m_shift) number of buckets
+    static constexpr float default_max_load_factor = 0.8F;
+
+public:
+    using key_type = Key;
+    using value_type = typename value_container_type::value_type;
+    using size_type = typename value_container_type::size_type;
+    using difference_type = typename value_container_type::difference_type;
+    using hasher = Hash;
+    using key_equal = KeyEqual;
+    using allocator_type = typename value_container_type::allocator_type;
+    using reference = typename value_container_type::reference;
+    using const_reference = typename value_container_type::const_reference;
+    using pointer = typename value_container_type::pointer;
+    using const_pointer = typename value_container_type::const_pointer;
+    using const_iterator = typename value_container_type::const_iterator;
+    using iterator = std::conditional_t<is_map_v<T>, typename value_container_type::iterator, const_iterator>;
+    using bucket_type = Bucket;
+
+private:
+    using value_idx_type = decltype(Bucket::m_value_idx);
+    using dist_and_fingerprint_type = decltype(Bucket::m_dist_and_fingerprint);
+
+    static_assert(std::is_trivially_destructible_v<Bucket>, "assert there's no need to call destructor / std::destroy");
+    static_assert(std::is_trivially_copyable_v<Bucket>, "assert we can just memset / memcpy");
+
+    value_container_type m_values{}; // Contains all the key-value pairs in one densely stored container. No holes.
+    typename std::allocator_traits<bucket_alloc>::pointer m_buckets{};
+    size_t m_num_buckets = 0;
+    size_t m_max_bucket_capacity = 0;
+    float m_max_load_factor = default_max_load_factor;
+    Hash m_hash{};
+    KeyEqual m_equal{};
+    uint8_t m_shifts = initial_shifts;
+
+    [[nodiscard]] auto next(value_idx_type bucket_idx) const -> value_idx_type {
+        return ANKERL_UNORDERED_DENSE_UNLIKELY(bucket_idx + 1U == m_num_buckets)
+                   ? 0
+                   : static_cast<value_idx_type>(bucket_idx + 1U);
+    }
+
+    // Helper to access bucket through pointer types
+    [[nodiscard]] static constexpr auto at(typename std::allocator_traits<bucket_alloc>::pointer bucket_ptr, size_t offset)
+        -> Bucket& {
+        return *(bucket_ptr + static_cast<typename std::allocator_traits<bucket_alloc>::difference_type>(offset));
+    }
+
+    // use the dist_inc and dist_dec functions so that uint16_t types work without warning
+    [[nodiscard]] static constexpr auto dist_inc(dist_and_fingerprint_type x) -> dist_and_fingerprint_type {
+        return static_cast<dist_and_fingerprint_type>(x + Bucket::dist_inc);
+    }
+
+    [[nodiscard]] static constexpr auto dist_dec(dist_and_fingerprint_type x) -> dist_and_fingerprint_type {
+        return static_cast<dist_and_fingerprint_type>(x - Bucket::dist_inc);
+    }
+
+    // The goal of mixed_hash is to always produce a high quality 64bit hash.
+    template <typename K>
+    [[nodiscard]] constexpr auto mixed_hash(K const& key) const -> uint64_t {
+        if constexpr (is_detected_v<detect_avalanching, Hash>) {
+            // we know that the hash is good because is_avalanching.
+            if constexpr (sizeof(decltype(m_hash(key))) < sizeof(uint64_t)) {
+                // 32bit hash and is_avalanching => multiply with a constant to avalanche bits upwards
+                return m_hash(key) * UINT64_C(0x9ddfea08eb382d69);
+            } else {
+                // 64bit and is_avalanching => only use the hash itself.
+                return m_hash(key);
+            }
+        } else {
+            // not is_avalanching => apply wyhash
+            return wyhash::hash(m_hash(key));
+        }
+    }
+
+    [[nodiscard]] constexpr auto dist_and_fingerprint_from_hash(uint64_t hash) const -> dist_and_fingerprint_type {
+        return Bucket::dist_inc | (static_cast<dist_and_fingerprint_type>(hash) & Bucket::fingerprint_mask);
+    }
+
+    [[nodiscard]] constexpr auto bucket_idx_from_hash(uint64_t hash) const -> value_idx_type {
+        return static_cast<value_idx_type>(hash >> m_shifts);
+    }
+
+    [[nodiscard]] static constexpr auto get_key(value_type const& vt) -> key_type const& {
+        if constexpr (is_map_v<T>) {
+            return vt.first;
+        } else {
+            return vt;
+        }
+    }
+
+    template <typename K>
+    [[nodiscard]] auto next_while_less(K const& key) const -> Bucket {
+        auto hash = mixed_hash(key);
+        auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
+        auto bucket_idx = bucket_idx_from_hash(hash);
+
+        while (dist_and_fingerprint < at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+        }
+        return {dist_and_fingerprint, bucket_idx};
+    }
+
+    void place_and_shift_up(Bucket bucket, value_idx_type place) {
+        while (0 != at(m_buckets, place).m_dist_and_fingerprint) {
+            bucket = std::exchange(at(m_buckets, place), bucket);
+            bucket.m_dist_and_fingerprint = dist_inc(bucket.m_dist_and_fingerprint);
+            place = next(place);
+        }
+        at(m_buckets, place) = bucket;
+    }
+
+    [[nodiscard]] static constexpr auto calc_num_buckets(uint8_t shifts) -> size_t {
+        return std::min(max_bucket_count(), size_t{1} << (64U - shifts));
+    }
+
+    [[nodiscard]] constexpr auto calc_shifts_for_size(size_t s) const -> uint8_t {
+        auto shifts = initial_shifts;
+        while (shifts > 0 && static_cast<size_t>(static_cast<float>(calc_num_buckets(shifts)) * max_load_factor()) < s) {
+            --shifts;
+        }
+        return shifts;
+    }
+
+    // assumes m_values has data, m_buckets=m_buckets_end=nullptr, m_shifts is INITIAL_SHIFTS
+    void copy_buckets(table const& other) {
+        if (!empty()) {
+            m_shifts = other.m_shifts;
+            allocate_buckets_from_shift();
+            std::memcpy(m_buckets, other.m_buckets, sizeof(Bucket) * bucket_count());
+        }
+    }
+
+    /**
+     * True when no element can be added any more without increasing the size
+     */
+    [[nodiscard]] auto is_full() const -> bool {
+        return size() >= m_max_bucket_capacity;
+    }
+
+    void deallocate_buckets() {
+        auto ba = bucket_alloc(m_values.get_allocator());
+        if (nullptr != m_buckets) {
+            bucket_alloc_traits::deallocate(ba, m_buckets, bucket_count());
+        }
+        m_buckets = nullptr;
+        m_num_buckets = 0;
+        m_max_bucket_capacity = 0;
+    }
+
+    void allocate_buckets_from_shift() {
+        auto ba = bucket_alloc(m_values.get_allocator());
+        m_num_buckets = calc_num_buckets(m_shifts);
+        m_buckets = bucket_alloc_traits::allocate(ba, m_num_buckets);
+        if (m_num_buckets == max_bucket_count()) {
+            // reached the maximum, make sure we can use each bucket
+            m_max_bucket_capacity = max_bucket_count();
+        } else {
+            m_max_bucket_capacity = static_cast<value_idx_type>(static_cast<float>(m_num_buckets) * max_load_factor());
+        }
+    }
+
+    void clear_buckets() {
+        if (m_buckets != nullptr) {
+            std::memset(&*m_buckets, 0, sizeof(Bucket) * bucket_count());
+        }
+    }
+
+    void clear_and_fill_buckets_from_values() {
+        clear_buckets();
+        for (value_idx_type value_idx = 0, end_idx = static_cast<value_idx_type>(m_values.size()); value_idx < end_idx;
+             ++value_idx) {
+            auto const& key = get_key(m_values[value_idx]);
+            auto [dist_and_fingerprint, bucket] = next_while_less(key);
+
+            // we know for certain that key has not yet been inserted, so no need to check it.
+            place_and_shift_up({dist_and_fingerprint, value_idx}, bucket);
+        }
+    }
+
+    void increase_size() {
+        if (ANKERL_UNORDERED_DENSE_UNLIKELY(m_max_bucket_capacity == max_bucket_count())) {
+            on_error_bucket_overflow();
+        }
+        --m_shifts;
+        deallocate_buckets();
+        allocate_buckets_from_shift();
+        clear_and_fill_buckets_from_values();
+    }
+
+    void do_erase(value_idx_type bucket_idx) {
+        auto const value_idx_to_remove = at(m_buckets, bucket_idx).m_value_idx;
+
+        // shift down until either empty or an element with correct spot is found
+        auto next_bucket_idx = next(bucket_idx);
+        while (at(m_buckets, next_bucket_idx).m_dist_and_fingerprint >= Bucket::dist_inc * 2) {
+            at(m_buckets, bucket_idx) = {dist_dec(at(m_buckets, next_bucket_idx).m_dist_and_fingerprint),
+                                         at(m_buckets, next_bucket_idx).m_value_idx};
+            bucket_idx = std::exchange(next_bucket_idx, next(next_bucket_idx));
+        }
+        at(m_buckets, bucket_idx) = {};
+
+        // update m_values
+        if (value_idx_to_remove != m_values.size() - 1) {
+            // no luck, we'll have to replace the value with the last one and update the index accordingly
+            auto& val = m_values[value_idx_to_remove];
+            val = std::move(m_values.back());
+
+            // update the values_idx of the moved entry. No need to play the info game, just look until we find the values_idx
+            auto mh = mixed_hash(get_key(val));
+            bucket_idx = bucket_idx_from_hash(mh);
+
+            auto const values_idx_back = static_cast<value_idx_type>(m_values.size() - 1);
+            while (values_idx_back != at(m_buckets, bucket_idx).m_value_idx) {
+                bucket_idx = next(bucket_idx);
+            }
+            at(m_buckets, bucket_idx).m_value_idx = value_idx_to_remove;
+        }
+        m_values.pop_back();
+    }
+
+    template <typename K>
+    auto do_erase_key(K&& key) -> size_t {
+        if (empty()) {
+            return 0;
+        }
+
+        auto [dist_and_fingerprint, bucket_idx] = next_while_less(key);
+
+        while (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
+               !m_equal(key, get_key(m_values[at(m_buckets, bucket_idx).m_value_idx]))) {
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+        }
+
+        if (dist_and_fingerprint != at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
+            return 0;
+        }
+        do_erase(bucket_idx);
+        return 1;
+    }
+
+    template <class K, class M>
+    auto do_insert_or_assign(K&& key, M&& mapped) -> std::pair<iterator, bool> {
+        auto it_isinserted = try_emplace(std::forward<K>(key), std::forward<M>(mapped));
+        if (!it_isinserted.second) {
+            it_isinserted.first->second = std::forward<M>(mapped);
+        }
+        return it_isinserted;
+    }
+
+    template <typename K, typename... Args>
+    auto do_place_element(dist_and_fingerprint_type dist_and_fingerprint, value_idx_type bucket_idx, K&& key, Args&&... args)
+        -> std::pair<iterator, bool> {
+
+        // emplace the new value. If that throws an exception, no harm done; index is still in a valid state
+        m_values.emplace_back(std::piecewise_construct,
+                              std::forward_as_tuple(std::forward<K>(key)),
+                              std::forward_as_tuple(std::forward<Args>(args)...));
+
+        // place element and shift up until we find an empty spot
+        auto value_idx = static_cast<value_idx_type>(m_values.size() - 1);
+        place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
+        return {begin() + static_cast<difference_type>(value_idx), true};
+    }
+
+    template <typename K, typename... Args>
+    auto do_try_emplace(K&& key, Args&&... args) -> std::pair<iterator, bool> {
+        if (ANKERL_UNORDERED_DENSE_UNLIKELY(is_full())) {
+            increase_size();
+        }
+
+        auto hash = mixed_hash(key);
+        auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
+        auto bucket_idx = bucket_idx_from_hash(hash);
+
+        while (true) {
+            auto* bucket = &at(m_buckets, bucket_idx);
+            if (dist_and_fingerprint == bucket->m_dist_and_fingerprint) {
+                if (m_equal(key, m_values[bucket->m_value_idx].first)) {
+                    return {begin() + static_cast<difference_type>(bucket->m_value_idx), false};
+                }
+            } else if (dist_and_fingerprint > bucket->m_dist_and_fingerprint) {
+                return do_place_element(dist_and_fingerprint, bucket_idx, std::forward<K>(key), std::forward<Args>(args)...);
+            }
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+        }
+    }
+
+    template <typename K>
+    auto do_find(K const& key) -> iterator {
+        if (ANKERL_UNORDERED_DENSE_UNLIKELY(empty())) {
+            return end();
+        }
+
+        auto mh = mixed_hash(key);
+        auto dist_and_fingerprint = dist_and_fingerprint_from_hash(mh);
+        auto bucket_idx = bucket_idx_from_hash(mh);
+        auto* bucket = &at(m_buckets, bucket_idx);
+
+        // unrolled loop. *Always* check a few directly, then enter the loop. This is faster.
+        if (dist_and_fingerprint == bucket->m_dist_and_fingerprint && m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
+            return begin() + static_cast<difference_type>(bucket->m_value_idx);
+        }
+        dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+        bucket_idx = next(bucket_idx);
+        bucket = &at(m_buckets, bucket_idx);
+
+        if (dist_and_fingerprint == bucket->m_dist_and_fingerprint && m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
+            return begin() + static_cast<difference_type>(bucket->m_value_idx);
+        }
+        dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+        bucket_idx = next(bucket_idx);
+        bucket = &at(m_buckets, bucket_idx);
+
+        while (true) {
+            if (dist_and_fingerprint == bucket->m_dist_and_fingerprint) {
+                if (m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
+                    return begin() + static_cast<difference_type>(bucket->m_value_idx);
+                }
+            } else if (dist_and_fingerprint > bucket->m_dist_and_fingerprint) {
+                return end();
+            }
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+            bucket = &at(m_buckets, bucket_idx);
+        }
+    }
+
+    template <typename K>
+    auto do_find(K const& key) const -> const_iterator {
+        return const_cast<table*>(this)->do_find(key); // NOLINT(cppcoreguidelines-pro-type-const-cast)
+    }
+
+    template <typename K, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto do_at(K const& key) -> Q& {
+        if (auto it = find(key); ANKERL_UNORDERED_DENSE_LIKELY(end() != it)) {
+            return it->second;
+        }
+        on_error_key_not_found();
+    }
+
+    template <typename K, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto do_at(K const& key) const -> Q const& {
+        return const_cast<table*>(this)->at(key); // NOLINT(cppcoreguidelines-pro-type-const-cast)
+    }
+
+public:
+    table()
+        : table(0) {}
+
+    explicit table(size_t bucket_count,
+                   Hash const& hash = Hash(),
+                   KeyEqual const& equal = KeyEqual(),
+                   allocator_type const& alloc_or_container = allocator_type())
+        : m_values(alloc_or_container)
+        , m_hash(hash)
+        , m_equal(equal) {
+        if (0 != bucket_count) {
+            reserve(bucket_count);
+        }
+    }
+
+    table(size_t bucket_count, allocator_type const& alloc)
+        : table(bucket_count, Hash(), KeyEqual(), alloc) {}
+
+    table(size_t bucket_count, Hash const& hash, allocator_type const& alloc)
+        : table(bucket_count, hash, KeyEqual(), alloc) {}
+
+    explicit table(allocator_type const& alloc)
+        : table(0, Hash(), KeyEqual(), alloc) {}
+
+    template <class InputIt>
+    table(InputIt first,
+          InputIt last,
+          size_type bucket_count = 0,
+          Hash const& hash = Hash(),
+          KeyEqual const& equal = KeyEqual(),
+          allocator_type const& alloc = allocator_type())
+        : table(bucket_count, hash, equal, alloc) {
+        insert(first, last);
+    }
+
+    template <class InputIt>
+    table(InputIt first, InputIt last, size_type bucket_count, allocator_type const& alloc)
+        : table(first, last, bucket_count, Hash(), KeyEqual(), alloc) {}
+
+    template <class InputIt>
+    table(InputIt first, InputIt last, size_type bucket_count, Hash const& hash, allocator_type const& alloc)
+        : table(first, last, bucket_count, hash, KeyEqual(), alloc) {}
+
+    table(table const& other)
+        : table(other, other.m_values.get_allocator()) {}
+
+    table(table const& other, allocator_type const& alloc)
+        : m_values(other.m_values, alloc)
+        , m_max_load_factor(other.m_max_load_factor)
+        , m_hash(other.m_hash)
+        , m_equal(other.m_equal) {
+        copy_buckets(other);
+    }
+
+    table(table&& other) noexcept
+        : table(std::move(other), other.m_values.get_allocator()) {}
+
+    table(table&& other, allocator_type const& alloc) noexcept
+        : m_values(std::move(other.m_values), alloc)
+        , m_buckets(std::exchange(other.m_buckets, nullptr))
+        , m_num_buckets(std::exchange(other.m_num_buckets, 0))
+        , m_max_bucket_capacity(std::exchange(other.m_max_bucket_capacity, 0))
+        , m_max_load_factor(std::exchange(other.m_max_load_factor, default_max_load_factor))
+        , m_hash(std::exchange(other.m_hash, {}))
+        , m_equal(std::exchange(other.m_equal, {}))
+        , m_shifts(std::exchange(other.m_shifts, initial_shifts)) {
+        other.m_values.clear();
+    }
+
+    table(std::initializer_list<value_type> ilist,
+          size_t bucket_count = 0,
+          Hash const& hash = Hash(),
+          KeyEqual const& equal = KeyEqual(),
+          allocator_type const& alloc = allocator_type())
+        : table(bucket_count, hash, equal, alloc) {
+        insert(ilist);
+    }
+
+    table(std::initializer_list<value_type> ilist, size_type bucket_count, allocator_type const& alloc)
+        : table(ilist, bucket_count, Hash(), KeyEqual(), alloc) {}
+
+    table(std::initializer_list<value_type> init, size_type bucket_count, Hash const& hash, allocator_type const& alloc)
+        : table(init, bucket_count, hash, KeyEqual(), alloc) {}
+
+    ~table() {
+        if (nullptr != m_buckets) {
+            auto ba = bucket_alloc(m_values.get_allocator());
+            bucket_alloc_traits::deallocate(ba, m_buckets, bucket_count());
+        }
+    }
+
+    auto operator=(table const& other) -> table& {
+        if (&other != this) {
+            deallocate_buckets(); // deallocate before m_values is set (might have another allocator)
+            m_values = other.m_values;
+            m_max_load_factor = other.m_max_load_factor;
+            m_hash = other.m_hash;
+            m_equal = other.m_equal;
+            m_shifts = initial_shifts;
+            copy_buckets(other);
+        }
+        return *this;
+    }
+
+    auto operator=(table&& other) noexcept(
+        noexcept(std::is_nothrow_move_assignable_v<value_container_type>&& std::is_nothrow_move_assignable_v<Hash>&&
+                     std::is_nothrow_move_assignable_v<KeyEqual>)) -> table& {
+        if (&other != this) {
+            deallocate_buckets(); // deallocate before m_values is set (might have another allocator)
+            m_values = std::move(other.m_values);
+            m_buckets = std::exchange(other.m_buckets, nullptr);
+            m_num_buckets = std::exchange(other.m_num_buckets, 0);
+            m_max_bucket_capacity = std::exchange(other.m_max_bucket_capacity, 0);
+            m_max_load_factor = std::exchange(other.m_max_load_factor, default_max_load_factor);
+            m_hash = std::exchange(other.m_hash, {});
+            m_equal = std::exchange(other.m_equal, {});
+            m_shifts = std::exchange(other.m_shifts, initial_shifts);
+            other.m_values.clear();
+        }
+        return *this;
+    }
+
+    auto operator=(std::initializer_list<value_type> ilist) -> table& {
+        clear();
+        insert(ilist);
+        return *this;
+    }
+
+    auto get_allocator() const noexcept -> allocator_type {
+        return m_values.get_allocator();
+    }
+
+    // iterators //////////////////////////////////////////////////////////////
+
+    auto begin() noexcept -> iterator {
+        return m_values.begin();
+    }
+
+    auto begin() const noexcept -> const_iterator {
+        return m_values.begin();
+    }
+
+    auto cbegin() const noexcept -> const_iterator {
+        return m_values.cbegin();
+    }
+
+    auto end() noexcept -> iterator {
+        return m_values.end();
+    }
+
+    auto cend() const noexcept -> const_iterator {
+        return m_values.cend();
+    }
+
+    auto end() const noexcept -> const_iterator {
+        return m_values.end();
+    }
+
+    // capacity ///////////////////////////////////////////////////////////////
+
+    [[nodiscard]] auto empty() const noexcept -> bool {
+        return m_values.empty();
+    }
+
+    [[nodiscard]] auto size() const noexcept -> size_t {
+        return m_values.size();
+    }
+
+    [[nodiscard]] static constexpr auto max_size() noexcept -> size_t {
+        if constexpr (std::numeric_limits<value_idx_type>::max() == std::numeric_limits<size_t>::max()) {
+            return size_t{1} << (sizeof(value_idx_type) * 8 - 1);
+        } else {
+            return size_t{1} << (sizeof(value_idx_type) * 8);
+        }
+    }
+
+    // modifiers //////////////////////////////////////////////////////////////
+
+    void clear() {
+        m_values.clear();
+        clear_buckets();
+    }
+
+    auto insert(value_type const& value) -> std::pair<iterator, bool> {
+        return emplace(value);
+    }
+
+    auto insert(value_type&& value) -> std::pair<iterator, bool> {
+        return emplace(std::move(value));
+    }
+
+    template <class P, std::enable_if_t<std::is_constructible_v<value_type, P&&>, bool> = true>
+    auto insert(P&& value) -> std::pair<iterator, bool> {
+        return emplace(std::forward<P>(value));
+    }
+
+    auto insert(const_iterator /*hint*/, value_type const& value) -> iterator {
+        return insert(value).first;
+    }
+
+    auto insert(const_iterator /*hint*/, value_type&& value) -> iterator {
+        return insert(std::move(value)).first;
+    }
+
+    template <class P, std::enable_if_t<std::is_constructible_v<value_type, P&&>, bool> = true>
+    auto insert(const_iterator /*hint*/, P&& value) -> iterator {
+        return insert(std::forward<P>(value)).first;
+    }
+
+    template <class InputIt>
+    void insert(InputIt first, InputIt last) {
+        while (first != last) {
+            insert(*first);
+            ++first;
+        }
+    }
+
+    void insert(std::initializer_list<value_type> ilist) {
+        insert(ilist.begin(), ilist.end());
+    }
+
+    // nonstandard API: *this is emptied.
+    // Also see "A Standard flat_map" https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0429r9.pdf
+    auto extract() && -> value_container_type {
+        return std::move(m_values);
+    }
+
+    // nonstandard API:
+    // Discards the internally held container and replaces it with the one passed. Erases non-unique elements.
+    auto replace(value_container_type&& container) {
+        if (ANKERL_UNORDERED_DENSE_UNLIKELY(container.size() > max_size())) {
+            on_error_too_many_elements();
+        }
+        auto shifts = calc_shifts_for_size(container.size());
+        if (0 == m_num_buckets || shifts < m_shifts || container.get_allocator() != m_values.get_allocator()) {
+            m_shifts = shifts;
+            deallocate_buckets();
+            allocate_buckets_from_shift();
+        }
+        clear_buckets();
+
+        m_values = std::move(container);
+
+        // can't use clear_and_fill_buckets_from_values() because container elements might not be unique
+        auto value_idx = value_idx_type{};
+
+        // loop until we reach the end of the container. duplicated entries will be replaced with back().
+        while (value_idx != static_cast<value_idx_type>(m_values.size())) {
+            auto const& key = get_key(m_values[value_idx]);
+
+            auto hash = mixed_hash(key);
+            auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
+            auto bucket_idx = bucket_idx_from_hash(hash);
+
+            bool key_found = false;
+            while (true) {
+                auto const& bucket = at(m_buckets, bucket_idx);
+                if (dist_and_fingerprint > bucket.m_dist_and_fingerprint) {
+                    break;
+                }
+                if (dist_and_fingerprint == bucket.m_dist_and_fingerprint &&
+                    m_equal(key, m_values[bucket.m_value_idx].first)) {
+                    key_found = true;
+                    break;
+                }
+                dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+                bucket_idx = next(bucket_idx);
+            }
+
+            if (key_found) {
+                if (value_idx != static_cast<value_idx_type>(m_values.size() - 1)) {
+                    m_values[value_idx] = std::move(m_values.back());
+                }
+                m_values.pop_back();
+            } else {
+                place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
+                ++value_idx;
+            }
+        }
+    }
+
+    template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto insert_or_assign(Key const& key, M&& mapped) -> std::pair<iterator, bool> {
+        return do_insert_or_assign(key, std::forward<M>(mapped));
+    }
+
+    template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto insert_or_assign(Key&& key, M&& mapped) -> std::pair<iterator, bool> {
+        return do_insert_or_assign(std::move(key), std::forward<M>(mapped));
+    }
+
+    template <typename K,
+              typename M,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto insert_or_assign(K&& key, M&& mapped) -> std::pair<iterator, bool> {
+        return do_insert_or_assign(std::forward<K>(key), std::forward<M>(mapped));
+    }
+
+    template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto insert_or_assign(const_iterator /*hint*/, Key const& key, M&& mapped) -> iterator {
+        return do_insert_or_assign(key, std::forward<M>(mapped)).first;
+    }
+
+    template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto insert_or_assign(const_iterator /*hint*/, Key&& key, M&& mapped) -> iterator {
+        return do_insert_or_assign(std::move(key), std::forward<M>(mapped)).first;
+    }
+
+    template <typename K,
+              typename M,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto insert_or_assign(const_iterator /*hint*/, K&& key, M&& mapped) -> iterator {
+        return do_insert_or_assign(std::forward<K>(key), std::forward<M>(mapped)).first;
+    }
+
+    // Single arguments for unordered_set can be used without having to construct the value_type
+    template <class K,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<!is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto emplace(K&& key) -> std::pair<iterator, bool> {
+        if (is_full()) {
+            increase_size();
+        }
+
+        auto hash = mixed_hash(key);
+        auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
+        auto bucket_idx = bucket_idx_from_hash(hash);
+
+        while (dist_and_fingerprint <= at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
+            if (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
+                m_equal(key, m_values[at(m_buckets, bucket_idx).m_value_idx])) {
+                // found it, return without ever actually creating anything
+                return {begin() + static_cast<difference_type>(at(m_buckets, bucket_idx).m_value_idx), false};
+            }
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+        }
+
+        // value is new, insert element first, so when exception happens we are in a valid state
+        m_values.emplace_back(std::forward<K>(key));
+        // now place the bucket and shift up until we find an empty spot
+        auto value_idx = static_cast<value_idx_type>(m_values.size() - 1);
+        place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
+        return {begin() + static_cast<difference_type>(value_idx), true};
+    }
+
+    template <class... Args>
+    auto emplace(Args&&... args) -> std::pair<iterator, bool> {
+        if (is_full()) {
+            increase_size();
+        }
+
+        // we have to instantiate the value_type to be able to access the key.
+        // 1. emplace_back the object so it is constructed. 2. If the key is already there, pop it later in the loop.
+        auto& key = get_key(m_values.emplace_back(std::forward<Args>(args)...));
+        auto hash = mixed_hash(key);
+        auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
+        auto bucket_idx = bucket_idx_from_hash(hash);
+
+        while (dist_and_fingerprint <= at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
+            if (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
+                m_equal(key, get_key(m_values[at(m_buckets, bucket_idx).m_value_idx]))) {
+                m_values.pop_back(); // value was already there, so get rid of it
+                return {begin() + static_cast<difference_type>(at(m_buckets, bucket_idx).m_value_idx), false};
+            }
+            dist_and_fingerprint = dist_inc(dist_and_fingerprint);
+            bucket_idx = next(bucket_idx);
+        }
+
+        // value is new, place the bucket and shift up until we find an empty spot
+        auto value_idx = static_cast<value_idx_type>(m_values.size() - 1);
+        place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
+
+        return {begin() + static_cast<difference_type>(value_idx), true};
+    }
+
+    template <class... Args>
+    auto emplace_hint(const_iterator /*hint*/, Args&&... args) -> iterator {
+        return emplace(std::forward<Args>(args)...).first;
+    }
+
+    template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto try_emplace(Key const& key, Args&&... args) -> std::pair<iterator, bool> {
+        return do_try_emplace(key, std::forward<Args>(args)...);
+    }
+
+    template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto try_emplace(Key&& key, Args&&... args) -> std::pair<iterator, bool> {
+        return do_try_emplace(std::move(key), std::forward<Args>(args)...);
+    }
+
+    template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto try_emplace(const_iterator /*hint*/, Key const& key, Args&&... args) -> iterator {
+        return do_try_emplace(key, std::forward<Args>(args)...).first;
+    }
+
+    template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto try_emplace(const_iterator /*hint*/, Key&& key, Args&&... args) -> iterator {
+        return do_try_emplace(std::move(key), std::forward<Args>(args)...).first;
+    }
+
+    template <
+        typename K,
+        typename... Args,
+        typename Q = T,
+        typename H = Hash,
+        typename KE = KeyEqual,
+        std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE> && is_neither_convertible_v<K&&, iterator, const_iterator>,
+                         bool> = true>
+    auto try_emplace(K&& key, Args&&... args) -> std::pair<iterator, bool> {
+        return do_try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
+    }
+
+    template <
+        typename K,
+        typename... Args,
+        typename Q = T,
+        typename H = Hash,
+        typename KE = KeyEqual,
+        std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE> && is_neither_convertible_v<K&&, iterator, const_iterator>,
+                         bool> = true>
+    auto try_emplace(const_iterator /*hint*/, K&& key, Args&&... args) -> iterator {
+        return do_try_emplace(std::forward<K>(key), std::forward<Args>(args)...).first;
+    }
+
+    auto erase(iterator it) -> iterator {
+        auto hash = mixed_hash(get_key(*it));
+        auto bucket_idx = bucket_idx_from_hash(hash);
+
+        auto const value_idx_to_remove = static_cast<value_idx_type>(it - cbegin());
+        while (at(m_buckets, bucket_idx).m_value_idx != value_idx_to_remove) {
+            bucket_idx = next(bucket_idx);
+        }
+
+        do_erase(bucket_idx);
+        return begin() + static_cast<difference_type>(value_idx_to_remove);
+    }
+
+    template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto erase(const_iterator it) -> iterator {
+        return erase(begin() + (it - cbegin()));
+    }
+
+    auto erase(const_iterator first, const_iterator last) -> iterator {
+        auto const idx_first = first - cbegin();
+        auto const idx_last = last - cbegin();
+        auto const first_to_last = std::distance(first, last);
+        auto const last_to_end = std::distance(last, cend());
+
+        // remove elements from left to right which moves elements from the end back
+        auto const mid = idx_first + std::min(first_to_last, last_to_end);
+        auto idx = idx_first;
+        while (idx != mid) {
+            erase(begin() + idx);
+            ++idx;
+        }
+
+        // all elements from the right are moved, now remove the last element until all done
+        idx = idx_last;
+        while (idx != mid) {
+            --idx;
+            erase(begin() + idx);
+        }
+
+        return begin() + idx_first;
+    }
+
+    auto erase(Key const& key) -> size_t {
+        return do_erase_key(key);
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto erase(K&& key) -> size_t {
+        return do_erase_key(std::forward<K>(key));
+    }
+
+    void swap(table& other) noexcept(noexcept(std::is_nothrow_swappable_v<value_container_type>&&
+                                                  std::is_nothrow_swappable_v<Hash>&& std::is_nothrow_swappable_v<KeyEqual>)) {
+        using std::swap;
+        swap(other, *this);
+    }
+
+    // lookup /////////////////////////////////////////////////////////////////
+
+    template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto at(key_type const& key) -> Q& {
+        return do_at(key);
+    }
+
+    template <typename K,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto at(K const& key) -> Q& {
+        return do_at(key);
+    }
+
+    template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto at(key_type const& key) const -> Q const& {
+        return do_at(key);
+    }
+
+    template <typename K,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto at(K const& key) const -> Q const& {
+        return do_at(key);
+    }
+
+    template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto operator[](Key const& key) -> Q& {
+        return try_emplace(key).first->second;
+    }
+
+    template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
+    auto operator[](Key&& key) -> Q& {
+        return try_emplace(std::move(key)).first->second;
+    }
+
+    template <typename K,
+              typename Q = T,
+              typename H = Hash,
+              typename KE = KeyEqual,
+              std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
+    auto operator[](K&& key) -> Q& {
+        return try_emplace(std::forward<K>(key)).first->second;
+    }
+
+    auto count(Key const& key) const -> size_t {
+        return find(key) == end() ? 0 : 1;
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto count(K const& key) const -> size_t {
+        return find(key) == end() ? 0 : 1;
+    }
+
+    auto find(Key const& key) -> iterator {
+        return do_find(key);
+    }
+
+    auto find(Key const& key) const -> const_iterator {
+        return do_find(key);
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto find(K const& key) -> iterator {
+        return do_find(key);
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto find(K const& key) const -> const_iterator {
+        return do_find(key);
+    }
+
+    auto contains(Key const& key) const -> bool {
+        return find(key) != end();
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto contains(K const& key) const -> bool {
+        return find(key) != end();
+    }
+
+    auto equal_range(Key const& key) -> std::pair<iterator, iterator> {
+        auto it = do_find(key);
+        return {it, it == end() ? end() : it + 1};
+    }
+
+    auto equal_range(const Key& key) const -> std::pair<const_iterator, const_iterator> {
+        auto it = do_find(key);
+        return {it, it == end() ? end() : it + 1};
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto equal_range(K const& key) -> std::pair<iterator, iterator> {
+        auto it = do_find(key);
+        return {it, it == end() ? end() : it + 1};
+    }
+
+    template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
+    auto equal_range(K const& key) const -> std::pair<const_iterator, const_iterator> {
+        auto it = do_find(key);
+        return {it, it == end() ? end() : it + 1};
+    }
+
+    // bucket interface ///////////////////////////////////////////////////////
+
+    auto bucket_count() const noexcept -> size_t { // NOLINT(modernize-use-nodiscard)
+        return m_num_buckets;
+    }
+
+    static constexpr auto max_bucket_count() noexcept -> size_t { // NOLINT(modernize-use-nodiscard)
+        return max_size();
+    }
+
+    // hash policy ////////////////////////////////////////////////////////////
+
+    [[nodiscard]] auto load_factor() const -> float {
+        return bucket_count() ? static_cast<float>(size()) / static_cast<float>(bucket_count()) : 0.0F;
+    }
+
+    [[nodiscard]] auto max_load_factor() const -> float {
+        return m_max_load_factor;
+    }
+
+    void max_load_factor(float ml) {
+        m_max_load_factor = ml;
+        if (m_num_buckets != max_bucket_count()) {
+            m_max_bucket_capacity = static_cast<value_idx_type>(static_cast<float>(bucket_count()) * max_load_factor());
+        }
+    }
+
+    void rehash(size_t count) {
+        count = std::min(count, max_size());
+        auto shifts = calc_shifts_for_size(std::max(count, size()));
+        if (shifts != m_shifts) {
+            m_shifts = shifts;
+            deallocate_buckets();
+            m_values.shrink_to_fit();
+            allocate_buckets_from_shift();
+            clear_and_fill_buckets_from_values();
+        }
+    }
+
+    void reserve(size_t capa) {
+        capa = std::min(capa, max_size());
+        if constexpr (has_reserve<value_container_type>) {
+            // std::deque doesn't have reserve(). Make sure we only call when available
+            m_values.reserve(capa);
+        }
+        auto shifts = calc_shifts_for_size(std::max(capa, size()));
+        if (0 == m_num_buckets || shifts < m_shifts) {
+            m_shifts = shifts;
+            deallocate_buckets();
+            allocate_buckets_from_shift();
+            clear_and_fill_buckets_from_values();
+        }
+    }
+
+    // observers //////////////////////////////////////////////////////////////
+
+    auto hash_function() const -> hasher {
+        return m_hash;
+    }
+
+    auto key_eq() const -> key_equal {
+        return m_equal;
+    }
+
+    // nonstandard API: expose the underlying values container
+    [[nodiscard]] auto values() const noexcept -> value_container_type const& {
+        return m_values;
+    }
+
+    // non-member functions ///////////////////////////////////////////////////
+
+    friend auto operator==(table const& a, table const& b) -> bool {
+        if (&a == &b) {
+            return true;
+        }
+        if (a.size() != b.size()) {
+            return false;
+        }
+        for (auto const& b_entry : b) {
+            auto it = a.find(get_key(b_entry));
+            if constexpr (is_map_v<T>) {
+                // map: check that key is here, then also check that value is the same
+                if (a.end() == it || !(b_entry.second == it->second)) {
+                    return false;
+                }
+            } else {
+                // set: only check that the key is here
+                if (a.end() == it) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    friend auto operator!=(table const& a, table const& b) -> bool {
+        return !(a == b);
+    }
+};
+
+} // namespace detail
+
+template <class Key,
+          class T,
+          class Hash = hash<Key>,
+          class KeyEqual = std::equal_to<Key>,
+          class AllocatorOrContainer = std::allocator<std::pair<Key, T>>,
+          class Bucket = bucket_type::standard>
+using map = detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket>;
+
+template <class Key,
+          class Hash = hash<Key>,
+          class KeyEqual = std::equal_to<Key>,
+          class AllocatorOrContainer = std::allocator<Key>,
+          class Bucket = bucket_type::standard>
+using set = detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket>;
+
+#    if ANKERL_UNORDERED_DENSE_PMR
+
+namespace pmr {
+
+template <class Key,
+          class T,
+          class Hash = hash<Key>,
+          class KeyEqual = std::equal_to<Key>,
+          class Bucket = bucket_type::standard>
+using map = detail::table<Key, T, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR<std::pair<Key, T>>, Bucket>;
+
+template <class Key, class Hash = hash<Key>, class KeyEqual = std::equal_to<Key>, class Bucket = bucket_type::standard>
+using set = detail::table<Key, void, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR<Key>, Bucket>;
+
+} // namespace pmr
+
+#    endif
+
+// deduction guides ///////////////////////////////////////////////////////////
+
+// deduction guides for alias templates are only possible since C++20
+// see https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
+
+} // namespace ANKERL_UNORDERED_DENSE_NAMESPACE
+} // namespace ankerl::unordered_dense
+
+// std extensions /////////////////////////////////////////////////////////////
+
+namespace std { // NOLINT(cert-dcl58-cpp)
+
+template <class Key, class T, class Hash, class KeyEqual, class AllocatorOrContainer, class Bucket, class Pred>
+// NOLINTNEXTLINE(cert-dcl58-cpp)
+auto erase_if(ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket>& map, Pred pred)
+    -> size_t {
+    using map_t = ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket>;
+
+    // going back to front because erase() invalidates the end iterator
+    auto const old_size = map.size();
+    auto idx = old_size;
+    while (idx) {
+        --idx;
+        auto it = map.begin() + static_cast<typename map_t::difference_type>(idx);
+        if (pred(*it)) {
+            map.erase(it);
+        }
+    }
+
+    return map.size() - old_size;
+}
+
+} // namespace std
+
+#endif
+#endif
diff --git a/src/rgw/driver/posix/zpp_bits.h b/src/rgw/driver/posix/zpp_bits.h
new file mode 100644 (file)
index 0000000..90e8916
--- /dev/null
@@ -0,0 +1,5678 @@
+/*
+MIT License
+
+Copyright (c) 2021 Eyal Z
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+*/
+#ifndef ZPP_BITS_H
+#define ZPP_BITS_H
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <climits>
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <numeric>
+#include <optional>
+#include <span>
+#include <system_error>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+#if __has_include("zpp_throwing.h")
+#include "zpp_throwing.h"
+#endif
+
+#ifdef __cpp_exceptions
+#include <stdexcept>
+#endif
+
+#ifndef ZPP_BITS_AUTODETECT_MEMBERS_MODE
+#define ZPP_BITS_AUTODETECT_MEMBERS_MODE (0)
+#endif
+
+#ifndef ZPP_BITS_INLINE
+#if defined __clang__ || defined __GNUC__
+#define ZPP_BITS_INLINE __attribute__((always_inline))
+#if defined __clang__
+#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA __attribute__((always_inline)) constexpr
+#else
+#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr __attribute__((always_inline))
+#endif
+#elif defined _MSC_VER
+#define ZPP_BITS_INLINE [[msvc::forceinline]]
+#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA /*constexpr*/ [[msvc::forceinline]]
+#endif
+#else // ZPP_BITS_INLINE
+#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr
+#endif // ZPP_BITS_INLINE
+
+#if defined ZPP_BITS_INLINE_MODE && !ZPP_BITS_INLINE_MODE
+#undef ZPP_BITS_INLINE
+#define ZPP_BITS_INLINE
+#undef ZPP_BITS_CONSTEXPR_INLINE_LAMBDA
+#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr
+#endif
+
+#ifndef ZPP_BITS_INLINE_DECODE_VARINT
+#define ZPP_BITS_INLINE_DECODE_VARINT (0)
+#endif
+
+namespace zpp::bits
+{
+using default_size_type = std::uint32_t;
+
+#ifndef __cpp_lib_bit_cast
+namespace std
+{
+using namespace ::std;
+template <class ToType,
+          class FromType,
+          class = enable_if_t<sizeof(ToType) == sizeof(FromType) &&
+                              is_trivially_copyable_v<ToType> &&
+                              is_trivially_copyable_v<FromType>>>
+constexpr ToType bit_cast(FromType const & from) noexcept
+{
+    return __builtin_bit_cast(ToType, from);
+}
+} // namespace std
+#endif
+
+enum class kind
+{
+    in,
+    out
+};
+
+template <std::size_t Count = std::numeric_limits<std::size_t>::max()>
+struct members
+{
+    constexpr static std::size_t value = Count;
+};
+
+template <auto Protocol, std::size_t Members = std::numeric_limits<std::size_t>::max()>
+struct protocol
+{
+    constexpr static auto value = Protocol;
+    constexpr static auto members = Members;
+};
+
+template <auto Id>
+struct serialization_id
+{
+    constexpr static auto value = Id;
+};
+
+constexpr auto success(std::errc code)
+{
+    return std::errc{} == code;
+}
+
+constexpr auto failure(std::errc code)
+{
+    return std::errc{} != code;
+}
+
+struct [[nodiscard]] errc
+{
+    constexpr errc(std::errc code = {}) : code(code)
+    {
+    }
+
+#if __has_include("zpp_throwing.h")
+    constexpr zpp::throwing<void> operator co_await() const
+    {
+        if (failure(code)) [[unlikely]] {
+            return code;
+        }
+        return zpp::void_v;
+    }
+#endif
+
+    constexpr operator std::errc() const
+    {
+        return code;
+    }
+
+    constexpr void or_throw() const
+    {
+        if (failure(code)) [[unlikely]] {
+#ifdef __cpp_exceptions
+            throw std::system_error(std::make_error_code(code));
+#else
+            std::abort();
+#endif
+        }
+    }
+
+    std::errc code;
+};
+
+constexpr auto success(errc code)
+{
+    return std::errc{} == code;
+}
+
+constexpr auto failure(errc code)
+{
+    return std::errc{} != code;
+}
+
+struct access
+{
+    struct any
+    {
+        template <typename Type>
+        operator Type();
+    };
+
+    template <typename Item>
+    constexpr static auto make(auto &&... arguments)
+    {
+        return Item{std::forward<decltype(arguments)>(arguments)...};
+    }
+
+    template <typename Item>
+    constexpr static auto placement_new(void * address,
+                                        auto &&... arguments)
+    {
+        return ::new (address)
+            Item(std::forward<decltype(arguments)>(arguments)...);
+    }
+
+    template <typename Item>
+    constexpr static auto make_unique(auto &&... arguments)
+    {
+        return std::unique_ptr<Item>(
+            new Item(std::forward<decltype(arguments)>(arguments)...));
+    }
+
+    template <typename Item>
+    constexpr static void destruct(Item & item)
+    {
+        item.~Item();
+    }
+
+    template <typename Type>
+    constexpr static auto number_of_members();
+
+    constexpr static auto max_visit_members = 50;
+
+    ZPP_BITS_INLINE constexpr static decltype(auto) visit_members(
+        auto && object,
+        auto && visitor) requires(0 <=
+                                  number_of_members<decltype(object)>()) &&
+        (number_of_members<decltype(object)>() <= max_visit_members)
+    {
+        constexpr auto count = number_of_members<decltype(object)>();
+
+        // clang-format off
+        if constexpr (count == 0) { return visitor(); } else if constexpr (count == 1) { auto && [a1] = object; return visitor(a1); } else if constexpr (count == 2) { auto && [a1, a2] = object; return visitor(a1, a2); /*......................................................................................................................................................................................................................................................................*/ } else if constexpr (count == 3) { auto && [a1, a2, a3] = object; return visitor(a1, a2, a3); } else if constexpr (count == 4) { auto && [a1, a2, a3, a4] = object; return visitor(a1, a2, a3, a4); } else if constexpr (count == 5) { auto && [a1, a2, a3, a4, a5] = object; return visitor(a1, a2, a3, a4, a5); } else if constexpr (count == 6) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor(a1, a2, a3, a4, a5, a6); } else if constexpr (count == 7) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor(a1, a2, a3, a4, a5, a6, a7); } else if constexpr (count == 8) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8); } else if constexpr (count == 9) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9); } else if constexpr (count == 10) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); } else if constexpr (count == 11) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); } else if constexpr (count == 12) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); } else if constexpr (count == 13) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); } else if constexpr (count == 14) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); } else if constexpr (count == 15) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); } else if constexpr (count == 16) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); } else if constexpr (count == 17) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17); } else if constexpr (count == 18) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18); } else if constexpr (count == 19) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19); } else if constexpr (count == 20) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); } else if constexpr (count == 21) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21); } else if constexpr (count == 22) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22); } else if constexpr (count == 23) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23); } else if constexpr (count == 24) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24); } else if constexpr (count == 25) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25); } else if constexpr (count == 26) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26); } else if constexpr (count == 27) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27); } else if constexpr (count == 28) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28); } else if constexpr (count == 29) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29); } else if constexpr (count == 30) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30); } else if constexpr (count == 31) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31); } else if constexpr (count == 32) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32); } else if constexpr (count == 33) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33); } else if constexpr (count == 34) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34); } else if constexpr (count == 35) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35); } else if constexpr (count == 36) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36); } else if constexpr (count == 37) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37); } else if constexpr (count == 38) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38); } else if constexpr (count == 39) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39); } else if constexpr (count == 40) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40); } else if constexpr (count == 41) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41); } else if constexpr (count == 42) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42); } else if constexpr (count == 43) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43); } else if constexpr (count == 44) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44); } else if constexpr (count == 45) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45); } else if constexpr (count == 46) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46); } else if constexpr (count == 47) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47); } else if constexpr (count == 48) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48); } else if constexpr (count == 49) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49); } else if constexpr (count == 50) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50);
+            // Calls the visitor above with all data members of object.
+        }
+        // clang-format on
+    }
+
+    template <typename Type>
+        constexpr static decltype(auto)
+        visit_members_types(auto && visitor) requires(0 <= number_of_members<Type>()) &&
+        (number_of_members<Type>() <= max_visit_members)
+
+    {
+        using type = std::remove_cvref_t<Type>;
+        constexpr auto count = number_of_members<Type>();
+
+        // clang-format off
+        if constexpr (count == 0) { return visitor.template operator()<>(); } else if constexpr (count == 1) { auto f = [&](auto && object) { auto && [a1] = object; return visitor.template operator()<decltype(a1)>(); }; /*......................................................................................................................................................................................................................................................................*/ return decltype(f(std::declval<type>()))(); } else if constexpr (count == 2) { auto f = [&](auto && object) { auto && [a1, a2] = object; return visitor.template operator()<decltype(a1), decltype(a2)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 3) { auto f = [&](auto && object) { auto && [a1, a2, a3] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 4) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 5) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 6) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 7) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 8) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 9) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 10) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 11) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 12) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 13) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 14) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 15) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 16) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 17) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 18) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 19) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 20) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 21) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 22) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 23) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 24) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 25) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 26) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 27) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 28) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 29) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 30) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 31) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 32) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 33) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 34) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 35) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 36) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 37) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 38) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 39) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 40) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 41) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 42) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 43) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 44) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 45) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 46) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45), decltype(a46)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 47) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45), decltype(a46), decltype(a47)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 48) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45), decltype(a46), decltype(a47), decltype(a48)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 49) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45), decltype(a46), decltype(a47), decltype(a48), decltype(a49)>(); }; return decltype(f(std::declval<type>()))(); } else if constexpr (count == 50) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor.template operator()<decltype(a1), decltype(a2), decltype(a3), decltype(a4), decltype(a5), decltype(a6), decltype(a7), decltype(a8), decltype(a9), decltype(a10), decltype(a11), decltype(a12), decltype(a13), decltype(a14), decltype(a15), decltype(a16), decltype(a17), decltype(a18), decltype(a19), decltype(a20), decltype(a21), decltype(a22), decltype(a23), decltype(a24), decltype(a25), decltype(a26), decltype(a27), decltype(a28), decltype(a29), decltype(a30), decltype(a31), decltype(a32), decltype(a33), decltype(a34), decltype(a35), decltype(a36), decltype(a37), decltype(a38), decltype(a39), decltype(a40), decltype(a41), decltype(a42), decltype(a43), decltype(a44), decltype(a45), decltype(a46), decltype(a47), decltype(a48), decltype(a49), decltype(a50)>(); }; return decltype(f(std::declval<type>()))();
+            // Returns visitor.template operator()<member-types...>();
+        }
+        // clang-format on
+    }
+
+    constexpr static auto try_serialize(auto && item)
+    {
+        if constexpr (requires { serialize(item); }) {
+            return serialize(item);
+        }
+    }
+
+    template <typename Type, typename Archive>
+    constexpr static auto has_serialize()
+    {
+        return requires {
+                   requires std::same_as<
+                       typename std::remove_cvref_t<Type>::serialize,
+                       members<
+                           std::remove_cvref_t<Type>::serialize::value>>;
+               } ||
+               requires(Type && item) {
+                   requires std::same_as<
+                       std::remove_cvref_t<decltype(try_serialize(
+                           item))>,
+                       members<std::remove_cvref_t<
+                           decltype(try_serialize(item))>::value>>;
+               } ||
+               requires {
+                   requires std::same_as<
+                       typename std::remove_cvref_t<Type>::serialize,
+                       protocol<
+                           std::remove_cvref_t<Type>::serialize::value,
+                           std::remove_cvref_t<Type>::serialize::members>>;
+               } ||
+               requires(Type && item) {
+                   requires std::same_as<
+                       std::remove_cvref_t<decltype(try_serialize(
+                           item))>,
+                       protocol<
+                           std::remove_cvref_t<decltype(try_serialize(
+                               item))>::value,
+                           std::remove_cvref_t<decltype(try_serialize(
+                               item))>::members>>;
+               } ||
+               requires(Type && item, Archive && archive) {
+                   std::remove_cvref_t<Type>::serialize(archive, item);
+               } || requires(Type && item, Archive && archive) {
+                        serialize(archive, item);
+                    };
+    }
+
+    template <typename Type, typename Archive>
+    constexpr static auto has_explicit_serialize()
+    {
+        return requires(Type && item, Archive && archive)
+        {
+            std::remove_cvref_t<Type>::serialize(archive, item);
+        }
+        || requires(Type && item, Archive && archive)
+        {
+            serialize(archive, item);
+        };
+    }
+
+    template <typename Type>
+    struct byte_serializable_visitor;
+
+    template <typename Type>
+    constexpr static auto byte_serializable();
+
+    template <typename Type>
+    struct endian_independent_byte_serializable_visitor;
+
+    template <typename Type>
+    constexpr static auto endian_independent_byte_serializable();
+
+    template <typename Type, typename Self, typename... Visited>
+    struct self_referencing_visitor;
+
+    template <typename Type, typename Self = Type, typename... Visited>
+    constexpr static auto self_referencing();
+
+    template <typename Type>
+    constexpr static auto has_protocol()
+    {
+        return requires
+        {
+            requires std::same_as<
+                typename std::remove_cvref_t<Type>::serialize,
+                protocol<std::remove_cvref_t<Type>::serialize::value,
+                         std::remove_cvref_t<Type>::serialize::members>>;
+        }
+        || requires(Type && item)
+        {
+            requires std::same_as<
+                std::remove_cvref_t<decltype(try_serialize(item))>,
+                protocol<
+                    std::remove_cvref_t<decltype(try_serialize(item))>::value,
+                    std::remove_cvref_t<decltype(try_serialize(
+                        item))>::members>>;
+        };
+    }
+
+    template <typename Type>
+    constexpr static auto get_protocol()
+    {
+        if constexpr (
+            requires {
+                requires std::same_as<
+                    typename std::remove_cvref_t<Type>::serialize,
+                    protocol<
+                        std::remove_cvref_t<Type>::serialize::value,
+                        std::remove_cvref_t<Type>::serialize::members>>;
+            }) {
+            return std::remove_cvref_t<Type>::serialize::value;
+        } else if constexpr (
+            requires(Type && item) {
+                requires std::same_as<
+                    std::remove_cvref_t<decltype(try_serialize(item))>,
+                    protocol<std::remove_cvref_t<decltype(try_serialize(
+                                 item))>::value,
+                             std::remove_cvref_t<decltype(try_serialize(
+                                 item))>::members>>;
+            }) {
+            return std::remove_cvref_t<decltype(try_serialize(
+                std::declval<Type>()))>::value;
+        } else {
+            static_assert(!sizeof(Type));
+        }
+    }
+};
+
+template <typename Type>
+struct destructor_guard
+{
+    ZPP_BITS_INLINE constexpr ~destructor_guard()
+    {
+        access::destruct(object);
+    }
+
+    Type & object;
+};
+
+template <typename Type>
+destructor_guard(Type) -> destructor_guard<Type>;
+
+namespace traits
+{
+template <typename Type>
+struct is_unique_ptr : std::false_type
+{
+};
+
+template <typename Type>
+struct is_unique_ptr<std::unique_ptr<Type, std::default_delete<Type>>>
+    : std::true_type
+{
+};
+
+template <typename Type>
+struct is_shared_ptr : std::false_type
+{
+};
+
+template <typename Type>
+struct is_shared_ptr<std::shared_ptr<Type>> : std::true_type
+{
+};
+
+template <typename Variant>
+struct variant_impl;
+
+template <typename... Types, template <typename...> typename Variant>
+struct variant_impl<Variant<Types...>>
+{
+    using variant_type = Variant<Types...>;
+
+    template <std::size_t Index,
+              std::size_t CurrentIndex,
+              typename FirstType,
+              typename... OtherTypes>
+    constexpr static auto get_id()
+    {
+        if constexpr (Index == CurrentIndex) {
+            if constexpr (requires {
+                              requires std::same_as<
+                                  serialization_id<
+                                      FirstType::serialize_id::value>,
+                              typename FirstType::serialize_id>;
+                          }) {
+                return FirstType::serialize_id::value;
+            } else if constexpr (
+                requires {
+                    requires std::same_as<
+                        serialization_id<decltype(serialize_id(
+                            std::declval<FirstType>()))::value>,
+                    decltype(serialize_id(std::declval<FirstType>()))>;
+                }) {
+                return decltype(serialize_id(
+                    std::declval<FirstType>()))::value;
+            } else {
+                return std::byte{Index};
+            }
+        } else {
+            return get_id<Index, CurrentIndex + 1, OtherTypes...>();
+        }
+    }
+
+    template <std::size_t Index>
+    constexpr static auto id()
+    {
+        return get_id<Index, 0, Types...>();
+    }
+
+    template <std::size_t CurrentIndex = 0>
+    ZPP_BITS_INLINE constexpr static auto id(auto index)
+    {
+        if constexpr (CurrentIndex == (sizeof...(Types) - 1)) {
+            return id<CurrentIndex>();
+        } else {
+            if (index == CurrentIndex) {
+                return id<CurrentIndex>();
+            } else {
+                return id<CurrentIndex + 1>(index);
+            }
+        }
+    }
+
+    template <auto Id, std::size_t CurrentIndex = 0>
+    constexpr static std::size_t index()
+    {
+        static_assert(CurrentIndex < sizeof...(Types));
+
+        if constexpr (variant_impl::id<CurrentIndex>() == Id) {
+            return CurrentIndex;
+        } else {
+            return index<Id, CurrentIndex + 1>();
+        }
+    }
+
+    template <std::size_t CurrentIndex = 0>
+    ZPP_BITS_INLINE constexpr static std::size_t index(auto && id)
+    {
+        if constexpr (CurrentIndex == sizeof...(Types)) {
+            return std::numeric_limits<std::size_t>::max();
+        } else {
+            if (variant_impl::id<CurrentIndex>() == id) {
+                return CurrentIndex;
+            } else {
+                return index<CurrentIndex + 1>(id);
+            }
+        }
+        return std::numeric_limits<std::size_t>::max();
+    }
+
+    template <std::size_t... LeftIndices, std::size_t... RightIndices>
+    constexpr static auto unique_ids(std::index_sequence<LeftIndices...>,
+                                     std::index_sequence<RightIndices...>)
+    {
+        auto unique_among_rest = []<auto LeftIndex, auto LeftId>()
+        {
+            return (... && ((LeftIndex == RightIndices) ||
+                            (LeftId != id<RightIndices>())));
+        };
+        return (... && unique_among_rest.template
+                       operator()<LeftIndices, id<LeftIndices>()>());
+    }
+
+    template <std::size_t... LeftIndices, std::size_t... RightIndices>
+    constexpr static auto
+    same_id_types(std::index_sequence<LeftIndices...>,
+                  std::index_sequence<RightIndices...>)
+    {
+        auto same_among_rest = []<auto LeftIndex, auto LeftId>()
+        {
+            return (... &&
+                    (std::same_as<
+                        std::remove_cv_t<decltype(LeftId)>,
+                        std::remove_cv_t<decltype(id<RightIndices>())>>));
+        };
+        return (... && same_among_rest.template
+                       operator()<LeftIndices, id<LeftIndices>()>());
+    }
+
+    template <typename Type, std::size_t... Indices>
+    constexpr static std::size_t index_by_type(std::index_sequence<Indices...>)
+    {
+        return ((std::same_as<
+                     Type,
+                     std::variant_alternative_t<Indices, variant_type>> *
+                 Indices) +
+                ...);
+    }
+
+    template <typename Type>
+    constexpr static std::size_t index_by_type()
+    {
+        return index_by_type<Type>(
+            std::make_index_sequence<std::variant_size_v<variant_type>>{});
+    }
+
+    using id_type = decltype(id<0>());
+};
+
+template <typename Variant>
+struct variant_checker;
+
+template <typename... Types, template <typename...> typename Variant>
+struct variant_checker<Variant<Types...>>
+{
+    using type = variant_impl<Variant<Types...>>;
+    static_assert(
+        type::unique_ids(std::make_index_sequence<sizeof...(Types)>(),
+                   std::make_index_sequence<sizeof...(Types)>()));
+    static_assert(
+        type::same_id_types(std::make_index_sequence<sizeof...(Types)>(),
+                            std::make_index_sequence<sizeof...(Types)>()));
+};
+
+template <typename Variant>
+using variant = typename variant_checker<Variant>::type;
+
+template <typename Tuple>
+struct tuple;
+
+template <typename... Types, template <typename...> typename Tuple>
+struct tuple<Tuple<Types...>>
+{
+    template <std::size_t Index = 0>
+    ZPP_BITS_INLINE constexpr static auto visit(auto && tuple, auto && index, auto && visitor)
+    {
+        if constexpr (Index + 1 == sizeof...(Types)) {
+            return visitor(std::get<Index>(tuple));
+        } else {
+            if (Index == index) {
+                return visitor(std::get<Index>(tuple));
+            }
+            return visit<Index + 1>(tuple, index, visitor);
+        }
+    }
+};
+
+template <typename Type, typename Visitor = std::monostate>
+struct visitor
+{
+    using byte_type = std::byte;
+    using view_type = std::span<std::byte>;
+
+    static constexpr bool resizable = false;
+
+    constexpr auto operator()(auto && ... arguments) const
+    {
+        if constexpr (requires {
+                          visitor(std::forward<decltype(arguments)>(
+                              arguments)...);
+                      }) {
+            return visitor(
+                std::forward<decltype(arguments)>(arguments)...);
+        } else {
+            return sizeof...(arguments);
+        }
+    }
+
+    template <typename...>
+    constexpr auto serialize_one(auto && ... arguments) const
+    {
+        return (*this)(std::forward<decltype(arguments)>(arguments)...);
+    }
+
+    template <typename...>
+    constexpr auto serialize_many(auto && ... arguments) const
+    {
+        return (*this)(std::forward<decltype(arguments)>(arguments)...);
+    }
+
+    constexpr static auto kind()
+    {
+        return kind::out;
+    }
+
+    std::span<std::byte> data();
+    std::span<std::byte> remaining_data();
+    std::span<std::byte> processed_data();
+    std::size_t position() const;
+    std::size_t & position();
+    errc enlarge_for(std::size_t);
+    void reset(std::size_t = 0);
+
+    [[no_unique_address]] Visitor visitor;
+};
+
+constexpr auto get_default_size_type()
+{
+    return default_size_type{};
+}
+
+constexpr auto get_default_size_type(auto option, auto... options)
+{
+    if constexpr (requires {
+                      typename decltype(option)::default_size_type;
+                  }) {
+        if constexpr (std::is_void_v<typename decltype(option)::default_size_type>) {
+            return std::monostate{};
+        } else {
+            return typename decltype(option)::default_size_type{};
+        }
+    } else {
+        return get_default_size_type(options...);
+    }
+}
+
+template <typename... Options>
+using default_size_type_t =
+    std::conditional_t<std::same_as<std::monostate,
+                                    decltype(get_default_size_type(
+                                        std::declval<Options>()...))>,
+                       void,
+                       decltype(get_default_size_type(
+                           std::declval<Options>()...))>;
+
+template <typename Option, typename... Options>
+constexpr auto get_alloc_limit()
+{
+    if constexpr (requires {
+                      std::remove_cvref_t<
+                          Option>::alloc_limit_value;
+                  }) {
+        return std::remove_cvref_t<Option>::alloc_limit_value;
+    } else if constexpr (sizeof...(Options) != 0) {
+        return get_alloc_limit<Options...>();
+    } else {
+        return std::numeric_limits<std::size_t>::max();
+    }
+}
+
+template <typename... Options>
+constexpr auto alloc_limit()
+{
+    if constexpr (sizeof...(Options) != 0) {
+        return get_alloc_limit<Options...>();
+    } else {
+        return std::numeric_limits<std::size_t>::max();
+    }
+}
+
+template <typename Option, typename... Options>
+constexpr auto get_enlarger()
+{
+    if constexpr (requires {
+                      std::remove_cvref_t<
+                          Option>::enlarger_value;
+                  }) {
+        return std::remove_cvref_t<Option>::enlarger_value;
+    } else if constexpr (sizeof...(Options) != 0) {
+        return get_enlarger<Options...>();
+    } else {
+        return std::tuple{3, 2};
+    }
+}
+
+template <typename... Options>
+constexpr auto enlarger()
+{
+    if constexpr (sizeof...(Options) != 0) {
+        return get_enlarger<Options...>();
+    } else {
+        return std::tuple{3, 2};
+    }
+}
+
+template <typename Type>
+constexpr auto underlying_type_generic()
+{
+    if constexpr (std::is_enum_v<Type>) {
+        return std::underlying_type_t<Type>{};
+    } else {
+        return Type{};
+    }
+}
+
+template <typename Type>
+using underlying_type_t = decltype(underlying_type_generic<Type>());
+
+template <typename Id>
+struct id_serializable
+{
+    using serialize_id = Id;
+};
+
+constexpr auto unique(auto && ... values)
+{
+    auto unique_among_rest = [](auto && value, auto && ... values)
+    {
+        return (... && ((&value == &values) ||
+                        (value != values)));
+    };
+    return (... && unique_among_rest(values, values...));
+}
+} // namespace traits
+
+namespace concepts
+{
+template <typename Type>
+concept byte_type = std::same_as<std::remove_cv_t<Type>, char> ||
+                    std::same_as<std::remove_cv_t<Type>, unsigned char> ||
+                    std::same_as<std::remove_cv_t<Type>, std::byte>;
+
+template <typename Type>
+concept byte_view = byte_type<typename std::remove_cvref_t<Type>::value_type> &&
+    requires(Type value)
+{
+    value.data();
+    value.size();
+};
+
+template <typename Type>
+concept has_serialize =
+    access::has_serialize<Type,
+                          traits::visitor<std::remove_cvref_t<Type>>>();
+
+template <typename Type>
+concept has_explicit_serialize = access::has_explicit_serialize<
+    Type,
+    traits::visitor<std::remove_cvref_t<Type>>>();
+
+template <typename Type>
+concept variant = !has_serialize<Type> && requires (Type variant) {
+    variant.index();
+    std::get_if<0>(&variant);
+    std::variant_size_v<std::remove_cvref_t<Type>>;
+};
+
+template <typename Type>
+concept optional = !has_serialize<Type> && requires (Type optional) {
+    optional.value();
+    optional.has_value();
+    optional.operator bool();
+    optional.operator*();
+};
+
+template <typename Type>
+concept container =
+    !has_serialize<Type> && !optional<Type> && requires(Type container)
+{
+    typename std::remove_cvref_t<Type>::value_type;
+    container.size();
+    container.begin();
+    container.end();
+};
+
+template <typename Type>
+concept associative_container = container<Type> && requires(Type container)
+{
+    typename std::remove_cvref_t<Type>::key_type;
+};
+
+template <typename Type>
+concept tuple = !has_serialize<Type> && !container<Type> && requires(Type tuple)
+{
+    sizeof(std::tuple_size<std::remove_cvref_t<Type>>);
+}
+&&!requires(Type tuple)
+{
+    tuple.index();
+};
+
+template <typename Type>
+concept owning_pointer = !optional<Type> &&
+    (traits::is_unique_ptr<std::remove_cvref_t<Type>>::value ||
+    traits::is_shared_ptr<std::remove_cvref_t<Type>>::value);
+
+template <typename Type>
+concept bitset =
+    !has_serialize<Type> && requires(std::remove_cvref_t<Type> bitset)
+{
+    bitset.flip();
+    bitset.set();
+    bitset.test(0);
+    bitset.to_ullong();
+};
+
+template <typename Type>
+concept has_protocol = access::has_protocol<Type>();
+
+template <typename Type>
+concept by_protocol = has_protocol<Type> && !has_explicit_serialize<Type>;
+
+template <typename Type>
+concept basic_array = std::is_array_v<std::remove_cvref_t<Type>>;
+
+template <typename Type>
+concept unspecialized =
+    !container<Type> && !owning_pointer<Type> && !tuple<Type> &&
+    !variant<Type> && !optional<Type> && !bitset<Type> &&
+    !std::is_array_v<std::remove_cvref_t<Type>> && !by_protocol<Type>;
+
+template <typename Type>
+concept empty = requires
+{
+    std::integral_constant<std::size_t, sizeof(Type)>::value;
+    requires std::is_empty_v<std::remove_cvref_t<Type>>;
+};
+
+template <typename Type>
+concept byte_serializable = access::byte_serializable<Type>();
+
+template <typename Type>
+concept endian_independent_byte_serializable =
+    access::endian_independent_byte_serializable<Type>();
+
+template <typename Archive>
+concept endian_aware_archive = requires
+{
+    requires std::remove_cvref_t<Archive>::endian_aware;
+};
+
+template <typename Archive, typename Type>
+concept serialize_as_bytes = endian_independent_byte_serializable<Type> ||
+    (!endian_aware_archive<Archive> && byte_serializable<Type>);
+
+template <typename Type, typename Reference>
+concept type_references = requires
+{
+    requires container<Type>;
+    requires std::same_as<typename std::remove_cvref_t<Type>::value_type,
+                          std::remove_cvref_t<Reference>>;
+}
+|| requires
+{
+    requires associative_container<Type>;
+    requires std::same_as<typename std::remove_cvref_t<Type>::key_type,
+                          std::remove_cvref_t<Reference>>;
+}
+|| requires
+{
+    requires associative_container<Type>;
+    requires std::same_as<typename std::remove_cvref_t<Type>::mapped_type,
+                          std::remove_cvref_t<Reference>>;
+}
+|| requires (Type && value)
+{
+    requires owning_pointer<Type>;
+    requires std::same_as<std::remove_cvref_t<decltype(*value)>,
+                          std::remove_cvref_t<Reference>>;
+}
+|| requires (Type && value)
+{
+    requires optional<Type>;
+    requires std::same_as<std::remove_cvref_t<decltype(*value)>,
+                          std::remove_cvref_t<Reference>>;
+};
+
+template <typename Type>
+concept self_referencing = access::self_referencing<Type>();
+
+template <typename Type>
+concept has_fixed_nonzero_size = requires
+{
+    requires std::integral_constant<std::size_t,
+        std::remove_cvref_t<Type>{}.size()>::value != 0;
+};
+
+template <typename Type>
+concept array =
+    basic_array<Type> ||
+    (container<Type> && has_fixed_nonzero_size<Type> && requires {
+        requires Type {
+        }
+        .size() * sizeof(typename Type::value_type) == sizeof(Type);
+        Type{}.data();
+    });
+
+} // namespace concepts
+
+template <typename CharType, std::size_t Size>
+struct string_literal : public std::array<CharType, Size + 1>
+{
+    using base = std::array<CharType, Size + 1>;
+    using value_type = typename base::value_type;
+    using pointer = typename base::pointer;
+    using const_pointer = typename base::const_pointer;
+    using iterator = typename base::iterator;
+    using const_iterator = typename base::const_iterator;
+    using reference = typename base::const_pointer;
+    using const_reference = typename base::const_pointer;
+    using size_type = default_size_type;
+
+    constexpr string_literal() = default;
+    constexpr string_literal(const CharType (&value)[Size + 1])
+    {
+        std::copy_n(std::begin(value), Size + 1, std::begin(*this));
+    }
+
+    constexpr auto operator<=>(const string_literal &) const = default;
+
+    constexpr default_size_type size() const
+    {
+        return Size;
+    }
+
+    constexpr bool empty() const
+    {
+        return !Size;
+    }
+
+    using base::begin;
+
+    constexpr auto end()
+    {
+        return base::end() - 1;
+    }
+
+    constexpr auto end() const
+    {
+        return base::end() - 1;
+    }
+
+    using base::data;
+    using base::operator[];
+    using base::at;
+
+private:
+    using base::cbegin;
+    using base::cend;
+    using base::rbegin;
+    using base::rend;
+};
+
+template <typename CharType, std::size_t Size>
+string_literal(const CharType (&value)[Size])
+    -> string_literal<CharType, Size - 1>;
+
+template <typename Item>
+class bytes
+{
+public:
+    using value_type = Item;
+
+    constexpr explicit bytes(std::span<Item> items) :
+        m_items(items.data()), m_size(items.size())
+    {
+    }
+
+    constexpr explicit bytes(std::span<Item> items, auto size) :
+        m_items(items.data()), m_size(std::size_t(size))
+    {
+    }
+
+    constexpr auto data() const
+    {
+        return m_items;
+    }
+
+    constexpr std::size_t size_in_bytes() const
+    {
+        return m_size * sizeof(Item);
+    }
+
+    constexpr std::size_t count() const
+    {
+        return m_size;
+    }
+
+private:
+    static_assert(std::is_trivially_copyable_v<Item>);
+
+    Item * m_items;
+    std::size_t m_size;
+};
+
+template <typename Item>
+bytes(std::span<Item>) -> bytes<Item>;
+
+template <typename Item>
+bytes(std::span<Item>, std::size_t) -> bytes<Item>;
+
+template <typename Item, std::size_t Count>
+bytes(Item(&)[Count]) -> bytes<Item>;
+
+template <concepts::container Container>
+bytes(Container && container)
+    -> bytes<std::remove_reference_t<decltype(container[0])>>;
+
+template <concepts::container Container>
+bytes(Container && container, std::size_t)
+    -> bytes<std::remove_reference_t<decltype(container[0])>>;
+
+constexpr auto as_bytes(auto && object)
+{
+    return bytes(std::span{&object, 1});
+}
+
+template <typename Option>
+struct option
+{
+    using zpp_bits_option = void;
+    constexpr auto operator()(auto && archive)
+    {
+        if constexpr (requires {
+                          archive.option(static_cast<Option &>(*this));
+                      }) {
+            archive.option(static_cast<Option &>(*this));
+        }
+    }
+};
+
+inline namespace options
+{
+struct append : option<append>
+{
+};
+
+struct reserve : option<reserve>
+{
+    constexpr explicit reserve(std::size_t size) : size(size)
+    {
+    }
+    std::size_t size{};
+};
+
+struct resize : option<resize>
+{
+    constexpr explicit resize(std::size_t size) : size(size)
+    {
+    }
+    std::size_t size{};
+};
+
+template <std::size_t Size>
+struct alloc_limit : option<alloc_limit<Size>>
+{
+    constexpr static auto alloc_limit_value = Size;
+};
+
+template <std::size_t Multiplier, std::size_t Divisor = 1>
+struct enlarger : option<enlarger<Multiplier, Divisor>>
+{
+    constexpr static auto enlarger_value =
+        std::tuple{Multiplier, Divisor};
+};
+
+using exact_enlarger = enlarger<1, 1>;
+
+namespace endian
+{
+struct big : option<big>
+{
+    constexpr static auto value = std::endian::big;
+};
+
+struct little : option<little>
+{
+    constexpr static auto value = std::endian::little;
+};
+
+using network = big;
+
+using native = std::
+    conditional_t<std::endian::native == std::endian::little, little, big>;
+
+using swapped = std::
+    conditional_t<std::endian::native == std::endian::little, big, little>;
+} // namespace endian
+
+struct no_fit_size : option<no_fit_size>
+{
+};
+
+struct no_enlarge_overflow : option<no_enlarge_overflow>
+{
+};
+
+struct enlarge_overflow : option<enlarge_overflow>
+{
+};
+
+struct no_size : option<no_size>
+{
+    using default_size_type = void;
+};
+
+struct size1b : option<size1b>
+{
+    using default_size_type = unsigned char;
+};
+
+struct size2b : option<size2b>
+{
+    using default_size_type = std::uint16_t;
+};
+
+struct size4b : option<size4b>
+{
+    using default_size_type = std::uint32_t;
+};
+
+struct size8b : option<size8b>
+{
+    using default_size_type = std::uint64_t;
+};
+
+struct size_native : option<size_native>
+{
+    using default_size_type = std::size_t;
+};
+} // namespace options
+
+template <typename Type>
+constexpr auto access::number_of_members()
+{
+    using type = std::remove_cvref_t<Type>;
+    if constexpr (std::is_array_v<type>) {
+        return std::extent_v<type>;
+    } else if constexpr (!std::is_class_v<type>) {
+        return 0;
+    } else if constexpr (concepts::container<type> &&
+                         concepts::has_fixed_nonzero_size<type>) {
+        return type{}.size();
+    } else if constexpr (concepts::tuple<type>) {
+        return std::tuple_size_v<type>;
+    } else if constexpr (requires {
+                             requires std::same_as<
+                                 typename type::serialize,
+                                 members<type::serialize::value>>;
+                             requires type::serialize::value !=
+                                 std::numeric_limits<
+                                     std::size_t>::max();
+                         }) {
+        return type::serialize::value;
+    } else if constexpr (requires(Type && item) {
+                             requires std::same_as<
+                                 decltype(try_serialize(item)),
+                                 members<decltype(try_serialize(
+                                     item))::value>>;
+                             requires decltype(try_serialize(
+                                 item))::value !=
+                                 std::numeric_limits<
+                                     std::size_t>::max();
+                         }) {
+        return decltype(serialize(std::declval<type>()))::value;
+    } else if constexpr (requires {
+                             requires std::same_as<
+                                 typename type::serialize,
+                                 protocol<type::serialize::value,
+                                          type::serialize::members>>;
+                             requires type::serialize::members !=
+                                 std::numeric_limits<
+                                     std::size_t>::max();
+                         }) {
+        return type::serialize::members;
+    } else if constexpr (requires(Type && item) {
+                             requires std::same_as<
+                                 decltype(try_serialize(item)),
+                                 protocol<decltype(try_serialize(item))::value,
+                                          decltype(try_serialize(
+                                              item))::members>>;
+                             requires decltype(try_serialize(
+                                 item))::members !=
+                                 std::numeric_limits<
+                                     std::size_t>::max();
+                         }) {
+        return decltype(serialize(std::declval<type>()))::members;
+#if ZPP_BITS_AUTODETECT_MEMBERS_MODE == 0
+    } else if constexpr (std::is_aggregate_v<type>) {
+        // clang-format off
+        if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{},  /*.................................................................................................................*/ any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 50; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 49; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 48; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 47; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 46; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 45; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 44; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 43; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 42; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 41; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 40; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 39; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 38; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 37; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 36; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 35; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 34; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 33; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 32; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 31; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 30; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 29; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 28; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 27; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 26; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 25; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 24; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 23; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 22; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 21; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 20; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 19; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 18; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 17; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 16; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 15; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 14; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 13; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 12; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 11; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 10; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 9; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 8; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 7; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}, any{}}; }) { return 6; } else if constexpr (requires { type{any{}, any{}, any{}, any{}, any{}}; }) { return 5; } else if constexpr (requires { type{any{}, any{}, any{}, any{}}; }) { return 4; } else if constexpr (requires { type{any{}, any{}, any{}}; }) { return 3; } else if constexpr (requires { type{any{}, any{}}; }) { return 2; } else if constexpr (requires { type{any{}}; }) { return 1;
+            // Returns the number of members
+            // clang-format on
+        } else if constexpr (concepts::empty<type> && requires {
+                                 typename std::void_t<decltype(type{})>;
+                             }) {
+            return 0;
+        } else {
+            return -1;
+        }
+#elif ZPP_BITS_AUTODETECT_MEMBERS_MODE > 0
+#if ZPP_BITS_AUTODETECT_MEMBERS_MODE == 1
+        // clang-format off
+    } else if constexpr (requires { [](Type && object) { auto && [a1] = object; }; }) { return 1; } else if constexpr (requires { [](Type && object) { auto && [a1, a2] = object; }; }) { return 2; /*.................................................................................................................*/ } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3] = object; }; }) { return 3; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4] = object; }; }) { return 4; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5] = object; }; }) { return 5; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6] = object; }; }) { return 6; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; }; }) { return 7; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; }; }) { return 8; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; }; }) { return 9; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; }; }) { return 10; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; }; }) { return 11; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; }; }) { return 12; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; }; }) { return 13; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; }; }) { return 14; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; }; }) { return 15; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; }; }) { return 16; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; }; }) { return 17; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; }; }) { return 18; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; }; }) { return 19; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; }; }) { return 20; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; }; }) { return 21; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; }; }) { return 22; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; }; }) { return 23; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; }; }) { return 24; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; }; }) { return 25; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; }; }) { return 26; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; }; }) { return 27; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; }; }) { return 28; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; }; }) { return 29; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; }; }) { return 30; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; }; }) { return 31; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; }; }) { return 32; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; }; }) { return 33; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; }; }) { return 34; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; }; }) { return 35; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; }; }) { return 36; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; }; }) { return 37; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; }; }) { return 38; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; }; }) { return 39; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; }; }) { return 40; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; }; }) { return 41; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; }; }) { return 42; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; }; }) { return 43; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; }; }) { return 44; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; }; }) { return 45; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; }; }) { return 46; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; }; }) { return 47; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; }; }) { return 48; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; }; }) { return 49; } else if constexpr (requires { [](Type && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; }; }) { return 50;
+        // Returns the number of members
+        // clang-format on
+#else // ZPP_BITS_AUTODETECT_MEMBERS_MODE == 1
+#error "Invalid value for ZPP_BITS_AUTODETECT_MEMBERS_MODE"
+#endif
+#endif
+    } else {
+        return -1;
+    }
+}
+
+template <typename Type>
+struct access::byte_serializable_visitor
+{
+    template <typename... Types>
+    constexpr auto operator()() {
+        using type = std::remove_cvref_t<Type>;
+
+        if constexpr (concepts::empty<type>) {
+            return std::false_type{};
+        } else if constexpr ((... || has_explicit_serialize<
+                                         Types,
+                                         traits::visitor<Types>>())) {
+            return std::false_type{};
+        } else if constexpr ((... || !byte_serializable<Types>())) {
+            return std::false_type{};
+        } else if constexpr ((0 + ... + sizeof(Types)) != sizeof(type)) {
+            return std::false_type{};
+        } else if constexpr ((... || concepts::empty<Types>)) {
+            return std::false_type{};
+        } else {
+            return std::true_type{};
+        }
+    }
+};
+
+template <typename Type>
+constexpr auto access::byte_serializable()
+{
+    constexpr auto members_count = number_of_members<Type>();
+    using type = std::remove_cvref_t<Type>;
+
+    if constexpr (members_count < 0) {
+        return false;
+    } else if constexpr (!std::is_trivially_copyable_v<type>) {
+        return false;
+    } else if constexpr (has_explicit_serialize<type,
+                                                traits::visitor<type>>()) {
+        return false;
+    } else if constexpr (
+        !requires {
+            requires std::integral_constant<
+                int,
+                (std::bit_cast<std::remove_all_extents_t<type>>(
+                     std::array<
+                         std::byte,
+                         sizeof(std::remove_all_extents_t<type>)>()),
+                 0)>::value == 0;
+        }) {
+        return false;
+    } else if constexpr (concepts::array<type>) {
+        return byte_serializable<
+            std::remove_cvref_t<decltype(std::declval<type>()[0])>>();
+    } else if constexpr (members_count > 0) {
+        return visit_members_types<type>(
+            byte_serializable_visitor<type>{})();
+    } else {
+        return true;
+    }
+}
+
+template <typename Type>
+struct access::endian_independent_byte_serializable_visitor
+{
+    template <typename... Types>
+    constexpr auto operator()() {
+        using type = std::remove_cvref_t<Type>;
+
+        if constexpr (concepts::empty<type>) {
+            return std::false_type{};
+        } else if constexpr ((... || has_explicit_serialize<
+                                         Types,
+                                         traits::visitor<Types>>())) {
+            return std::false_type{};
+        } else if constexpr ((... || !endian_independent_byte_serializable<Types>())) {
+            return std::false_type{};
+        } else if constexpr ((0 + ... + sizeof(Types)) != sizeof(type)) {
+            return std::false_type{};
+        } else if constexpr ((... || concepts::empty<Types>)) {
+            return std::false_type{};
+        } else if constexpr (!concepts::byte_type<type>) {
+            return std::false_type{};
+        } else {
+            return std::true_type{};
+        }
+    }
+};
+
+template <typename Type>
+constexpr auto access::endian_independent_byte_serializable()
+{
+    constexpr auto members_count = number_of_members<Type>();
+    using type = std::remove_cvref_t<Type>;
+
+    if constexpr (members_count < 0) {
+        return false;
+    } else if constexpr (!std::is_trivially_copyable_v<type>) {
+        return false;
+    } else if constexpr (has_explicit_serialize<type,
+                                                traits::visitor<type>>()) {
+        return false;
+    } else if constexpr (
+        !requires {
+            requires std::integral_constant<
+                int,
+                (std::bit_cast<std::remove_all_extents_t<type>>(
+                     std::array<
+                         std::byte,
+                         sizeof(std::remove_all_extents_t<type>)>()),
+                 0)>::value == 0;
+        }) {
+        return false;
+    } else if constexpr (concepts::array<type>) {
+        return endian_independent_byte_serializable<
+            std::remove_cvref_t<decltype(std::declval<type>()[0])>>();
+    } else if constexpr (members_count > 0) {
+        return visit_members_types<type>(
+            endian_independent_byte_serializable_visitor<type>{})();
+    } else {
+        return concepts::byte_type<type>;
+    }
+}
+
+template <typename Type, typename Self, typename... Visited>
+struct access::self_referencing_visitor
+{
+    template <typename... Types>
+    constexpr auto operator()() {
+        using type = std::remove_cvref_t<Type>;
+        using self = std::remove_cvref_t<Self>;
+
+        if constexpr (concepts::empty<type>) {
+            return std::false_type{};
+        } else if constexpr ((... || concepts::type_references<
+                                         std::remove_cvref_t<Types>,
+                                         self>)) {
+            return std::true_type{};
+        } else if constexpr ((sizeof...(Visited) != 0) &&
+                             (... || std::same_as<type, Visited>)) {
+            return std::false_type{};
+        } else if constexpr ((... ||
+                              self_referencing<std::remove_cvref_t<Types>,
+                                               self,
+                                               type,
+                                               Visited...>())) {
+            return std::true_type{};
+        } else {
+            return std::false_type{};
+        }
+    }
+};
+
+template <typename Type, typename Self/* = Type*/, typename... Visited>
+constexpr auto access::self_referencing()
+{
+    constexpr auto members_count = number_of_members<Type>();
+    using type = std::remove_cvref_t<Type>;
+    using self = std::remove_cvref_t<Self>;
+
+    if constexpr (members_count < 0) {
+        return false;
+    } else if constexpr (has_explicit_serialize<type,
+                                                traits::visitor<type>>()) {
+        return false;
+    } else if constexpr (members_count == 0) {
+        return false;
+    } else if constexpr (concepts::array<type>) {
+        return self_referencing<
+            std::remove_cvref_t<decltype(std::declval<type>()[0])>,
+            self,
+            Visited...>();
+    } else {
+        return visit_members_types<type>(
+            self_referencing_visitor<type, self, Visited...>{})();
+    }
+}
+
+template <typename Type>
+constexpr auto number_of_members()
+{
+    return access::number_of_members<Type>();
+}
+
+ZPP_BITS_INLINE constexpr decltype(auto) visit_members(auto && object,
+                                                       auto && visitor)
+{
+    return access::visit_members(object, visitor);
+}
+
+template <typename Type>
+constexpr decltype(auto) visit_members_types(auto && visitor)
+{
+    return access::visit_members_types<Type>(visitor);
+}
+
+template <typename Type>
+struct optional_ptr : std::unique_ptr<Type>
+{
+    using base = std::unique_ptr<Type>;
+    using base::base;
+    using base::operator=;
+
+    constexpr optional_ptr(base && other) noexcept :
+        base(std::move(other))
+    {
+    }
+};
+
+template <typename Type, typename...>
+optional_ptr(Type *) -> optional_ptr<Type>;
+
+template <typename Archive, typename Type>
+ZPP_BITS_INLINE constexpr static auto serialize(
+    Archive & archive,
+    const optional_ptr<Type> & self) requires(Archive::kind() == kind::out)
+{
+    if (!self) [[unlikely]] {
+        return archive(std::byte(false));
+    } else {
+        return archive(std::byte(true), *self);
+    }
+}
+
+template <typename Archive, typename Type>
+ZPP_BITS_INLINE constexpr static auto
+serialize(Archive & archive,
+          optional_ptr<Type> & self) requires(Archive::kind() == kind::in)
+{
+    std::byte has_value{};
+    if (auto result = archive(has_value); failure(result))
+        [[unlikely]] {
+        return result;
+    }
+
+    if (!bool(has_value)) [[unlikely]] {
+        self = {};
+        return errc{};
+    }
+
+    if (auto result =
+            archive(static_cast<std::unique_ptr<Type> &>(self));
+        failure(result)) [[unlikely]] {
+        return result;
+    }
+
+    return errc{};
+}
+
+template <typename Type, typename SizeType>
+struct sized_item : public Type
+{
+    using Type::Type;
+    using Type::operator=;
+
+    constexpr sized_item(Type && other) noexcept(
+        std::is_nothrow_move_constructible_v<Type>) :
+        Type(std::move(other))
+    {
+    }
+
+    constexpr sized_item(const Type & other) :
+        Type(other)
+    {
+    }
+
+    ZPP_BITS_INLINE constexpr static auto serialize(auto & archive,
+                                                    auto & self)
+    {
+        if constexpr (std::remove_cvref_t<decltype(archive)>::kind() == kind::out) {
+            return archive.template serialize_one<SizeType>(
+                static_cast<const Type &>(self));
+        } else {
+            return archive.template serialize_one<SizeType>(
+                static_cast<Type &>(self));
+        }
+    }
+};
+
+template <typename Type, typename SizeType>
+auto serialize(const sized_item<Type, SizeType> &)
+    -> members<number_of_members<Type>()>;
+
+template <typename Type, typename SizeType>
+using sized_t = sized_item<Type, SizeType>;
+
+template <typename Type>
+using unsized_t = sized_t<Type, void>;
+
+template <typename Type, typename SizeType>
+struct sized_item_ref
+{
+    constexpr explicit sized_item_ref(Type && value) :
+        value(std::forward<Type>(value))
+    {
+    }
+
+    ZPP_BITS_INLINE constexpr static auto serialize(auto & serializer,
+                                                    auto & self)
+    {
+        return serializer.template serialize_one<SizeType>(self.value);
+    }
+
+    Type && value;
+};
+
+template <typename SizeType, typename Type>
+constexpr auto sized(Type && value)
+{
+    return sized_item_ref<Type &, SizeType>(value);
+}
+
+template <typename Type>
+constexpr auto unsized(Type && value)
+{
+    return sized_item_ref<Type &, void>(value);
+}
+
+enum class varint_encoding
+{
+    normal,
+    zig_zag,
+};
+
+template <typename Type, varint_encoding Encoding = varint_encoding::normal>
+struct varint
+{
+    varint() = default;
+
+    using value_type = Type;
+    static constexpr auto encoding = Encoding;
+
+    constexpr varint(Type value) : value(value)
+    {
+    }
+
+    constexpr operator Type &() &
+    {
+        return value;
+    }
+
+    constexpr operator Type() const
+    {
+        return value;
+    }
+
+    constexpr decltype(auto) operator*() &
+    {
+        return (value);
+    }
+
+    constexpr auto operator*() const &
+    {
+        return value;
+    }
+
+    Type value{};
+};
+
+namespace concepts
+{
+
+template <typename Type>
+concept varint = requires
+{
+    requires std::same_as<
+        Type,
+        zpp::bits::varint<typename Type::value_type, Type::encoding>>;
+};
+
+} // namespace concepts
+
+template <typename Type>
+constexpr auto varint_max_size = sizeof(Type) * CHAR_BIT / (CHAR_BIT - 1) +
+                                 1;
+
+template <varint_encoding Encoding = varint_encoding::normal>
+ZPP_BITS_INLINE constexpr auto varint_size(auto value)
+{
+    if constexpr (Encoding == varint_encoding::zig_zag) {
+        return varint_size(std::make_unsigned_t<decltype(value)>((value << 1) ^
+                           (value >> (sizeof(value) * CHAR_BIT - 1))));
+    } else {
+        return ((sizeof(value) * CHAR_BIT) -
+                std::countl_zero(
+                    std::make_unsigned_t<decltype(value)>(value | 0x1)) +
+                (CHAR_BIT - 2)) /
+               (CHAR_BIT - 1);
+    }
+}
+
+template <typename Archive, typename Type, varint_encoding Encoding>
+ZPP_BITS_INLINE constexpr auto serialize(
+    Archive & archive,
+    varint<Type, Encoding> self) requires(Archive::kind() == kind::out)
+{
+    auto orig_value = std::conditional_t<std::is_enum_v<Type>,
+                                         traits::underlying_type_t<Type>,
+                                         Type>(self.value);
+    auto value = std::make_unsigned_t<Type>(orig_value);
+    if constexpr (varint_encoding::zig_zag == Encoding) {
+        value =
+            (value << 1) ^ (orig_value >> (sizeof(Type) * CHAR_BIT - 1));
+    }
+
+    constexpr auto max_size = varint_max_size<Type>;
+    if constexpr (Archive::resizable) {
+        if (auto result = archive.enlarge_for(max_size); failure(result))
+            [[unlikely]] {
+            return result;
+        }
+    }
+
+    auto data = archive.remaining_data();
+    if constexpr (!Archive::resizable) {
+        auto data_size = data.size();
+        if (data_size < max_size) [[unlikely]] {
+            if (data_size < varint_size(value)) [[unlikely]] {
+                return errc{std::errc::result_out_of_range};
+            }
+        }
+    }
+
+    using byte_type = std::remove_cvref_t<decltype(data[0])>;
+    std::size_t position = {};
+    while (value >= 0x80) {
+        data[position++] = byte_type((value & 0x7f) | 0x80);
+        value >>= (CHAR_BIT - 1);
+    }
+    data[position++] = byte_type(value);
+
+    archive.position() += position;
+    return errc{};
+}
+
+constexpr auto decode_varint(auto data, auto & value, auto & position)
+{
+    using value_type = std::remove_cvref_t<decltype(value)>;
+    if (data.size() < varint_max_size<value_type>) [[unlikely]] {
+        std::size_t shift = 0;
+        for (auto & byte_value : data) {
+            auto next_byte = value_type(byte_value);
+            value |= (next_byte & 0x7f) << shift;
+            if (next_byte >= 0x80) [[unlikely]] {
+                shift += CHAR_BIT - 1;
+                continue;
+            }
+            position += 1 + std::distance(data.data(), &byte_value);
+            return errc{};
+        }
+        return errc{std::errc::result_out_of_range};
+    } else {
+        auto p = data.data();
+        do {
+            // clang-format off
+            value_type next_byte;
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 0)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 1)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 2) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 2)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 3) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 3)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 4)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 5) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 5)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 6)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 7)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 8)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x01) << ((CHAR_BIT - 1) * 9)); if (next_byte < 0x80) [[likely]] { break; } }}}
+            return errc{std::errc::value_too_large};
+            // clang-format on
+        } while (false);
+        position += std::distance(data.data(), p);
+        return errc{};
+    }
+}
+
+template <typename Archive, typename Type, varint_encoding Encoding>
+ZPP_BITS_INLINE constexpr auto serialize(
+    Archive & archive,
+    varint<Type, Encoding> & self) requires(Archive::kind() == kind::in)
+{
+    using value_type = std::conditional_t<
+        std::is_enum_v<Type>,
+        std::make_unsigned_t<traits::underlying_type_t<Type>>,
+        std::make_unsigned_t<Type>>;
+    value_type value{};
+    auto data = archive.remaining_data();
+
+    if constexpr (!ZPP_BITS_INLINE_DECODE_VARINT) {
+        auto & position = archive.position();
+        if (!data.empty() && !(value_type(data[0]) & 0x80)) [[likely]] {
+            value = value_type(data[0]);
+            position += 1;
+        } else if (auto result =
+                       std::is_constant_evaluated()
+                           ? decode_varint(data, value, position)
+                           : decode_varint(
+                                 std::span{
+                                     reinterpret_cast<const std::byte *>(
+                                         data.data()),
+                                     data.size()},
+                                 value,
+                                 position);
+                   failure(result)) [[unlikely]] {
+            return result;
+        }
+
+        if constexpr (varint_encoding::zig_zag == Encoding) {
+            self.value =
+                decltype(self.value)((value >> 1) ^ -(value & 0x1));
+        } else {
+            self.value = decltype(self.value)(value);
+        }
+        return errc{};
+    } else if (data.size() < varint_max_size<value_type>) [[unlikely]] {
+        std::size_t shift = 0;
+        for (auto & byte_value : data) {
+            auto next_byte = decltype(value)(byte_value);
+            value |= (next_byte & 0x7f) << shift;
+            if (next_byte >= 0x80) [[unlikely]] {
+                shift += CHAR_BIT - 1;
+                continue;
+            }
+            if constexpr (varint_encoding::zig_zag == Encoding) {
+                self.value =
+                    decltype(self.value)((value >> 1) ^ -(value & 0x1));
+            } else {
+                self.value = decltype(self.value)(value);
+            }
+            archive.position() +=
+                1 + std::distance(data.data(), &byte_value);
+            return errc{};
+        }
+        return errc{std::errc::result_out_of_range};
+    } else {
+        auto p = data.data();
+        do {
+            // clang-format off
+            value_type next_byte;
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 0)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 1)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 2) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 2)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 3) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 3)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 4)); if (next_byte < 0x80) [[likely]] { break; }
+            if constexpr (varint_max_size<value_type> > 5) {
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 5)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 6)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 7)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x7f) << ((CHAR_BIT - 1) * 8)); if (next_byte < 0x80) [[likely]] { break; }
+            next_byte = value_type(*p++); value |= ((next_byte & 0x01) << ((CHAR_BIT - 1) * 9)); if (next_byte < 0x80) [[likely]] { break; } }}}
+            return errc{std::errc::value_too_large};
+            // clang-format on
+        } while (false);
+        if constexpr (varint_encoding::zig_zag == Encoding) {
+            self.value =
+                decltype(self.value)((value >> 1) ^ -(value & 0x1));
+        } else {
+            self.value = decltype(self.value)(value);
+        }
+        archive.position() += std::distance(data.data(), p);
+        return errc{};
+    }
+}
+
+template <typename Archive, typename Type, varint_encoding Encoding>
+constexpr auto
+serialize(Archive & archive,
+          varint<Type, Encoding> && self) requires(Archive::kind() ==
+                                                   kind::in) = delete;
+
+using vint32_t = varint<std::int32_t>;
+using vint64_t = varint<std::int64_t>;
+
+using vuint32_t = varint<std::uint32_t>;
+using vuint64_t = varint<std::uint64_t>;
+
+using vsint32_t = varint<std::int32_t, varint_encoding::zig_zag>;
+using vsint64_t = varint<std::int64_t, varint_encoding::zig_zag>;
+
+using vsize_t = varint<std::size_t>;
+
+inline namespace options
+{
+struct size_varint : option<size_varint>
+{
+    using default_size_type = vsize_t;
+};
+} // namespace options
+
+template <concepts::byte_view ByteView, typename... Options>
+class basic_out
+{
+public:
+    template <concepts::byte_view, typename...>
+    friend class basic_out;
+
+    template <typename>
+    friend struct option;
+
+    template <typename... Types>
+    using template_type = basic_out<Types...>;
+
+    friend access;
+
+    template <typename, typename>
+    friend struct sized_item;
+
+    template <typename, typename>
+    friend struct sized_item_ref;
+
+    template <typename, concepts::variant>
+    friend struct known_id_variant;
+
+    template <typename, concepts::variant>
+    friend struct known_dynamic_id_variant;
+
+    using byte_type = typename ByteView::value_type;
+
+    static constexpr auto endian_aware =
+        (... ||
+         std::same_as<std::remove_cvref_t<Options>, endian::swapped>);
+
+    using default_size_type = traits::default_size_type_t<Options...>;
+
+    constexpr static auto allocation_limit = traits::alloc_limit<Options...>();
+
+    constexpr static auto enlarger = traits::enlarger<Options...>();
+
+    constexpr static auto no_enlarge_overflow =
+        (... ||
+         std::same_as<std::remove_cvref_t<Options>, options::no_enlarge_overflow>);
+
+    constexpr static bool resizable = requires(ByteView view)
+    {
+        view.resize(1);
+    };
+
+    using view_type =
+        std::conditional_t<resizable,
+                           ByteView &,
+                           std::remove_cvref_t<decltype(
+                               std::span{std::declval<ByteView &>()})>>;
+
+    constexpr explicit basic_out(ByteView && view, Options && ... options) : m_data(view)
+    {
+        static_assert(!resizable);
+        (options(*this), ...);
+    }
+
+    constexpr explicit basic_out(ByteView & view, Options && ... options) : m_data(view)
+    {
+        (options(*this), ...);
+    }
+
+    ZPP_BITS_INLINE constexpr auto operator()(auto &&... items)
+    {
+        return serialize_many(items...);
+    }
+
+    constexpr decltype(auto) data()
+    {
+        return m_data;
+    }
+
+    constexpr std::size_t position() const
+    {
+        return m_position;
+    }
+
+    constexpr std::size_t & position()
+    {
+        return m_position;
+    }
+
+    constexpr auto remaining_data()
+    {
+        return std::span<byte_type>{m_data.data() + m_position,
+                                    m_data.size() - m_position};
+    }
+
+    constexpr auto processed_data()
+    {
+        return std::span<byte_type>{m_data.data(), m_position};
+    }
+
+    constexpr void reset(std::size_t position = 0)
+    {
+        m_position = position;
+    }
+
+    constexpr static auto kind()
+    {
+        return kind::out;
+    }
+
+    ZPP_BITS_INLINE constexpr errc enlarge_for(auto additional_size)
+    {
+        auto size = m_data.size();
+        if (additional_size > size - m_position) [[unlikely]] {
+            constexpr auto multiplier = std::get<0>(enlarger);
+            constexpr auto divisor = std::get<1>(enlarger);
+            static_assert(multiplier != 0 && divisor != 0);
+
+            auto required_size = size + additional_size;
+            if constexpr (!no_enlarge_overflow) {
+                if (required_size < size) [[unlikely]] {
+                    return std::errc::no_buffer_space;
+                }
+            }
+
+            auto new_size = required_size;
+            if constexpr (multiplier != 1) {
+                new_size *= multiplier;
+                if constexpr (!no_enlarge_overflow) {
+                    if (new_size / multiplier != required_size) [[unlikely]] {
+                        return std::errc::no_buffer_space;
+                    }
+                }
+            }
+            if constexpr (divisor != 1) {
+                new_size /= divisor;
+            }
+            if constexpr (allocation_limit !=
+                          std::numeric_limits<std::size_t>::max()) {
+                if (new_size > allocation_limit) [[unlikely]] {
+                    return std::errc::no_buffer_space;
+                }
+            }
+            m_data.resize(new_size);
+        }
+        return {};
+    }
+
+protected:
+    ZPP_BITS_INLINE constexpr errc serialize_many(auto && first_item,
+                                                  auto &&... items)
+    {
+        if (auto result = serialize_one(first_item); failure(result))
+            [[unlikely]] {
+            return result;
+        }
+
+        return serialize_many(items...);
+    }
+
+    ZPP_BITS_INLINE constexpr errc serialize_many()
+    {
+        return {};
+    }
+
+    constexpr auto option(append)
+    {
+        static_assert(resizable);
+        m_position = m_data.size();
+    }
+
+    constexpr auto option(reserve size)
+    {
+        static_assert(resizable);
+        m_data.reserve(size.size);
+    }
+
+    constexpr auto option(resize size)
+    {
+        static_assert(resizable);
+        m_data.resize(size.size);
+        if (m_position > size.size) {
+            m_position = size.size;
+        }
+    }
+
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::unspecialized auto && item)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        static_assert(!std::is_pointer_v<type>);
+
+        if constexpr (requires { type::serialize(*this, item); }) {
+            return type::serialize(*this, item);
+        } else if constexpr (requires { serialize(*this, item); }) {
+            return serialize(*this, item);
+        } else if constexpr (std::is_fundamental_v<type> || std::is_enum_v<type>) {
+            if constexpr (resizable) {
+                if (auto result = enlarge_for(sizeof(item));
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            } else if (sizeof(item) > m_data.size() - m_position)
+                [[unlikely]] {
+                return std::errc::result_out_of_range;
+            }
+
+            if (std::is_constant_evaluated()) {
+                auto value = std::bit_cast<
+                    std::array<std::remove_const_t<byte_type>,
+                               sizeof(item)>>(item);
+                for (std::size_t i = 0; i < sizeof(value); ++i) {
+                    if constexpr (endian_aware) {
+                        m_data[m_position + i] = value[sizeof(value) - 1 - i];
+                    } else {
+                        m_data[m_position + i] = value[i];
+                    }
+                }
+            } else {
+                if constexpr (endian_aware) {
+                    std::reverse_copy(
+                        reinterpret_cast<const byte_type *>(&item),
+                        reinterpret_cast<const byte_type *>(&item) +
+                            sizeof(item),
+                        m_data.data() + m_position);
+                } else {
+                    std::memcpy(
+                        m_data.data() + m_position, &item, sizeof(item));
+                }
+            }
+            m_position += sizeof(item);
+            return {};
+        } else if constexpr (requires {
+                                 requires std::same_as<
+                                     bytes<typename type::value_type>,
+                                     type>;
+                             }) {
+            static_assert(
+                !endian_aware ||
+                concepts::byte_type<
+                    std::remove_cvref_t<decltype(*item.data())>>);
+
+            auto item_size_in_bytes = item.size_in_bytes();
+            if (!item_size_in_bytes) [[unlikely]] {
+                return {};
+            }
+
+            if constexpr (resizable) {
+                if (auto result = enlarge_for(item_size_in_bytes);
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            } else if (item_size_in_bytes > m_data.size() - m_position)
+                [[unlikely]] {
+                return std::errc::result_out_of_range;
+            }
+
+            if (std::is_constant_evaluated()) {
+                auto count = item.count();
+                for (std::size_t index = 0; index < count; ++index) {
+                    auto value = std::bit_cast<
+                        std::array<std::remove_const_t<byte_type>,
+                                   sizeof(typename type::value_type)>>(
+                        item.data()[index]);
+                    for (std::size_t i = 0;
+                         i < sizeof(typename type::value_type);
+                         ++i) {
+                        m_data[m_position +
+                               index * sizeof(typename type::value_type) +
+                               i] = value[i];
+                    }
+                }
+            } else {
+                // Ignore GCC Issue.
+#if !defined __clang__ && defined __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+                std::memcpy(m_data.data() + m_position,
+                            item.data(),
+                            item_size_in_bytes);
+#if !defined __clang__ && defined __GNUC__
+#pragma GCC diagnostic pop
+#endif
+            }
+            m_position += item_size_in_bytes;
+            return {};
+        } else if constexpr (concepts::empty<type>) {
+            return {};
+        } else if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                          type>) {
+            return serialize_one(as_bytes(item));
+        } else if constexpr (concepts::self_referencing<type>) {
+            return visit_members(
+                item,
+                [&](auto &&... items) constexpr {
+                    return serialize_many(items...);
+                });
+        } else {
+            return visit_members(
+                item,
+                [&](auto &&... items) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    return serialize_many(items...);
+                });
+        }
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::array auto && array)
+    {
+        using value_type = std::remove_cvref_t<decltype(array[0])>;
+
+        if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                   value_type>) {
+            return serialize_one(bytes(array));
+        } else {
+            for (auto & item : array) {
+                if (auto result = serialize_one(item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        }
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::container auto && container)
+    {
+        using type = std::remove_cvref_t<decltype(container)>;
+        using value_type = typename type::value_type;
+
+        if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                   value_type> &&
+                      std::is_base_of_v<std::random_access_iterator_tag,
+                                        typename std::iterator_traits<
+                                            typename type::iterator>::
+                                            iterator_category> &&
+                      requires { container.data(); }) {
+            auto size = container.size();
+            if constexpr (!std::is_void_v<SizeType> &&
+                          (concepts::associative_container<
+                               decltype(container)> ||
+                           requires(type container) {
+                               container.resize(1);
+                           } ||
+                           (
+                               requires(type container) {
+                                   container = {container.data(), 1};
+                               } &&
+                               !requires {
+                                   requires(type::extent !=
+                                            std::dynamic_extent);
+                                   requires concepts::
+                                       has_fixed_nonzero_size<type>;
+                               }))) {
+                if (auto result =
+                        serialize_one(static_cast<SizeType>(size));
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            }
+            return serialize_one(bytes(container, size));
+        } else {
+            if constexpr (!std::is_void_v<SizeType> &&
+                          (concepts::associative_container<
+                               decltype(container)> ||
+                           requires(type container) {
+                               container.resize(1);
+                           } ||
+                           (
+                               requires(type container) {
+                                   container = {container.data(), 1};
+                               } &&
+                               !requires {
+                                   requires(type::extent !=
+                                            std::dynamic_extent);
+                                   requires concepts::
+                                       has_fixed_nonzero_size<type>;
+                               }))) {
+                if (auto result = serialize_one(
+                        static_cast<SizeType>(container.size()));
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            }
+            for (auto & item : container) {
+                if (auto result = serialize_one(item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        }
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::tuple auto && tuple)
+    {
+        return serialize_one(tuple,
+                             std::make_index_sequence<std::tuple_size_v<
+                                 std::remove_cvref_t<decltype(tuple)>>>());
+    }
+
+    template <std::size_t... Indices>
+    ZPP_BITS_INLINE constexpr errc serialize_one(
+        concepts::tuple auto && tuple, std::index_sequence<Indices...>)
+    {
+        return serialize_many(std::get<Indices>(tuple)...);
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::optional auto && optional)
+    {
+        if (!optional) [[unlikely]] {
+            return serialize_one(std::byte(false));
+        } else {
+            return serialize_many(std::byte(true), *optional);
+        }
+    }
+
+    template <typename KnownId = void>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::variant auto && variant)
+    {
+        using type = std::remove_cvref_t<decltype(variant)>;
+
+        if constexpr (!std::is_void_v<KnownId>) {
+            return serialize_one(
+                *std::get_if<
+                    traits::variant<type>::template index<KnownId::value>()>(
+                    std::addressof(variant)));
+        } else {
+            auto variant_index = variant.index();
+            if (std::variant_npos == variant_index) [[unlikely]] {
+                return std::errc::invalid_argument;
+            }
+
+            return std::visit(
+                [index = variant_index,
+                 this](auto & object) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    return this->serialize_many(
+                        traits::variant<type>::id(index), object);
+                },
+                variant);
+        }
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::owning_pointer auto && pointer)
+    {
+        if (nullptr == pointer) [[unlikely]] {
+            return std::errc::invalid_argument;
+        }
+
+        return serialize_one(*pointer);
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::bitset auto && bitset)
+    {
+        constexpr auto size = std::remove_cvref_t<decltype(bitset)>{}.size();
+        constexpr auto size_in_bytes = (size + (CHAR_BIT - 1)) / CHAR_BIT;
+
+        if constexpr (resizable) {
+            if (auto result = enlarge_for(size_in_bytes);
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+        } else if (size_in_bytes > m_data.size() - m_position)
+            [[unlikely]] {
+            return std::errc::result_out_of_range;
+        }
+
+        auto data = m_data.data() + m_position;
+        for (std::size_t i = 0; i < size; ++i) {
+            auto & value = data[i / CHAR_BIT];
+            value = byte_type(static_cast<unsigned char>(value) |
+                              (bitset[i] << (i & 0x7)));
+        }
+
+        m_position += size_in_bytes;
+        return {};
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::by_protocol auto && item)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        if constexpr (!std::is_void_v<SizeType>) {
+            auto size_position = m_position;
+            if (auto result = serialize_one(SizeType{});
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+
+            if constexpr (requires { typename type::serialize; }) {
+                constexpr auto protocol = type::serialize::value;
+                if (auto result = protocol(*this, item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            } else {
+                constexpr auto protocol = decltype(serialize(item))::value;
+                if (auto result = protocol(*this, item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+
+            auto current_position = m_position;
+            std::size_t message_size =
+                    current_position - size_position - sizeof(SizeType);
+            if constexpr (concepts::varint<SizeType>) {
+                constexpr auto preserialized_varint_size = 1;
+                message_size = current_position - size_position -
+                               preserialized_varint_size;
+                auto move_ahead_count =
+                    varint_size(message_size) - preserialized_varint_size;
+                if (move_ahead_count) {
+                    if constexpr (resizable) {
+                        if (auto result = enlarge_for(move_ahead_count);
+                            failure(result)) [[unlikely]] {
+                            return result;
+                        }
+                    } else if (move_ahead_count >
+                               m_data.size() - current_position)
+                        [[unlikely]] {
+                        return std::errc::result_out_of_range;
+                    }
+                    auto data = m_data.data();
+                    auto message_start =
+                        data + size_position + preserialized_varint_size;
+                    auto message_end = data + current_position;
+                    if (std::is_constant_evaluated()) {
+                        for (auto p = message_end - 1; p >= message_start;
+                             --p) {
+                            *(p + move_ahead_count) = *p;
+                        }
+                    } else {
+                        std::memmove(message_start + move_ahead_count,
+                                     message_start,
+                                     message_size);
+                    }
+                    m_position += move_ahead_count;
+                }
+            }
+            return basic_out<std::span<byte_type, sizeof(SizeType)>>{
+                std::span<byte_type, sizeof(SizeType)>{
+                    m_data.data() + size_position, sizeof(SizeType)}}(
+                SizeType(message_size));
+        } else {
+            if constexpr (requires {typename type::serialize;}) {
+                constexpr auto protocol = type::serialize::value;
+                return protocol(*this, item);
+            } else {
+                constexpr auto protocol = decltype(serialize(item))::value;
+                return protocol(*this, item);
+            }
+        }
+    }
+
+    constexpr ~basic_out() = default;
+
+    view_type m_data{};
+    std::size_t m_position{};
+};
+
+template <concepts::byte_view ByteView = std::vector<std::byte>, typename... Options>
+class out : public basic_out<ByteView, Options...>
+{
+public:
+    template <typename... Types>
+    using template_type = out<Types...>;
+
+    using base = basic_out<ByteView, Options...>;
+    using base::basic_out;
+
+    friend access;
+
+    using base::resizable;
+    using base::enlarger;
+
+    constexpr static auto no_fit_size =
+        (... ||
+         std::same_as<std::remove_cvref_t<Options>, options::no_fit_size>);
+
+    ZPP_BITS_INLINE constexpr auto operator()(auto &&... items)
+    {
+        if constexpr (resizable && !no_fit_size &&
+                      enlarger != std::tuple{1, 1}) {
+            auto end = m_data.size();
+            auto result = serialize_many(items...);
+            if (m_position >= end) {
+                m_data.resize(m_position);
+            }
+            return result;
+        } else {
+            return serialize_many(items...);
+        }
+    }
+
+private:
+    using base::serialize_many;
+    using base::m_data;
+    using base::m_position;
+};
+
+template <typename Type, typename... Options>
+out(Type &, Options &&...) -> out<Type, Options...>;
+
+template <typename Type, typename... Options>
+out(Type &&, Options &&...) -> out<Type, Options...>;
+
+template <typename Type, std::size_t Size, typename... Options>
+out(Type (&)[Size], Options &&...)
+    -> out<std::span<Type, Size>, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+out(sized_item<Type, SizeType> &, Options &&...)
+    -> out<Type, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+out(const sized_item<Type, SizeType> &, Options &&...)
+    -> out<const Type, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+out(sized_item<Type, SizeType> &&, Options &&...) -> out<Type, Options...>;
+
+template <concepts::byte_view ByteView = std::vector<std::byte>,
+          typename... Options>
+class in
+{
+public:
+    template <typename... Types>
+    using template_type = in<Types...>;
+
+    template <typename>
+    friend struct option;
+
+    friend access;
+
+    template <typename, typename>
+    friend struct sized_item;
+
+    template <typename, typename>
+    friend struct sized_item_ref;
+
+    template <typename, concepts::variant>
+    friend struct known_id_variant;
+
+    template <typename, concepts::variant>
+    friend struct known_dynamic_id_variant;
+
+    using byte_type = std::add_const_t<typename ByteView::value_type>;
+
+    constexpr static auto endian_aware =
+        (... ||
+         std::same_as<std::remove_cvref_t<Options>, endian::swapped>);
+
+    using default_size_type = traits::default_size_type_t<Options...>;
+
+    constexpr static auto allocation_limit =
+        traits::alloc_limit<Options...>();
+
+    constexpr explicit in(ByteView && view, Options && ... options) : m_data(view)
+    {
+        static_assert(!resizable);
+        (options(*this), ...);
+    }
+
+    constexpr explicit in(ByteView & view, Options && ... options) : m_data(view)
+    {
+        (options(*this), ...);
+    }
+
+    ZPP_BITS_INLINE constexpr auto operator()(auto &&... items)
+    {
+        return serialize_many(items...);
+    }
+
+    constexpr decltype(auto) data()
+    {
+        return m_data;
+    }
+
+    constexpr std::size_t position() const
+    {
+        return m_position;
+    }
+
+    constexpr std::size_t & position()
+    {
+        return m_position;
+    }
+
+    constexpr auto remaining_data()
+    {
+        return std::span<byte_type>{m_data.data() + m_position,
+                                    m_data.size() - m_position};
+    }
+
+    constexpr auto processed_data()
+    {
+        return std::span<byte_type>{m_data.data(), m_position};
+    }
+
+    constexpr void reset(std::size_t position = 0)
+    {
+        m_position = position;
+    }
+
+    constexpr static auto kind()
+    {
+        return kind::in;
+    }
+
+    constexpr static bool resizable = requires(ByteView view)
+    {
+        view.resize(1);
+    };
+
+    using view_type =
+        std::conditional_t<resizable,
+                           ByteView &,
+                           std::remove_cvref_t<decltype(
+                               std::span{std::declval<ByteView &>()})>>;
+
+private:
+    ZPP_BITS_INLINE constexpr errc serialize_many(auto && first_item,
+                                                  auto &&... items)
+    {
+        if (auto result = serialize_one(first_item); failure(result))
+            [[unlikely]] {
+            return result;
+        }
+
+        return serialize_many(items...);
+    }
+
+    ZPP_BITS_INLINE constexpr errc serialize_many()
+    {
+        return {};
+    }
+
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::unspecialized auto && item)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        static_assert(!std::is_pointer_v<type>);
+
+        if constexpr (requires { type::serialize(*this, item); }) {
+            return type::serialize(*this, item);
+        } else if constexpr (requires { serialize(*this, item); }) {
+            return serialize(*this, item);
+        } else if constexpr (std::is_fundamental_v<type> || std::is_enum_v<type>) {
+            auto size = m_data.size();
+            if (sizeof(item) > size - m_position) [[unlikely]] {
+                return std::errc::result_out_of_range;
+            }
+            if (std::is_constant_evaluated()) {
+                std::array<std::remove_const_t<byte_type>, sizeof(item)>
+                    value;
+                for (std::size_t i = 0; i < sizeof(value); ++i) {
+                    if constexpr (endian_aware) {
+                        value[sizeof(value) - 1 - i] =
+                            byte_type(m_data[m_position + i]);
+                    } else {
+                        value[i] = byte_type(m_data[m_position + i]);
+                    }
+                }
+                item = std::bit_cast<type>(value);
+            } else {
+                if constexpr (endian_aware) {
+                    auto begin = m_data.data() + m_position;
+                    std::reverse_copy(
+                        begin,
+                        begin + sizeof(item),
+                        reinterpret_cast<std::remove_const_t<byte_type> *>(
+                            &item));
+                } else {
+                    std::memcpy(
+                        &item, m_data.data() + m_position, sizeof(item));
+                }
+            }
+            m_position += sizeof(item);
+            return {};
+        } else if constexpr (requires {
+                                 requires std::same_as<
+                                     bytes<typename type::value_type>,
+                                     type>;
+                             }) {
+            static_assert(
+                !endian_aware ||
+                concepts::byte_type<
+                    std::remove_cvref_t<decltype(*item.data())>>);
+
+            auto size = m_data.size();
+            auto item_size_in_bytes = item.size_in_bytes();
+            if (!item_size_in_bytes) [[unlikely]] {
+                return {};
+            }
+
+            if (item_size_in_bytes > size - m_position) [[unlikely]] {
+                return std::errc::result_out_of_range;
+            }
+            if (std::is_constant_evaluated()) {
+                std::size_t count = item.count();
+                for (std::size_t index = 0; index < count; ++index) {
+                    std::array<std::remove_const_t<byte_type>,
+                               sizeof(typename type::value_type)>
+                        value;
+                    for (std::size_t i = 0;
+                         i < sizeof(typename type::value_type);
+                         ++i) {
+                        value[i] = byte_type(
+                            m_data[m_position +
+                                   index *
+                                       sizeof(typename type::value_type) +
+                                   i]);
+                    }
+                    item.data()[index] =
+                        std::bit_cast<typename type::value_type>(value);
+                }
+            } else {
+                std::memcpy(item.data(),
+                            m_data.data() + m_position,
+                            item_size_in_bytes);
+            }
+            m_position += item_size_in_bytes;
+            return {};
+        } else if constexpr (concepts::empty<type>) {
+            return {};
+        } else if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                          type>) {
+            return serialize_one(as_bytes(item));
+        } else if constexpr (concepts::self_referencing<type>) {
+            return visit_members(
+                item,
+                [&](auto &&... items) constexpr {
+                    return serialize_many(items...);
+                });
+        } else {
+            return visit_members(
+                item,
+                [&](auto &&... items) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    return serialize_many(items...);
+                });
+        }
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::array auto && array)
+    {
+        using value_type = std::remove_cvref_t<decltype(array[0])>;
+
+        if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                   value_type>) {
+            return serialize_one(bytes(array));
+        } else {
+            for (auto & item : array) {
+                if (auto result = serialize_one(item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        }
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::container auto && container)
+    {
+        using type = std::remove_cvref_t<decltype(container)>;
+        using value_type = typename type::value_type;
+        constexpr auto is_const = std::is_const_v<
+            std::remove_reference_t<decltype(container[0])>>;
+
+        if constexpr (!std::is_void_v<SizeType> &&
+                      (requires(type container) { container.resize(1); } ||
+                       (
+                           requires(type container) {
+                               container = {container.data(), 1};
+                           } &&
+                           !requires {
+                               requires(type::extent !=
+                                        std::dynamic_extent);
+                               requires concepts::has_fixed_nonzero_size<
+                                   type>;
+                           }))) {
+            SizeType size{};
+            if (auto result = serialize_one(size); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+
+            if constexpr (requires(type container) {
+                              container.resize(size);
+                          }) {
+                if constexpr (allocation_limit !=
+                              std::numeric_limits<std::size_t>::max()) {
+                    constexpr auto limit =
+                        allocation_limit / sizeof(value_type);
+                    if (size > limit) [[unlikely]] {
+                        return std::errc::message_size;
+                    }
+                }
+                container.resize(size);
+            } else if constexpr (is_const &&
+                                 (std::same_as<std::byte, value_type> ||
+                                  std::same_as<char, value_type> ||
+                                  std::same_as<unsigned char,
+                                               value_type>)) {
+                if (size > m_data.size() - m_position) [[unlikely]] {
+                    return std::errc::result_out_of_range;
+                }
+                container = {m_data.data() + m_position, size};
+                m_position += size;
+            } else {
+                if (size > container.size()) [[unlikely]] {
+                    return std::errc::result_out_of_range;
+                }
+                container = {container.data(), size};
+            }
+
+            if constexpr (
+                concepts::serialize_as_bytes<decltype(*this),
+                                             value_type> &&
+                std::is_base_of_v<
+                    std::random_access_iterator_tag,
+                    typename std::iterator_traits<
+                        typename type::iterator>::iterator_category> &&
+                requires { container.data(); } &&
+                !(is_const &&
+                  (std::same_as<std::byte, value_type> ||
+                   std::same_as<char, value_type> ||
+                   std::same_as<unsigned char,
+                                value_type>)&&requires(type container) {
+                      container = {m_data.data(), 1};
+                  })) {
+                return serialize_one(bytes(container, size));
+            }
+        }
+
+        if constexpr (concepts::serialize_as_bytes<decltype(*this),
+                                                   value_type> &&
+                      std::is_base_of_v<std::random_access_iterator_tag,
+                                        typename std::iterator_traits<
+                                            typename type::iterator>::
+                                            iterator_category> &&
+                      requires { container.data(); }) {
+            if constexpr (is_const &&
+                          (std::same_as<std::byte, value_type> ||
+                           std::same_as<char, value_type> ||
+                           std::same_as<
+                               unsigned char,
+                               value_type>)&&requires(type container) {
+                              container = {m_data.data(), 1};
+                          }) {
+                if constexpr (requires {
+                                  requires(type::extent !=
+                                           std::dynamic_extent);
+                                  requires concepts::has_fixed_nonzero_size<type>;
+                              }) {
+                    if (type::extent > m_data.size() - m_position)
+                        [[unlikely]] {
+                        return std::errc::result_out_of_range;
+                    }
+                    container = {m_data.data() + m_position, type::extent};
+                    m_position += type::extent;
+                } else if constexpr (std::is_void_v<SizeType>) {
+                    auto size = m_data.size();
+                    container = {m_data.data() + m_position,
+                                 size - m_position};
+                    m_position = size;
+                }
+                return {};
+            } else {
+                return serialize_one(bytes(container));
+            }
+        } else {
+            for (auto & item : container) {
+                if (auto result = serialize_one(item); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        }
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::associative_container auto && container)
+    {
+        using type = typename std::remove_cvref_t<decltype(container)>;
+
+        SizeType size{};
+
+        if constexpr (!std::is_void_v<SizeType>) {
+            if (auto result = serialize_one(size); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+        } else {
+            size = container.size();
+        }
+
+        container.clear();
+
+        for (std::size_t index{}; index < size; ++index)
+        {
+            if constexpr (requires { typename type::mapped_type; }) {
+                using value_type = std::pair<typename type::key_type,
+                                             typename type::mapped_type>;
+                std::aligned_storage_t<sizeof(value_type),
+                                       alignof(value_type)>
+                    storage;
+
+                auto object = access::placement_new<value_type>(
+                    std::addressof(storage));
+                destructor_guard guard{*object};
+                if (auto result = serialize_one(*object); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+
+                container.insert(std::move(*object));
+            } else {
+                using value_type = typename type::value_type;
+
+                std::aligned_storage_t<sizeof(value_type),
+                                       alignof(value_type)>
+                    storage;
+
+                auto object = access::placement_new<value_type>(
+                    std::addressof(storage));
+                destructor_guard guard{*object};
+                if (auto result = serialize_one(*object); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+
+                container.insert(std::move(*object));
+            }
+        }
+
+        return {};
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::tuple auto && tuple)
+    {
+        return serialize_one(tuple,
+                             std::make_index_sequence<std::tuple_size_v<
+                                 std::remove_cvref_t<decltype(tuple)>>>());
+    }
+
+    template <std::size_t... Indices>
+    ZPP_BITS_INLINE constexpr errc serialize_one(
+        concepts::tuple auto && tuple, std::index_sequence<Indices...>)
+    {
+        return serialize_many(std::get<Indices>(tuple)...);
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::optional auto && optional)
+    {
+        using value_type = std::remove_reference_t<decltype(*optional)>;
+
+        std::byte has_value{};
+        if (auto result = serialize_one(has_value); failure(result))
+            [[unlikely]] {
+            return result;
+        }
+
+        if (!bool(has_value)) [[unlikely]] {
+            optional = std::nullopt;
+            return {};
+        }
+
+        if constexpr (std::is_default_constructible_v<value_type>) {
+            if (!optional) {
+                optional = value_type{};
+            }
+
+            if (auto result = serialize_one(*optional); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+        } else {
+            std::aligned_storage_t<sizeof(value_type), alignof(value_type)>
+                storage;
+
+            auto object =
+                access::placement_new<value_type>(std::addressof(storage));
+            destructor_guard guard{*object};
+
+            if (auto result = serialize_one(*object); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+
+            optional = std::move(*object);
+        }
+
+        return {};
+    }
+
+    template <typename KnownId = void,
+              typename... Types,
+              template <typename...>
+              typename Variant>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(Variant<Types...> & variant) requires
+        concepts::variant<Variant<Types...>>
+    {
+        using type = std::remove_cvref_t<decltype(variant)>;
+
+        if constexpr (!std::is_void_v<KnownId>) {
+            constexpr auto index =
+                traits::variant<type>::template index<KnownId::value>();
+
+            using element_type =
+                std::remove_reference_t<decltype(std::get<index>(
+                    variant))>;
+
+            if constexpr (std::is_default_constructible_v<element_type>) {
+                if (variant.index() !=
+                    traits::variant<type>::template index_by_type<
+                        element_type>()) {
+                    variant = element_type{};
+                }
+                return serialize_one(*std::get_if<element_type>(&variant));
+            } else {
+                std::aligned_storage_t<sizeof(element_type),
+                                       alignof(element_type)>
+                    storage;
+
+                auto object = access::placement_new<element_type>(
+                    std::addressof(storage));
+                destructor_guard guard{*object};
+
+                if (auto result = serialize_one(*object); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+                variant = std::move(*object);
+            }
+        } else {
+            typename traits::variant<type>::id_type id;
+            if (auto result = serialize_one(id); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+
+            return serialize_one(variant, id);
+        }
+    }
+
+    template <typename... Types, template <typename...> typename Variant>
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(Variant<Types...> & variant,
+                  auto && id) requires concepts::variant<Variant<Types...>>
+    {
+        using type = std::remove_cvref_t<decltype(variant)>;
+
+        auto index = traits::variant<type>::index(id);
+        if (index > sizeof...(Types)) [[unlikely]] {
+            return std::errc::bad_message;
+        }
+
+        constexpr std::tuple loaders{
+            [](auto & self,
+               auto & variant) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                if constexpr (std::is_default_constructible_v<Types>) {
+                    if (variant.index() !=
+                        traits::variant<type>::template index_by_type<
+                            Types>()) {
+                        variant = Types{};
+                    }
+                    return self.serialize_one(
+                        *std::get_if<Types>(&variant));
+                } else {
+                    std::aligned_storage_t<sizeof(Types), alignof(Types)>
+                        storage;
+
+                    auto object = access::placement_new<Types>(
+                        std::addressof(storage));
+                    destructor_guard guard{*object};
+
+                    if (auto result = self.serialize_one(*object);
+                        failure(result)) [[unlikely]] {
+                        return result;
+                    }
+                    variant = std::move(*object);
+                }
+            }...};
+
+        return traits::tuple<std::remove_cvref_t<decltype(loaders)>>::
+            visit(loaders, index, [&](auto && loader) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                return loader(*this, variant);
+            });
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::owning_pointer auto && pointer)
+    {
+        using type = std::remove_reference_t<decltype(*pointer)>;
+
+        auto loaded = access::make_unique<type>();;
+        if (auto result = serialize_one(*loaded); failure(result))
+            [[unlikely]] {
+            return result;
+        }
+
+        pointer.reset(loaded.release());
+        return {};
+    }
+
+    ZPP_BITS_INLINE constexpr errc
+    serialize_one(concepts::bitset auto && bitset)
+    {
+        constexpr auto size = std::remove_cvref_t<decltype(bitset)>{}.size();
+        constexpr auto size_in_bytes = (size + (CHAR_BIT - 1)) / CHAR_BIT;
+
+        if (size_in_bytes > m_data.size() - m_position)
+            [[unlikely]] {
+            return std::errc::result_out_of_range;
+        }
+
+        auto data = m_data.data() + m_position;
+        for (std::size_t i = 0; i < size; ++i) {
+            bitset[i] = (static_cast<unsigned char>(data[i / CHAR_BIT]) >>
+                         (i & 0x7)) &
+                        0x1;
+        }
+
+        m_position += size_in_bytes;
+        return {};
+    }
+
+    template <typename SizeType = default_size_type>
+    ZPP_BITS_INLINE constexpr errc serialize_one(concepts::by_protocol auto && item)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        if constexpr (!std::is_void_v<SizeType>) {
+            SizeType size{};
+            if (auto result = serialize_one(size); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+
+            if constexpr (requires {typename type::serialize;}) {
+                constexpr auto protocol = type::serialize::value;
+                return protocol(*this, item, size);
+            } else {
+                constexpr auto protocol = decltype(serialize(item))::value;
+                return protocol(*this, item, size);
+            }
+        } else {
+            if constexpr (requires {typename type::serialize;}) {
+                constexpr auto protocol = type::serialize::value;
+                return protocol(*this, item);
+            } else {
+                constexpr auto protocol = decltype(serialize(item))::value;
+                return protocol(*this, item);
+            }
+        }
+    }
+
+    view_type m_data{};
+    std::size_t m_position{};
+};
+
+template <typename Type, std::size_t Size, typename... Options>
+in(Type (&)[Size], Options && ...) -> in<std::span<Type, Size>, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+in(sized_item<Type, SizeType> &, Options && ...)
+    -> in<Type, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+in(const sized_item<Type, SizeType> &, Options && ...)
+    -> in<const Type, Options...>;
+
+template <typename Type, typename SizeType, typename... Options>
+in(sized_item<Type, SizeType> &&, Options && ...)
+    -> in<Type, Options...>;
+
+constexpr auto input(auto && view, auto &&... option)
+{
+    return in(std::forward<decltype(view)>(view),
+              std::forward<decltype(option)>(option)...);
+}
+
+constexpr auto output(auto && view, auto &&... option)
+{
+    return out(std::forward<decltype(view)>(view),
+               std::forward<decltype(option)>(option)...);
+}
+
+constexpr auto in_out(auto && view, auto &&... option)
+{
+    return std::tuple{
+        in<std::remove_reference_t<typename decltype(in{view})::view_type>,
+           decltype(option) &...>(view, option...),
+        out(std::forward<decltype(view)>(view),
+            std::forward<decltype(option)>(option)...)};
+}
+
+template <typename ByteType = std::byte>
+constexpr auto data_in_out(auto &&... option)
+{
+    struct data_in_out
+    {
+        data_in_out(decltype(option) &&... option) :
+            input(data, option...),
+            output(data, std::forward<decltype(option)>(option)...)
+        {
+        }
+
+        std::vector<ByteType> data;
+        in<decltype(data), decltype(option) &...> input;
+        out<decltype(data), decltype(option)...> output;
+    };
+    return data_in_out{std::forward<decltype(option)>(option)...};
+}
+
+template <typename ByteType = std::byte>
+constexpr auto data_in(auto &&... option)
+{
+    struct data_in
+    {
+        data_in(decltype(option) &&... option) :
+            input(data, std::forward<decltype(option)>(option)...)
+        {
+        }
+
+        std::vector<ByteType> data;
+        in<decltype(data), decltype(option)...> input;
+    };
+    return data_in{std::forward<decltype(option)>(option)...};
+}
+
+template <typename ByteType = std::byte>
+constexpr auto data_out(auto &&... option)
+{
+    struct data_out
+    {
+        data_out(decltype(option) &&... option) :
+            output(data, std::forward<decltype(option)>(option)...)
+        {
+        }
+
+        std::vector<ByteType> data;
+        out<decltype(data), decltype(option)...> output;
+    };
+    return data_out{std::forward<decltype(option)>(option)...};
+}
+
+template <auto Object, std::size_t MaxSize = 0x1000>
+constexpr auto to_bytes_one()
+{
+    constexpr auto size = [] {
+        std::array<std::byte, MaxSize> data;
+        out out{data};
+        out(Object).or_throw();
+        return out.position();
+    }();
+
+    if constexpr (!size) {
+        return string_literal<std::byte, 0>{};
+    } else {
+        std::array<std::byte, size> data;
+        out{data}(Object).or_throw();
+        return data;
+    }
+}
+
+template <auto... Data>
+constexpr auto join()
+{
+    constexpr auto size = (0 + ... + Data.size());
+    if constexpr (!size) {
+        return string_literal<std::byte, 0>{};
+    } else {
+        std::array<std::byte, size> data;
+        out{data}(Data...).or_throw();
+        return data;
+    }
+}
+
+template <auto Left, auto Right = -1>
+constexpr auto slice(auto array)
+{
+    constexpr auto left = Left;
+    constexpr auto right = (-1 == Right) ? array.size() : Right;
+    constexpr auto size = right - left;
+    static_assert(Left < Right || -1 == Right);
+
+    std::array<std::remove_reference_t<decltype(array[0])>, size> sliced;
+    std::copy(std::begin(array) + left,
+              std::begin(array) + right,
+              std::begin(sliced));
+    return sliced;
+}
+
+template <auto... Object>
+constexpr auto to_bytes()
+{
+    return join<to_bytes_one<Object>()...>();
+}
+
+template <auto Data, typename Type>
+constexpr auto from_bytes()
+{
+    Type object;
+    in{Data}(object).or_throw();
+    return object;
+}
+
+template <auto Data, typename... Types>
+constexpr auto from_bytes() requires (sizeof...(Types) > 1)
+{
+    std::tuple<Types...> object;
+    in{Data}(object).or_throw();
+    return object;
+}
+
+template <auto Id, auto MaxSize = -1>
+constexpr auto serialize_id()
+{
+    constexpr auto serialized_id = slice<0, MaxSize>(to_bytes<Id>());
+    if constexpr (sizeof(serialized_id) == 1) {
+        return serialization_id<from_bytes<serialized_id, std::byte>()>{};
+    } else if constexpr (sizeof(serialized_id) == 2) {
+        return serialization_id<from_bytes<serialized_id, std::uint16_t>()>{};
+    } else if constexpr (sizeof(serialized_id) == 4) {
+        return serialization_id<from_bytes<serialized_id, std::uint32_t>()>{};
+    } else if constexpr (sizeof(serialized_id) == 8) {
+        return serialization_id<from_bytes<serialized_id, std::uint64_t>()>{};
+    } else {
+        return serialization_id<serialized_id>{};
+    }
+}
+
+template <auto Id, auto MaxSize = -1>
+using id = decltype(serialize_id<Id, MaxSize>());
+
+template <auto Id, auto MaxSize = -1>
+constexpr auto id_v = id<Id, MaxSize>::value;
+
+template <typename Id, concepts::variant Variant>
+struct known_id_variant
+{
+    constexpr explicit known_id_variant(Variant & variant) :
+        variant(variant)
+    {
+    }
+
+    ZPP_BITS_INLINE constexpr static auto serialize(auto & serializer,
+                                                    auto & self)
+    {
+        return serializer.template serialize_one<Id>(self.variant);
+    }
+
+    Variant & variant;
+};
+
+template <auto Id, auto MaxSize = -1, typename Variant>
+constexpr auto known_id(Variant && variant)
+{
+    return known_id_variant<id<Id, MaxSize>,
+                            std::remove_reference_t<Variant>>(variant);
+}
+
+template <typename Id, concepts::variant Variant>
+struct known_dynamic_id_variant
+{
+    using id_type =
+        std::conditional_t<std::is_integral_v<std::remove_cvref_t<Id>> ||
+                               std::is_enum_v<std::remove_cvref_t<Id>>,
+                           std::remove_cvref_t<Id>,
+                           Id &>;
+
+    constexpr explicit known_dynamic_id_variant(Variant & variant, id_type id) :
+        variant(variant),
+        id(id)
+    {
+    }
+
+    ZPP_BITS_INLINE constexpr static auto serialize(auto & serializer,
+                                                    auto & self)
+    {
+        return serializer.template serialize_one(self.variant, self.id);
+    }
+
+    Variant & variant;
+    id_type id;
+};
+
+template <typename Id, typename Variant>
+constexpr auto known_id(Id && id, Variant && variant)
+{
+    return known_dynamic_id_variant<Id, std::remove_reference_t<Variant>>(
+        variant, id);
+}
+
+template <typename Function>
+struct function_traits;
+
+template <typename Return, typename... Arguments>
+struct function_traits<Return(*)(Arguments...)>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename Return, typename... Arguments>
+struct function_traits<Return(*)(Arguments...) noexcept>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename This, typename Return, typename... Arguments>
+struct function_traits<Return(This::*)(Arguments...)>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename This, typename Return, typename... Arguments>
+struct function_traits<Return(This::*)(Arguments...) noexcept>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename This, typename Return, typename... Arguments>
+struct function_traits<Return(This::*)(Arguments...) const>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename This, typename Return, typename... Arguments>
+struct function_traits<Return(This::*)(Arguments...) const noexcept>
+{
+    using parameters_type = std::tuple<std::remove_cvref_t<Arguments>...>;
+    using return_type = Return;
+};
+
+template <typename Return>
+struct function_traits<Return(*)()>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename Return>
+struct function_traits<Return(*)() noexcept>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename This, typename Return>
+struct function_traits<Return(This::*)()>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename This, typename Return>
+struct function_traits<Return(This::*)() noexcept>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename This, typename Return>
+struct function_traits<Return(This::*)() const>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename This, typename Return>
+struct function_traits<Return(This::*)() const noexcept>
+{
+    using parameters_type = void;
+    using return_type = Return;
+};
+
+template <typename Function>
+using function_parameters_t =
+    typename function_traits<std::remove_cvref_t<Function>>::parameters_type;
+
+template <typename Function>
+using function_return_type_t =
+    typename function_traits<std::remove_cvref_t<Function>>::return_type;
+
+constexpr auto success(auto && value_or_errc) requires
+    std::same_as<decltype(value_or_errc.error()), errc>
+{
+    return value_or_errc.success();
+}
+
+constexpr auto failure(auto && value_or_errc) requires
+    std::same_as<decltype(value_or_errc.error()), errc>
+{
+    return value_or_errc.failure();
+}
+
+template <typename Type>
+struct [[nodiscard]] value_or_errc
+{
+    using error_type = errc;
+    using value_type = std::conditional_t<
+        std::is_void_v<Type>,
+        std::nullptr_t,
+        std::conditional_t<
+            std::is_reference_v<Type>,
+            std::add_pointer_t<std::remove_reference_t<Type>>,
+            Type>>;
+
+    constexpr value_or_errc() = default;
+
+    constexpr explicit value_or_errc(auto && value) :
+        m_return_value(std::forward<decltype(value)>(value))
+    {
+    }
+
+    constexpr explicit value_or_errc(error_type error) :
+        m_error(std::forward<decltype(error)>(error))
+    {
+    }
+
+    constexpr value_or_errc(value_or_errc && other) noexcept
+    {
+        if (other.is_value()) {
+            if constexpr (!std::is_void_v<Type>) {
+                if constexpr (!std::is_reference_v<Type>) {
+                    ::new (std::addressof(m_return_value))
+                        Type(std::move(other.m_return_value));
+                } else {
+                    m_return_value = other.m_return_value;
+                }
+            }
+        } else {
+            m_failure = other.m_failure;
+            std::memcpy(&m_error, &other.m_error, sizeof(m_error));
+        }
+    }
+
+    constexpr ~value_or_errc()
+    {
+        if constexpr (!std::is_void_v<Type> &&
+                      !std::is_trivially_destructible_v<Type>) {
+            if (success()) {
+                m_return_value.~Type();
+            }
+        }
+    }
+
+    constexpr bool success() const noexcept
+    {
+        return !m_failure;
+    }
+
+    constexpr bool failure() const noexcept
+    {
+        return m_failure;
+    }
+
+    constexpr decltype(auto) value() & noexcept
+    {
+        if constexpr (std::is_same_v<Type, decltype(m_return_value)>) {
+            return (m_return_value);
+        } else {
+            return (*m_return_value);
+        }
+    }
+
+    constexpr decltype(auto) value() && noexcept
+    {
+        if constexpr (std::is_same_v<Type, decltype(m_return_value)>) {
+            return std::forward<Type>(m_return_value);
+        } else {
+            return std::forward<Type>(*m_return_value);
+        }
+    }
+
+    constexpr decltype(auto) value() const & noexcept
+    {
+        if constexpr (std::is_same_v<Type, decltype(m_return_value)>) {
+            return (m_return_value);
+        } else {
+            return (*m_return_value);
+        }
+    }
+
+    constexpr auto error() const noexcept
+    {
+        return m_error;
+    }
+
+    #if __has_include("zpp_throwing.h")
+    constexpr zpp::throwing<Type> operator co_await() &&
+    {
+        if (failure()) [[unlikely]] {
+            return error().code;
+        }
+        return std::move(*this).value();
+    }
+
+    constexpr zpp::throwing<Type> operator co_await() const &
+    {
+        if (failure()) [[unlikely]] {
+            return error().code;
+        }
+        return value();
+    }
+#endif
+
+    constexpr decltype(auto) or_throw() &
+    {
+        if (failure()) [[unlikely]] {
+#ifdef __cpp_exceptions
+            throw std::system_error(std::make_error_code(error().code));
+#else
+            std::abort();
+#endif
+        }
+        return value();
+    }
+
+    constexpr decltype(auto) or_throw() &&
+    {
+        if (failure()) [[unlikely]] {
+#ifdef __cpp_exceptions
+            throw std::system_error(std::make_error_code(error().code));
+#else
+            std::abort();
+#endif
+        }
+        return std::move(*this).value();
+    }
+
+    constexpr decltype(auto) or_throw() const &
+    {
+        if (failure()) [[unlikely]] {
+#ifdef __cpp_exceptions
+            throw std::system_error(std::make_error_code(error().code));
+#else
+            std::abort();
+#endif
+        }
+        return value();
+    }
+
+    union
+    {
+        error_type m_error{};
+        value_type m_return_value;
+    };
+    bool m_failure{};
+};
+
+ZPP_BITS_INLINE constexpr auto
+apply(auto && function, auto && archive) requires(
+    std::remove_cvref_t<decltype(archive)>::kind() == kind::in)
+{
+    using function_type = std::decay_t<decltype(function)>;
+
+    if constexpr (requires { &function_type::operator(); }) {
+        using parameters_type =
+            function_parameters_t<decltype(&function_type::operator())>;
+        using return_type =
+            function_return_type_t<decltype(&function_type::operator())>;
+        if constexpr (std::is_void_v<parameters_type>) {
+            return std::forward<decltype(function)>(function)();
+        } else {
+            parameters_type parameters;
+            if constexpr (std::is_void_v<return_type>) {
+                if (auto result = archive(parameters); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+                std::apply(std::forward<decltype(function)>(function),
+                           std::move(parameters));
+                return errc{};
+            } else {
+                if (auto result = archive(parameters); failure(result))
+                    [[unlikely]] {
+                    return value_or_errc<return_type>{result};
+                }
+                return value_or_errc<return_type>{
+                    std::apply(std::forward<decltype(function)>(function),
+                               std::move(parameters))};
+            }
+        }
+    } else {
+        using parameters_type = function_parameters_t<function_type>;
+        using return_type = function_return_type_t<function_type>;
+        if constexpr (std::is_void_v<parameters_type>) {
+            return std::forward<decltype(function)>(function)();
+        } else {
+            parameters_type parameters;
+            if constexpr (std::is_void_v<return_type>) {
+                if (auto result = archive(parameters); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+                std::apply(std::forward<decltype(function)>(function),
+                           std::move(parameters));
+                return errc{};
+            } else {
+                if (auto result = archive(parameters); failure(result))
+                    [[unlikely]] {
+                    return value_or_errc<return_type>{result};
+                }
+                return value_or_errc<return_type>{
+                    std::apply(std::forward<decltype(function)>(function),
+                               std::move(parameters))};
+            }
+        }
+    }
+}
+
+ZPP_BITS_INLINE constexpr auto
+apply(auto && self, auto && function, auto && archive) requires(
+    std::remove_cvref_t<decltype(archive)>::kind() == kind::in)
+{
+    using parameters_type = function_parameters_t<
+        std::remove_cvref_t<decltype(function)>>;
+    using return_type = function_return_type_t<
+        std::remove_cvref_t<decltype(function)>>;
+    if constexpr (std::is_void_v<parameters_type>) {
+        return (std::forward<decltype(self)>(self).*
+                std::forward<decltype(function)>(function))();
+    } else {
+        parameters_type parameters;
+        if constexpr (std::is_void_v<return_type>) {
+            if (auto result = archive(parameters); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+            // Ignore GCC issue.
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+            std::apply(
+                [&](auto &&... arguments) -> decltype(auto) {
+                    return (std::forward<decltype(self)>(self).*
+                            std::forward<decltype(function)>(function))(
+                        std::forward<decltype(arguments)>(arguments)...);
+                },
+                std::move(parameters));
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+            return errc{};
+        } else {
+            if (auto result = archive(parameters); failure(result))
+                [[unlikely]] {
+                return value_or_errc<return_type>{result};
+            }
+            return value_or_errc<return_type>(std::apply(
+                [&](auto &&... arguments) -> decltype(auto) {
+            // Ignore GCC issue.
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+                    return (std::forward<decltype(self)>(self).*
+                            std::forward<decltype(function)>(function))(
+                        std::forward<decltype(arguments)>(arguments)...);
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+                },
+                std::move(parameters)));
+        }
+    }
+}
+
+template <auto Function, auto Id, auto MaxSize = -1>
+struct bind
+{
+    using id = zpp::bits::id<Id, MaxSize>;
+    using function_type = decltype(Function);
+    using parameters_type =
+        typename function_traits<function_type>::parameters_type;
+    using return_type =
+        typename function_traits<function_type>::return_type;
+    static constexpr auto opaque = false;
+
+    ZPP_BITS_INLINE constexpr static decltype(auto) call(auto && archive,
+                                                         auto && context)
+    {
+        if constexpr (std::is_member_function_pointer_v<
+                          std::remove_cvref_t<decltype(Function)>>) {
+            return apply(context, Function, archive);
+        } else {
+            return apply(Function, archive);
+        }
+    }
+};
+
+template <auto Function, auto Id, auto MaxSize = -1>
+struct bind_opaque
+{
+    using id = zpp::bits::id<Id, MaxSize>;
+    using function_type = decltype(Function);
+    using parameters_type =
+        typename function_traits<function_type>::parameters_type;
+    using return_type =
+        typename function_traits<function_type>::return_type;
+    static constexpr auto opaque = true;
+
+    ZPP_BITS_INLINE constexpr static decltype(auto) call(auto && in,
+                                                         auto && out,
+                                                         auto && context)
+    {
+        if constexpr (std::is_member_function_pointer_v<
+                          std::remove_cvref_t<decltype(Function)>>) {
+            if constexpr (requires { (context.*Function)(in, out); }) {
+                return (context.*Function)(in, out);
+            } else if constexpr (requires { (context.*Function)(in); }) {
+                return (context.*Function)(in);
+            } else if constexpr (requires { (context.*Function)(out); }) {
+                return (context.*Function)(out);
+            } else if constexpr (
+                requires(decltype(in.remaining_data()) & data) {
+                    (context.*Function)(data);
+                }) {
+                struct _
+                {
+                    decltype(in) archive;
+                    decltype(in.remaining_data()) data;
+                    constexpr ~_()
+                    {
+                        archive.position() += data.size();
+                    }
+                } _{in, in.remaining_data()};
+                return (context.*Function)(_.data);
+            } else {
+                return (context.*Function)();
+            }
+        } else {
+            if constexpr (requires { Function(in, out); }) {
+                return Function(in, out);
+            } else if constexpr (requires { Function(in); }) {
+                return Function(in);
+            } else if constexpr (requires { Function(out); }) {
+                return Function(out);
+            } else if constexpr (
+                requires(decltype(in.remaining_data()) & data) {
+                    Function(data);
+                }) {
+                struct _
+                {
+                    decltype(in) archive;
+                    decltype(in.remaining_data()) data;
+                    constexpr ~_()
+                    {
+                        archive.position() += data.size();
+                    }
+                } _{in, in.remaining_data()};
+                return Function(_.data);
+            } else {
+                return Function();
+            }
+        }
+    }
+};
+
+template <typename... Bindings>
+struct rpc_impl
+{
+    using id = std::remove_cvref_t<
+        decltype(std::remove_cvref_t<decltype(get<0>(
+                     std::tuple<Bindings...>{}))>::id::value)>;
+
+    template <typename In, typename Out>
+    struct client
+    {
+        constexpr client(In && in, Out && out) :
+            in(in),
+            out(out)
+        {
+        }
+
+        constexpr client(client && other) = default;
+
+        constexpr ~client()
+        {
+            static_assert(std::remove_cvref_t<decltype(in)>::kind() == kind::in);
+            static_assert(std::remove_cvref_t<decltype(out)>::kind() == kind::out);
+        }
+
+        template <typename Id, typename FirstBinding, typename... OtherBindings>
+        constexpr auto binding()
+        {
+            if constexpr (std::same_as<Id, typename FirstBinding::id>) {
+                return FirstBinding{};
+            } else {
+                static_assert(sizeof...(OtherBindings));
+                return binding<Id, OtherBindings...>();
+            }
+        }
+
+        template <typename Id, std::size_t... Indices>
+        constexpr auto request(std::index_sequence<Indices...>,
+                               auto &&... arguments)
+        {
+            using request_binding = decltype(binding<Id, Bindings...>());
+            using parameters_type =
+                typename request_binding::parameters_type;
+
+            if constexpr (std::is_void_v<parameters_type>) {
+                static_assert(!sizeof...(arguments));
+                return out(Id::value);
+            } else if constexpr (request_binding::opaque) {
+                return out(Id::value, arguments...);;
+            } else if constexpr (std::same_as<
+                                     std::tuple<std::remove_cvref_t<
+                                         decltype(arguments)>...>,
+                                     parameters_type>
+
+            ) {
+                return out(Id::value, arguments...);
+            } else {
+                static_assert(requires {
+                    {parameters_type{
+                        std::forward_as_tuple<decltype(arguments)...>(
+                            arguments...)}};
+                });
+
+                return out(
+                    Id::value,
+                    static_cast<std::conditional_t<
+                        std::is_fundamental_v<
+                            std::remove_cvref_t<decltype(get<Indices>(
+                                std::declval<parameters_type>()))>> ||
+                            std::is_enum_v<
+                                std::remove_cvref_t<decltype(get<Indices>(
+                                    std::declval<parameters_type>()))>>,
+                        std::remove_cvref_t<decltype(get<Indices>(
+                            std::declval<parameters_type>()))>,
+                        const decltype(get<Indices>(
+                            std::declval<parameters_type>())) &>>(
+                        arguments)...);
+            }
+        }
+
+        template <typename Id>
+        constexpr auto request(auto &&... arguments)
+        {
+            return request<Id>(
+                std::make_index_sequence<sizeof...(arguments)>{},
+                arguments...);
+        }
+
+        template <auto Id, auto MaxSize = -1>
+        constexpr auto request(auto &&... arguments)
+        {
+            return request<zpp::bits::id<Id, MaxSize>>(arguments...);
+        }
+
+        template <typename Id, std::size_t... Indices>
+        constexpr auto request_body(std::index_sequence<Indices...>,
+                                    auto &&... arguments)
+        {
+            using request_binding = decltype(binding<Id, Bindings...>());
+            using parameters_type =
+                typename request_binding::parameters_type;
+
+            if constexpr (std::is_void_v<parameters_type>) {
+                static_assert(!sizeof...(arguments));
+                return;
+            } else if constexpr (request_binding::opaque) {
+                return out(arguments...);;
+            } else if constexpr (std::same_as<
+                                     std::tuple<std::remove_cvref_t<
+                                         decltype(arguments)>...>,
+                                     parameters_type>
+
+            ) {
+                return out(arguments...);
+            } else {
+                static_assert(requires {
+                    {parameters_type{
+                        std::forward_as_tuple<decltype(arguments)...>(
+                            arguments...)}};
+                });
+
+                return out(
+                    static_cast<std::conditional_t<
+                        std::is_fundamental_v<
+                            std::remove_cvref_t<decltype(get<Indices>(
+                                std::declval<parameters_type>()))>> ||
+                            std::is_enum_v<
+                                std::remove_cvref_t<decltype(get<Indices>(
+                                    std::declval<parameters_type>()))>>,
+                        std::remove_cvref_t<decltype(get<Indices>(
+                            std::declval<parameters_type>()))>,
+                        const decltype(get<Indices>(
+                            std::declval<parameters_type>())) &>>(
+                        arguments)...);
+            }
+        }
+
+        template <typename Id>
+        constexpr auto request_body(auto &&... arguments)
+        {
+            return request_body<Id>(
+                std::make_index_sequence<sizeof...(arguments)>{},
+                arguments...);
+        }
+
+        template <auto Id, auto MaxSize = -1>
+        constexpr auto request_body(auto &&... arguments)
+        {
+            return request_body<zpp::bits::id<Id, MaxSize>>(arguments...);
+        }
+
+        template <typename Id>
+        constexpr auto response()
+        {
+            using request_binding = decltype(binding<Id, Bindings...>());
+            using return_type = typename request_binding::return_type;
+
+            if constexpr (std::is_void_v<return_type>) {
+                return;
+#if __has_include("zpp_throwing.h")
+            } else if constexpr (requires(return_type && value) {
+                                     value.await_ready();
+                                 }) {
+                using nested_return = std::remove_cvref_t<
+                    decltype(std::declval<return_type>().await_resume())>;
+                if constexpr (std::is_void_v<nested_return>) {
+                    return;
+                } else {
+                    nested_return return_value;
+                    if (auto result = in(return_value); failure(result))
+                        [[unlikely]] {
+                        return value_or_errc<nested_return>{result};
+                    }
+                    return value_or_errc<nested_return>{std::move(return_value)};
+                }
+#endif
+            } else {
+                return_type return_value;
+                if (auto result = in(return_value); failure(result))
+                    [[unlikely]] {
+                    return value_or_errc<return_type>{result};
+                }
+                return value_or_errc<return_type>{std::move(return_value)};
+            }
+        }
+
+        template <auto Id, auto MaxSize = -1>
+        constexpr auto response()
+        {
+            return response<zpp::bits::id<Id, MaxSize>>();
+        }
+
+        In & in;
+        Out & out;
+    };
+
+#if defined __clang__ || !defined __GNUC__ || __GNUC__ >= 12 // GCC issue
+    template <typename... Types>
+    client(Types && ...) -> client<Types&&...>;
+#endif
+
+    template <typename In, typename Out, typename Context = std::monostate>
+    struct server
+    {
+        constexpr server(In && in, Out && out) :
+            in(in),
+            out(out)
+        {
+        }
+
+        constexpr server(In && in, Out && out, Context && context) :
+            in(in),
+            out(out),
+            context(context)
+        {
+        }
+
+        constexpr server(server && other) = default;
+
+        constexpr ~server()
+        {
+            static_assert(std::remove_cvref_t<decltype(in)>::kind() == kind::in);
+            static_assert(std::remove_cvref_t<decltype(out)>::kind() == kind::out);
+        }
+
+        template <typename FirstBinding, typename... OtherBindings>
+        ZPP_BITS_INLINE constexpr auto
+        call_binding(auto & id) requires(!FirstBinding::opaque)
+        {
+            if (FirstBinding::id::value == id) {
+                if constexpr (std::is_void_v<decltype(FirstBinding::call(
+                                  in, context))>) {
+                    FirstBinding::call(in, context);
+                    return errc{};
+                } else if constexpr (std::same_as<
+                                         decltype(FirstBinding::call(
+                                             in, context)),
+                                         errc>) {
+                    if (auto result = FirstBinding::call(in, context);
+                        failure(result)) [[unlikely]] {
+                        return result;
+                    }
+                    return errc{};
+                } else if constexpr (std::is_void_v<typename FirstBinding::
+                                                        parameters_type>) {
+                    return out(FirstBinding::call(in, context));
+                } else {
+                    if (auto result = FirstBinding::call(in, context);
+                        failure(result)) [[unlikely]] {
+                        return result.error();
+                    } else {
+                        return out(result.value());
+                    }
+                }
+            } else {
+                if constexpr (!sizeof...(OtherBindings)) {
+                    return errc{std::errc::not_supported};
+                } else {
+                    return call_binding<OtherBindings...>(id);
+                }
+            }
+        }
+
+        template <typename FirstBinding, typename... OtherBindings>
+        ZPP_BITS_INLINE constexpr auto
+        call_binding(auto & id) requires FirstBinding::opaque
+        {
+            if (FirstBinding::id::value == id) {
+                if constexpr (std::is_void_v<decltype(FirstBinding::call(
+                                  in, out, context))>) {
+                    FirstBinding::call(in, out, context);
+                    return errc{};
+                } else if constexpr (std::same_as<
+                                         decltype(FirstBinding::call(
+                                             in, out, context)),
+                                         errc>) {
+                    if (auto result = FirstBinding::call(in, out, context);
+                        failure(result)) [[unlikely]] {
+                        return result;
+                    }
+                    return errc{};
+                } else if constexpr (
+                    requires {
+                        requires std::same_as<
+                            typename decltype(FirstBinding::call(
+                                in, out, context))::value_type,
+                            value_or_errc<decltype(FirstBinding::call(
+                                in, out, context))>>;
+                    }) {
+                    if (auto result = FirstBinding::call(in, out, context);
+                        failure(result)) [[unlikely]] {
+                        return result.error();
+                    } else {
+                        return out(result.value());
+                    }
+                } else {
+                    return out(FirstBinding::call(in, out, context));
+                }
+            } else {
+                if constexpr (!sizeof...(OtherBindings)) {
+                    return errc{std::errc::not_supported};
+                } else {
+                    return call_binding<OtherBindings...>(id);
+                }
+            }
+        }
+
+#if __has_include("zpp_throwing.h")
+        template <typename FirstBinding, typename... OtherBindings>
+        zpp::throwing<void>
+        call_binding_throwing(auto & id) requires(!FirstBinding::opaque)
+        {
+            if (FirstBinding::id::value == id) {
+                if constexpr (std::is_void_v<decltype(FirstBinding::call(
+                                  in, context))>) {
+                    FirstBinding::call(in, context);
+                    co_return;
+                } else if constexpr (std::same_as<
+                                         decltype(FirstBinding::call(
+                                             in, context)),
+                                         errc>) {
+                    if (auto result = FirstBinding::call(in, context);
+                        failure(result)) [[unlikely]] {
+                        co_yield result.code;
+                    }
+                    co_return;
+                } else if constexpr (std::is_void_v<typename FirstBinding::
+                                                        parameters_type>) {
+                    if constexpr (requires {
+                                      FirstBinding::call(in, context)
+                                          .await_ready();
+                                  }) {
+                        if constexpr (std::is_void_v<
+                                          decltype(FirstBinding::call(
+                                                       in, context)
+                                                       .await_resume())>) {
+                            co_await FirstBinding::call(in, context);
+                        } else {
+                            co_await out(
+                                co_await FirstBinding::call(in, context));
+                        }
+                    } else {
+                        co_await out(FirstBinding::call(in, context));
+                    }
+                } else {
+                    if (auto result = FirstBinding::call(in, context);
+                        failure(result)) [[unlikely]] {
+                        co_yield result.error().code;
+                    } else if constexpr (requires {
+                                             result.value().await_ready();
+                                         }) {
+                        if constexpr (!std::is_void_v<
+                                          decltype(result.value()
+                                                       .await_resume())>) {
+                            co_await out(co_await result.value());
+                        }
+                        co_return;
+                    } else {
+                        co_await out(result.value());
+                    }
+                }
+            } else {
+                if constexpr (!sizeof...(OtherBindings)) {
+                    co_yield std::errc::not_supported;
+                } else {
+                    co_return co_await call_binding_throwing<
+                        OtherBindings...>(id);
+                }
+            }
+        }
+
+        template <typename FirstBinding, typename... OtherBindings>
+        zpp::throwing<void>
+        call_binding_throwing(auto & id) requires FirstBinding::opaque
+        {
+            if (FirstBinding::id::value == id) {
+                if constexpr (std::is_void_v<decltype(FirstBinding::call(
+                                  in, out, context))>) {
+                    FirstBinding::call(in, out, context);
+                    co_return;
+                } else if constexpr (std::same_as<
+                                         decltype(FirstBinding::call(
+                                             in, out, context)),
+                                         errc>) {
+                    if (auto result = FirstBinding::call(in, out, context);
+                        failure(result)) [[unlikely]] {
+                        co_yield result.code;
+                    }
+                    co_return;
+                } else if constexpr (
+                    requires {
+                        requires std::same_as<
+                            typename decltype(FirstBinding::call(
+                                in, out, context))::value_type,
+                            value_or_errc<decltype(FirstBinding::call(
+                                in, out, context))>>;
+                    }) {
+                    if (auto result = FirstBinding::call(in, out, context);
+                        failure(result)) [[unlikely]] {
+                        co_yield result.error().code;
+                    } else if constexpr (requires {
+                                             result.value().await_ready();
+                                         }) {
+                        if constexpr (!std::is_void_v<
+                                          decltype(result.value()
+                                                       .await_resume())>) {
+                            co_await out(co_await result.value());
+                        }
+                        co_return;
+                    } else {
+                        co_await out(result.value());
+                    }
+                } else {
+                    if constexpr (requires {
+                                      FirstBinding::call(in, out, context)
+                                          .await_ready();
+                                  }) {
+                        if constexpr (std::is_void_v<
+                                          decltype(FirstBinding::call(
+                                                       in, out, context)
+                                                       .await_resume())>) {
+                            co_await FirstBinding::call(in, out, context);
+                        } else {
+                            co_await out(
+                                co_await FirstBinding::call(in, out, context));
+                        }
+                    } else {
+                        co_await out(FirstBinding::call(in, out, context));
+                    }
+                }
+            } else {
+                if constexpr (!sizeof...(OtherBindings)) {
+                    co_yield std::errc::not_supported;
+                } else {
+                    co_return co_await call_binding_throwing<
+                        OtherBindings...>(id);
+                }
+            }
+        }
+#endif
+
+        constexpr auto serve(auto && id)
+        {
+#if __has_include("zpp_throwing.h")
+            if constexpr ((... || requires {
+                              std::declval<
+                                  typename Bindings::return_type>()
+                                  .await_ready();
+                          })) {
+                return call_binding_throwing<Bindings...>(id);
+            } else {
+#endif
+                return call_binding<Bindings...>(id);
+#if __has_include("zpp_throwing.h")
+            }
+#endif
+        }
+
+        constexpr auto serve()
+        {
+            rpc_impl::id id;
+            if (auto result = in(id); failure(result)) [[unlikely]] {
+                return decltype(serve(rpc_impl::id{})){result.code};
+            }
+
+            return serve(id);
+        }
+
+        In & in;
+        Out & out;
+        [[no_unique_address]] Context context;
+    };
+
+#if defined __clang__ || !defined __GNUC__ || __GNUC__ >= 12 // GCC issue
+    template <typename... Types>
+    server(Types && ...) -> server<Types&&...>;
+#endif
+
+#if defined __clang__ || !defined __GNUC__ || __GNUC__ >= 12 // GCC issue
+    constexpr static auto client_server(auto && in, auto && out, auto &&... context)
+    {
+        return std::tuple{client{in, out}, server{in, out, context...}};
+    }
+#else
+    constexpr static auto client_server(auto && in, auto && out)
+    {
+        return std::tuple{client<decltype(in), decltype(out)>{in, out},
+                          server<decltype(in), decltype(out)>{in, out}};
+    }
+
+    constexpr static auto client_server(auto && in, auto && out, auto && context)
+    {
+        return std::tuple{
+            client<decltype(in), decltype(out)>{in, out},
+            server<decltype(in), decltype(out), decltype(context)>{
+                in, out, context}};
+    }
+#endif
+};
+
+template <typename... Bindings>
+struct rpc_checker
+{
+    using check_unique_id = traits::variant<
+        std::variant<traits::id_serializable<typename Bindings::id>...>>;
+    using type = rpc_impl<Bindings...>;
+};
+
+template <typename... Bindings>
+using rpc = typename rpc_checker<Bindings...>::type;
+
+struct pb_reserved
+{
+};
+
+template <std::size_t From, std::size_t To>
+struct pb_map
+{
+    static_assert(From != 0 && To != 0);
+
+    constexpr static unsigned int mapped_field(auto index)
+    {
+        return ((index + 1) == From) ? To : 0u;
+    }
+};
+
+template <typename Type, auto FieldNumber>
+struct pb_field_fundamental
+{
+    using value_type = Type;
+    using pb_field_type = Type;
+
+    constexpr static auto pb_field_number = FieldNumber;
+
+    constexpr pb_field_fundamental() = default;
+
+    constexpr pb_field_fundamental(Type value) :
+        value(value)
+    {
+    }
+
+    constexpr operator Type &() &
+    {
+        return value;
+    }
+
+    constexpr operator Type() const
+    {
+        return value;
+    }
+
+    Type value{};
+};
+
+template <typename Type, auto FieldNumber>
+constexpr decltype(auto)
+pb_value(pb_field_fundamental<Type, FieldNumber> & pb)
+{
+    return static_cast<
+        typename pb_field_fundamental<Type, FieldNumber>::pb_field_type &>(
+        pb);
+}
+
+template <typename Type, auto FieldNumber>
+constexpr auto pb_value(const pb_field_fundamental<Type, FieldNumber> & pb)
+{
+    return static_cast<
+        typename pb_field_fundamental<Type, FieldNumber>::pb_field_type>(
+        pb);
+}
+
+template <typename Type, auto FieldNumber>
+struct pb_field_struct : Type
+{
+    using Type::Type;
+    using Type::operator=;
+    using pb_field_type = Type;
+
+    static constexpr auto pb_field_number = FieldNumber;
+
+    constexpr pb_field_struct(Type && other) noexcept(
+        std::is_nothrow_move_constructible_v<Type>) :
+        Type(std::move(other))
+    {
+    }
+
+    constexpr pb_field_struct(const Type & other)
+        : Type(other)
+    {
+    }
+};
+
+template <typename Type, auto FieldNumber>
+constexpr decltype(auto) pb_value(pb_field_struct<Type, FieldNumber> & pb)
+{
+    return static_cast<
+        typename pb_field_struct<Type, FieldNumber>::pb_field_type &>(pb);
+}
+
+template <typename Type, auto FieldNumber>
+constexpr decltype(auto)
+pb_value(const pb_field_struct<Type, FieldNumber> & pb)
+{
+    return static_cast<const typename pb_field_struct<Type, FieldNumber>::
+                           pb_field_type &>(pb);
+}
+
+template <typename Type, auto FieldNumber>
+constexpr decltype(auto) pb_value(pb_field_struct<Type, FieldNumber> && pb)
+{
+    return static_cast<
+        typename pb_field_struct<Type, FieldNumber>::pb_field_type &&>(pb);
+}
+
+template <typename Type, auto FieldNumber>
+using pb_field =
+    std::conditional_t<std::is_class_v<Type>,
+                       pb_field_struct<Type, FieldNumber>,
+                       pb_field_fundamental<Type, FieldNumber>>;
+
+template <typename... Options>
+struct pb
+{
+    using pb_default = pb<>;
+
+    constexpr pb(Options && ...)
+    {
+    }
+
+    template <std::size_t Index>
+    constexpr static auto has_mapped_field(auto option)
+    {
+        return requires
+        {
+            requires decltype(option)::mapped_field(Index) != 0;
+        };
+    }
+
+    template <std::size_t Index>
+    constexpr static auto get_mapped_field(auto option)
+    {
+        if constexpr (requires {
+                          requires decltype(option)::mapped_field(Index) != 0;
+                      }) {
+            return decltype(option)::mapped_field(Index);
+        } else {
+            return 0u;
+        }
+    }
+
+    template <std::size_t Index>
+    struct field_number_visitor
+    {
+        template <typename... Types>
+        constexpr auto operator()() const
+        {
+            if constexpr (requires {
+                              std::remove_cvref_t<decltype(std::get<Index>(
+                                  std::declval<std::tuple<Types...>>()))>::
+                                  pb_field_number;
+                          }) {
+                static_assert(0 !=
+                              std::remove_cvref_t<decltype(std::get<Index>(
+                                  std::declval<std::tuple<Types...>>()))>::
+                                  pb_field_number);
+                return std::integral_constant<
+                    unsigned int,
+                    std::remove_cvref_t<decltype(std::get<Index>(
+                        std::declval<std::tuple<Types...>>()))>::
+                        pb_field_number>();
+            } else {
+                return std::integral_constant<unsigned int, 0>{};
+            }
+        }
+    };
+
+    template <typename Type, std::size_t Index>
+    constexpr static auto field_number_from_struct()
+    {
+        constexpr auto explicit_field_number =
+            visit_members_types<Type>(field_number_visitor<Index>{})();
+        if constexpr (explicit_field_number > 0) {
+            return explicit_field_number;
+        } else {
+            static_assert(
+                (0 + ... + std::size_t(has_mapped_field<Index>(Options{}))) <=
+                1);
+
+            constexpr auto mapped_field =
+                (0 + ... + get_mapped_field<Index>(Options{}));
+            if constexpr (mapped_field != 0) {
+                return mapped_field;
+            } else {
+                return Index + 1;
+            }
+        }
+    }
+
+    template <typename Type, std::size_t Index>
+    constexpr static auto field_number()
+    {
+        if constexpr (requires { requires(Type::pb_field_number > 0); }) {
+            return Type::pb_field_number;
+        } else {
+            static_assert(
+                (0 + ... + std::size_t(has_mapped_field<Index>(Options{}))) <=
+                1);
+
+            constexpr auto mapped_field =
+                (0 + ... + get_mapped_field<Index>(Options{}));
+            if constexpr (mapped_field != 0) {
+                return mapped_field;
+            } else {
+                return Index + 1;
+            }
+        }
+    }
+
+    template <typename Type, std::size_t... Indices>
+    constexpr static auto unique_field_numbers(std::index_sequence<Indices...>)
+    {
+        return traits::unique(
+            std::size_t{field_number_from_struct<Type, Indices>()}...);
+    }
+
+    template <typename Type>
+    constexpr static auto unique_field_numbers()
+    {
+        constexpr auto members =
+            number_of_members<std::remove_cvref_t<Type>>();
+        if constexpr (members >= 0) {
+            return unique_field_numbers<std::remove_cvref_t<Type>>(
+                std::make_index_sequence<members>());
+        } else {
+            static_assert(members >= 0);
+        }
+    }
+
+    template <typename Type>
+    constexpr static auto is_pb_field()
+    {
+        using type = std::remove_cvref_t<Type>;
+        return requires
+        {
+            requires std::same_as<type,
+                                  pb_field<typename type::pb_field_type,
+                                           type::pb_field_number>>;
+        };
+    }
+
+    template <typename Type>
+    constexpr static auto check_type()
+    {
+        using type = std::remove_cvref_t<Type>;
+        if constexpr (is_pb_field<type>()) {
+            return check_type<typename type::pb_field_type>();
+        } else if constexpr (!std::is_class_v<type> ||
+                             concepts::varint<type> ||
+                             concepts::empty<type>) {
+            return true;
+        } else if constexpr (concepts::associative_container<type> &&
+                             requires { typename type::mapped_type; }) {
+            static_assert(
+                requires {
+                    type{}.push_back(typename type::value_type{});
+                } ||
+                requires { type{}.insert(typename type::value_type{}); });
+            static_assert(check_type<typename type::key_type>());
+            static_assert(check_type<typename type::mapped_type>());
+            return true;
+        } else if constexpr (concepts::container<type>) {
+            static_assert(
+                requires {
+                    type{}.push_back(typename type::value_type{});
+                } ||
+                requires { type{}.insert(typename type::value_type{}); });
+            static_assert(check_type<typename type::value_type>());
+            return true;
+        } else if constexpr (concepts::by_protocol<type>) {
+            static_assert(
+                std::same_as<pb_default,
+                             typename decltype(access::get_protocol<
+                                               type>())::pb_default>);
+            static_assert(unique_field_numbers<type>());
+            return true;
+        } else {
+            static_assert(!sizeof(Type));
+        }
+    }
+
+    enum class wire_type : unsigned int
+    {
+        varint = 0,
+        fixed_64 = 1,
+        length_delimited = 2,
+        fixed_32 = 5,
+    };
+
+    constexpr static auto make_tag_explicit(wire_type type, auto field_number)
+    {
+        return varint{(field_number << 3) |
+                      std::underlying_type_t<wire_type>(type)};
+    }
+
+    constexpr static auto tag_type(auto tag)
+    {
+        return wire_type(tag & 0x7);
+    }
+
+    constexpr static auto tag_number(auto tag)
+    {
+        return (unsigned int)(tag >> 3);
+    }
+
+    template <typename Type>
+    constexpr static auto tag_type()
+    {
+        using type = std::remove_cvref_t<Type>;
+        if constexpr (is_pb_field<type>()) {
+            return tag_type<typename type::pb_field_type>();
+        } else if constexpr (concepts::varint<type> ||
+                      (std::is_enum_v<type> &&
+                       !std::same_as<type, std::byte>) ||
+                      std::same_as<type, bool>) {
+            return wire_type::varint;
+        } else if constexpr (std::is_integral_v<type> ||
+                             std::is_floating_point_v<type>) {
+            if constexpr (sizeof(type) == 4) {
+                return wire_type::fixed_32;
+            } else if constexpr (sizeof(type) == 8) {
+                return wire_type::fixed_64;
+            } else {
+                static_assert(!sizeof(type));
+            }
+        } else {
+            return wire_type::length_delimited;
+        }
+    }
+
+    template <typename Type>
+    constexpr static auto make_tag_explicit(auto field_number)
+    {
+        return make_tag_explicit(tag_type<Type>(), field_number);
+    }
+
+    template <typename Type, auto Index>
+    constexpr static auto make_tag()
+    {
+        return make_tag_explicit(tag_type<Type>(),
+                                 field_number<Type, Index>());
+    }
+
+    template <wire_type WireType, typename Type, auto Index>
+    constexpr static auto make_tag()
+    {
+        return make_tag_explicit(WireType, field_number<Type, Index>());
+    }
+
+    ZPP_BITS_INLINE constexpr auto
+    operator()(auto & archive, auto & item) const requires(
+        std::remove_cvref_t<decltype(archive)>::kind() == kind::out)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        static_assert(check_type<type>());
+
+        using archive_type = typename std::remove_cvref_t<decltype(archive)>;
+        if constexpr (!concepts::varint<
+                          typename archive_type::default_size_type> ||
+                      ((std::endian::little != std::endian::native) &&
+                       !archive_type::endian_aware)) {
+            out out{archive.data(),
+                    size_varint{},
+                    no_fit_size{},
+                    endian::little{},
+                    enlarger<std::get<0>(archive_type::enlarger),
+                             std::get<1>(archive_type::enlarger)>{},
+                    std::conditional_t<archive_type::no_enlarge_overflow,
+                                       no_enlarge_overflow,
+                                       enlarge_overflow>{},
+                    alloc_limit<archive_type::allocation_limit>{}};
+            out.position() = archive.position();
+            if constexpr (concepts::self_referencing<type>) {
+                auto result = visit_members(
+                    item,
+                    [&](auto &&... items) constexpr {
+                        static_assert((... && check_type<decltype(items)>()));
+                        return serialize_many(
+                            std::make_index_sequence<sizeof...(items)>{},
+                            out,
+                            items...);
+                    });
+                archive.position() = out.position();
+                return result;
+            } else {
+                auto result = visit_members(
+                    item,
+                    [&](auto &&... items) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                        static_assert((... && check_type<decltype(items)>()));
+                        return serialize_many(
+                            std::make_index_sequence<sizeof...(items)>{},
+                            out,
+                            items...);
+                    });
+                archive.position() = out.position();
+                return result;
+            }
+        } else if constexpr (concepts::self_referencing<type>) {
+            return visit_members(
+                item,
+                [&](auto &&... items) constexpr {
+                    static_assert((... && check_type<decltype(items)>()));
+                    return serialize_many(
+                        std::make_index_sequence<sizeof...(items)>{},
+                        archive,
+                        items...);
+                });
+        } else {
+            return visit_members(
+                item,
+                [&](auto &&... items) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    static_assert((... && check_type<decltype(items)>()));
+                    return serialize_many(
+                        std::make_index_sequence<sizeof...(items)>{},
+                        archive,
+                        items...);
+                });
+        }
+    }
+
+    template <std::size_t FirstIndex, std::size_t... Indices>
+    ZPP_BITS_INLINE constexpr static auto serialize_many(
+        std::index_sequence<FirstIndex, Indices...>,
+        auto & archive,
+        auto & first_item,
+        auto &... items) requires(std::remove_cvref_t<decltype(archive)>::
+                                      kind() == kind::out)
+    {
+        if (auto result = serialize_one<FirstIndex>(archive, first_item);
+            failure(result)) [[unlikely]] {
+            return result;
+        }
+
+        return serialize_many(
+            std::index_sequence<Indices...>{}, archive, items...);
+    }
+
+    ZPP_BITS_INLINE constexpr static errc
+    serialize_many(std::index_sequence<>, auto & archive) requires(
+        std::remove_cvref_t<decltype(archive)>::kind() == kind::out)
+    {
+        return {};
+    }
+
+    template <std::size_t Index, typename TagType = void>
+    ZPP_BITS_INLINE constexpr static errc
+    serialize_one(auto & archive, auto & item) requires(
+        std::remove_cvref_t<decltype(archive)>::kind() == kind::out)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        using tag_type = std::conditional_t<std::is_void_v<TagType>, type, TagType>;
+
+        if constexpr (concepts::empty<type>) {
+            return {};
+        } else if constexpr (is_pb_field<type>()) {
+            return serialize_one<Index, tag_type>(
+                archive,
+                static_cast<const typename type::pb_field_type &>(item));
+        } else if constexpr (std::is_enum_v<type> &&
+                             !std::same_as<type, std::byte>) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+            if (auto result = archive(
+                    tag, varint{std::underlying_type_t<type>(item)});
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+            return {};
+        } else if constexpr (!concepts::container<type>) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+            if (auto result = archive(tag, item); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+            return {};
+        } else if constexpr (concepts::associative_container<type> &&
+                             requires { typename type::mapped_type; }) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+
+            using key_type = std::conditional_t<
+                std::is_enum_v<typename type::key_type> &&
+                    !std::same_as<typename type::key_type, std::byte>,
+                varint<typename type::key_type>,
+                typename type::key_type>;
+
+            using mapped_type = std::conditional_t<
+                std::is_enum_v<typename type::mapped_type> &&
+                    !std::same_as<typename type::mapped_type, std::byte>,
+                varint<typename type::mapped_type>,
+                typename type::mapped_type>;
+
+            struct value_type
+            {
+                const key_type & key;
+                const mapped_type & value;
+
+                using serialize = protocol<pb_default{}>;
+                serialize use();
+            };
+
+            for (auto & [key, value] : item) {
+                if (auto result = archive(
+                        tag, value_type{.key = key, .value = value});
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            }
+
+            return {};
+        } else if constexpr (requires {
+                                 requires std::is_fundamental_v<
+                                     typename type::value_type> ||
+                                     std::same_as<
+                                         typename type::value_type,
+                                         std::byte>;
+                             }) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+            auto size = item.size();
+            if (!size) [[unlikely]] {
+                return {};
+            }
+            if (auto result = archive(
+                    tag,
+                    varint{size * sizeof(typename type::value_type)},
+                    unsized(item));
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+            return {};
+        } else if constexpr (requires {
+                                 requires concepts::varint<
+                                     typename type::value_type>;
+                             }) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+
+            std::size_t size = {};
+            for (auto & element : item) {
+                size +=
+                    varint_size<type::value_type::encoding>(element.value);
+            }
+            if (!size) [[unlikely]] {
+                return {};
+            }
+            if (auto result = archive(tag, varint{size}, unsized(item));
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+            return {};
+        } else if constexpr (requires {
+                                 requires std::is_enum_v<
+                                     typename type::value_type>;
+                             }) {
+            constexpr auto tag = make_tag<tag_type, Index>();
+
+            using type = typename type::value_type;
+            std::size_t size = {};
+            for (auto & element : item) {
+                size += varint_size(std::underlying_type_t<type>(element));
+            }
+            if (!size) [[unlikely]] {
+                return {};
+            }
+            if (auto result = archive(tag, varint{size}); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+            for (auto & element : item) {
+                if (auto result = archive(
+                        varint{std::underlying_type_t<type>(element)});
+                    failure(result)) [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        } else {
+            constexpr auto tag =
+                make_tag<typename type::value_type, Index>();
+            for (auto & element : item) {
+                if (auto result = archive(tag, element); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+            }
+            return {};
+        }
+    }
+
+    ZPP_BITS_INLINE constexpr errc operator()(
+        auto & archive,
+        auto & item,
+        std::size_t size = std::numeric_limits<std::size_t>::max()) const
+        requires(std::remove_cvref_t<decltype(archive)>::kind() ==
+                 kind::in)
+    {
+        auto data = archive.remaining_data();
+        in in{std::span{data.data(), std::min(size, data.size())},
+              size_varint{},
+              endian::little{},
+              alloc_limit<std::remove_cvref_t<
+                  decltype(archive)>::allocation_limit>{}};
+        auto result = deserialize_fields(in, item);
+        archive.position() += in.position();
+        return result;
+    }
+
+    ZPP_BITS_INLINE constexpr static errc
+    deserialize_fields(auto & archive, auto & item)
+    {
+        using type = std::remove_cvref_t<decltype(item)>;
+        static_assert(check_type<type>());
+
+        auto size = archive.data().size();
+        visit_members(
+            item, [](auto &&... members) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                (
+                    [](auto && member) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                        using type = std::remove_cvref_t<decltype(member)>;
+                        if constexpr (concepts::container<type> &&
+                                      !std::is_fundamental_v<type> &&
+                                      !std::same_as<type, std::byte> &&
+                                      requires { member.clear(); }) {
+                            member.clear();
+                        }
+                    }(members),
+                    ...);
+            });
+
+        while (archive.position() < size) {
+            vuint32_t tag;
+            if (auto result = archive(tag); failure(result)) [[unlikely]] {
+                return result;
+            }
+
+            if (auto result = deserialize_field(
+                    archive, item, tag_number(tag), tag_type(tag));
+                failure(result)) [[unlikely]] {
+                return result;
+            }
+        }
+
+        return {};
+    }
+
+    template <std::size_t Index = 0>
+    ZPP_BITS_INLINE constexpr static auto
+    deserialize_field(auto & archive,
+                      auto && item,
+                      auto field_num,
+                      wire_type field_type)
+    {
+        using type = std::remove_reference_t<decltype(item)>;
+        if constexpr (Index >= number_of_members<type>()) {
+            if (!field_num) [[unlikely]] {
+                return errc{std::errc::protocol_error};
+            }
+            return errc{};
+        } else if (field_number_from_struct<type, Index>() != field_num) {
+            return deserialize_field<Index + 1>(
+                archive, item, field_num, field_type);
+        } else if constexpr (concepts::self_referencing<type>) {
+            return visit_members(
+                item,
+                [&](auto &&... items) constexpr {
+                    std::tuple<decltype(items) &...> refs = {items...};
+                    auto & item = std::get<Index>(refs);
+                    using type = std::remove_reference_t<decltype(item)>;
+                    static_assert(check_type<type>());
+
+                    return deserialize_field(archive, field_type, item);
+                });
+        } else {
+            return visit_members(
+                item,
+                [&](auto &&... items) ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    std::tuple<decltype(items) &...> refs = {items...};
+                    auto & item = std::get<Index>(refs);
+                    using type = std::remove_reference_t<decltype(item)>;
+                    static_assert(check_type<type>());
+
+                    return deserialize_field(archive, field_type, item);
+                });
+        }
+    }
+
+    ZPP_BITS_INLINE constexpr static auto deserialize_field(
+        auto & archive, wire_type field_type, auto & item)
+    {
+        using type = std::remove_reference_t<decltype(item)>;
+        using archive_type = std::remove_reference_t<decltype(archive)>;
+        static_assert(check_type<type>());
+
+        if constexpr (std::is_enum_v<type>) {
+            varint<type> value;
+            if (auto result = archive(value); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+            item = value;
+            return errc{};
+        } else if constexpr (is_pb_field<type>()) {
+            return deserialize_field(
+                archive,
+                field_type,
+                static_cast<typename type::pb_field_type &>(item));
+        } else if constexpr (!concepts::container<type>) {
+            return archive(item);
+        } else if constexpr (concepts::associative_container<type> &&
+                             requires { typename type::mapped_type; }) {
+            using key_type = std::conditional_t<
+                std::is_enum_v<typename type::key_type> &&
+                    !std::same_as<typename type::key_type, std::byte>,
+                varint<typename type::key_type>,
+                typename type::key_type>;
+
+            using mapped_type = std::conditional_t<
+                std::is_enum_v<typename type::mapped_type> &&
+                    !std::same_as<typename type::mapped_type, std::byte>,
+                varint<typename type::mapped_type>,
+                typename type::mapped_type>;
+
+            struct value_type
+            {
+                key_type key;
+                mapped_type value;
+
+                using serialize = protocol<pb_default{}>;
+                serialize use();
+            };
+
+            std::aligned_storage_t<sizeof(value_type),
+                                   alignof(value_type)>
+                storage;
+
+            auto object =
+                access::placement_new<value_type>(std::addressof(storage));
+            destructor_guard guard{*object};
+            if (auto result = archive(*object); failure(result))
+                [[unlikely]] {
+                return result;
+            }
+
+            item.emplace(std::move(object->key), std::move(object->value));
+            return errc{};
+        } else {
+            using orig_value_type = typename type::value_type;
+            using value_type = std::conditional_t<
+                std::is_enum_v<orig_value_type> &&
+                    !std::same_as<orig_value_type, std::byte>,
+                varint<orig_value_type>,
+                orig_value_type>;
+
+            if constexpr (std::is_fundamental_v<value_type> ||
+                          std::same_as<std::byte, value_type> ||
+                          concepts::varint<value_type>) {
+                auto fetch = [&]() ZPP_BITS_CONSTEXPR_INLINE_LAMBDA {
+                    value_type value;
+                    if (auto result = archive(value); failure(result))
+                        [[unlikely]] {
+                        return result;
+                    }
+
+                    if constexpr (requires {
+                                      item.push_back(
+                                          orig_value_type(value));
+                                  }) {
+                        item.push_back(orig_value_type(value));
+                    } else {
+                        item.insert(orig_value_type(value));
+                    }
+
+                    return errc{};
+                };
+                if (field_type != wire_type::length_delimited)
+                    [[unlikely]] {
+                    return fetch();
+                }
+                vsize_t length;
+                if (auto result = archive(length); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+
+                if constexpr (requires { item.resize(1); } &&
+                              (std::is_fundamental_v<value_type> ||
+                               std::same_as<value_type, std::byte>)) {
+                    if constexpr (archive_type::allocation_limit !=
+                                  std::numeric_limits<
+                                      std::size_t>::max()) {
+                        if (length > archive_type::allocation_limit)
+                            [[unlikely]] {
+                            return errc{std::errc::message_size};
+                        }
+                    }
+                    item.resize(length / sizeof(value_type));
+                    return archive(unsized(item));
+                } else {
+                    if constexpr (requires { item.reserve(1); }) {
+                        item.reserve(length);
+                    }
+
+                    auto end_position = length + archive.position();
+                    while (archive.position() < end_position) {
+                        if (auto result = fetch(); failure(result))
+                            [[unlikely]] {
+                            return result;
+                        }
+                    }
+
+                    return errc{};
+                }
+            } else {
+                std::aligned_storage_t<sizeof(value_type),
+                                       alignof(value_type)>
+                    storage;
+
+                auto object = access::placement_new<value_type>(
+                    std::addressof(storage));
+                destructor_guard guard{*object};
+                if (auto result = archive(*object); failure(result))
+                    [[unlikely]] {
+                    return result;
+                }
+
+                if constexpr (requires {
+                                  item.push_back(std::move(*object));
+                              }) {
+                    item.push_back(std::move(*object));
+                } else {
+                    item.insert(std::move(*object));
+                }
+
+                return errc{};
+            }
+        }
+    }
+};
+
+using pb_protocol = protocol<pb{}>;
+
+template <std::size_t Members = std::numeric_limits<std::size_t>::max()>
+using pb_members = protocol<pb{}, Members>;
+
+namespace numbers
+{
+template <typename Type>
+struct big_endian
+{
+    struct emplace{};
+
+    constexpr big_endian() = default;
+
+    constexpr explicit big_endian(Type value, emplace) : value(value)
+    {
+    }
+
+    constexpr explicit big_endian(Type value)
+    {
+        std::array<std::byte, sizeof(value)> data;
+        for (std::size_t i = 0; i < sizeof(value); ++i) {
+            data[sizeof(value) - 1 - i] = std::byte(value & 0xff);
+            value >>= CHAR_BIT;
+        }
+
+        this->value = std::bit_cast<Type>(data);
+    }
+
+    constexpr auto operator<<(auto value) const
+    {
+        auto data =
+            std::bit_cast<std::array<unsigned char, sizeof(*this)>>(*this);
+
+        for (std::size_t i = 0; i < sizeof(Type); ++i) {
+            auto offset = (value % CHAR_BIT);
+            auto current = i + (value / CHAR_BIT);
+
+            if (current >= sizeof(Type)) {
+                data[i] = 0;
+                continue;
+            }
+
+            data[i] = data[current] << offset;
+            if (current == sizeof(Type) - 1) {
+                continue;
+            }
+
+            offset = CHAR_BIT - offset;
+            if (offset >= 0) {
+                data[i] |= data[current + 1] >> offset;
+            } else {
+                data[i] |= data[current + 1] << (-offset);
+            }
+        }
+
+        return std::bit_cast<big_endian>(data);
+    }
+
+    constexpr auto operator>>(auto value) const
+    {
+        auto data =
+            std::bit_cast<std::array<unsigned char, sizeof(*this)>>(*this);
+
+        for (std::size_t j = 0; j < sizeof(Type); ++j) {
+            auto i = sizeof(Type) - 1 - j;
+            auto offset = (value % CHAR_BIT);
+            auto current = i - (value / CHAR_BIT);
+
+            if (current >= sizeof(Type)) {
+                data[i] = 0;
+                continue;
+            }
+
+            data[i] = data[current] >> offset;
+            if (!current) {
+                continue;
+            }
+
+            offset = CHAR_BIT - offset;
+            if (offset >= 0) {
+                data[i] |= data[current - 1] << offset;
+            } else {
+                data[i] |= data[current - 1] >> (-offset);
+            }
+        }
+
+        return std::bit_cast<big_endian>(data);
+    }
+
+    constexpr auto friend operator+(big_endian left, big_endian right)
+    {
+        auto left_data = std::bit_cast<std::array<unsigned char, sizeof(left)>>(left);
+        auto right_data = std::bit_cast<std::array<unsigned char, sizeof(right)>>(right);
+        unsigned char remaining{};
+
+        for (std::size_t i = 0; i < sizeof(Type); ++i) {
+            auto current = sizeof(Type) - 1 - i;
+            std::uint16_t byte_addition =
+                std::uint16_t(left_data[current]) +
+                std::uint16_t(right_data[current]) + remaining;
+            left_data[current] = std::uint8_t(byte_addition & 0xff);
+            remaining = std::uint8_t((byte_addition >> CHAR_BIT) & 0xff);
+        }
+
+        return std::bit_cast<big_endian>(left_data);
+    }
+
+    constexpr big_endian operator~() const
+    {
+        return big_endian{~value, emplace{}};
+    }
+
+    constexpr auto & operator+=(big_endian other)
+    {
+        *this = (*this) + other;
+        return *this;
+    }
+
+    constexpr auto friend operator&(big_endian left, big_endian right)
+    {
+        return big_endian{left.value & right.value, emplace{}};
+    }
+
+    constexpr auto friend operator^(big_endian left, big_endian right)
+    {
+        return big_endian{left.value ^ right.value, emplace{}};
+    }
+
+    constexpr auto friend operator|(big_endian left, big_endian right)
+    {
+        return big_endian{left.value | right.value, emplace{}};
+    }
+
+    constexpr auto friend operator<=>(big_endian left,
+                                      big_endian right) = default;
+
+    using serialize = members<1>;
+
+    Type value{};
+};
+} // namespace numbers
+
+template <auto Object, typename Digest = std::array<std::byte, 20>>
+requires requires
+{
+    requires success(in{Digest{}}(std::array<std::byte, 20>{}));
+}
+constexpr auto sha1()
+{
+    using numbers::big_endian;
+    auto rotate_left = [](auto n, auto c) {
+        return (n << c) | (n >> ((sizeof(n) * CHAR_BIT) - c));
+    };
+    auto align = [](auto v, auto a) { return (v + (a - 1)) / a * a; };
+
+    auto h0 = big_endian{std::uint32_t{0x67452301u}};
+    auto h1 = big_endian{std::uint32_t{0xefcdab89u}};
+    auto h2 = big_endian{std::uint32_t{0x98badcfeu}};
+    auto h3 = big_endian{std::uint32_t{0x10325476u}};
+    auto h4 = big_endian{std::uint32_t{0xc3d2e1f0u}};
+
+    constexpr auto original_message = to_bytes<Object>();
+    constexpr auto chunk_size = 512 / CHAR_BIT;
+    constexpr auto message = to_bytes<
+        original_message,
+        std::byte{0x80},
+        std::array<std::byte,
+                   align(original_message.size() + sizeof(std::byte{0x80}),
+                         chunk_size) -
+                       original_message.size() - sizeof(std::byte{0x80}) -
+                       sizeof(std::uint64_t{original_message.size()})>{},
+        big_endian<std::uint64_t>{original_message.size() * CHAR_BIT}>();
+
+    for (auto chunk :
+         from_bytes<message,
+                    std::array<std::array<big_endian<std::uint32_t>, 16>,
+                               message.size() / chunk_size>>()) {
+        std::array<big_endian<std::uint32_t>, 80> w;
+        std::copy(std::begin(chunk), std::end(chunk), std::begin(w));
+
+        for (std::size_t i = 16; i < w.size(); ++i) {
+            w[i] = rotate_left(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16],
+                               1);
+        }
+
+        auto a = h0;
+        auto b = h1;
+        auto c = h2;
+        auto d = h3;
+        auto e = h4;
+
+        for (std::size_t i = 0; i < w.size(); ++i) {
+            auto f = big_endian{std::uint32_t{}};
+            auto k = big_endian{std::uint32_t{}};
+            if (i <= 19) {
+                f = (b & c) | ((~b) & d);
+                k = big_endian{std::uint32_t{0x5a827999u}};
+            } else if (i <= 39) {
+                f = b ^ c ^ d;
+                k = big_endian{std::uint32_t{0x6ed9eba1u}};
+            } else if (i <= 59) {
+                f = (b & c) | (b & d) | (c & d);
+                k = big_endian{std::uint32_t{0x8f1bbcdcu}};
+            } else {
+                f = b ^ c ^ d;
+                k = big_endian{std::uint32_t{0xca62c1d6u}};
+            }
+
+            auto temp = rotate_left(a, 5) + f + e + k + w[i];
+            e = d;
+            d = c;
+            c = rotate_left(b, 30);
+            b = a;
+            a = temp;
+        }
+
+        h0 += a;
+        h1 += b;
+        h2 += c;
+        h3 += d;
+        h4 += e;
+    }
+
+    std::array<std::byte, 20> digest_data;
+    out{digest_data}(h0, h1, h2, h3, h4).or_throw();
+
+    Digest digest;
+    in{digest_data}(digest).or_throw();
+    return digest;
+}
+
+template <auto Object, typename Digest = std::array<std::byte, 32>>
+requires requires
+{
+    requires success(in{Digest{}}(std::array<std::byte, 32>{}));
+}
+constexpr auto sha256()
+{
+    using numbers::big_endian;
+    auto rotate_right = [](auto n, auto c) {
+        return (n >> c) | (n << ((sizeof(n) * CHAR_BIT) - c));
+    };
+    auto align = [](auto v, auto a) { return (v + (a - 1)) / a * a; };
+
+    auto h0 = big_endian{0x6a09e667u};
+    auto h1 = big_endian{0xbb67ae85u};
+    auto h2 = big_endian{0x3c6ef372u};
+    auto h3 = big_endian{0xa54ff53au};
+    auto h4 = big_endian{0x510e527fu};
+    auto h5 = big_endian{0x9b05688cu};
+    auto h6 = big_endian{0x1f83d9abu};
+    auto h7 = big_endian{0x5be0cd19u};
+
+    std::array k{big_endian{0x428a2f98u}, big_endian{0x71374491u},
+                 big_endian{0xb5c0fbcfu}, big_endian{0xe9b5dba5u},
+                 big_endian{0x3956c25bu}, big_endian{0x59f111f1u},
+                 big_endian{0x923f82a4u}, big_endian{0xab1c5ed5u},
+                 big_endian{0xd807aa98u}, big_endian{0x12835b01u},
+                 big_endian{0x243185beu}, big_endian{0x550c7dc3u},
+                 big_endian{0x72be5d74u}, big_endian{0x80deb1feu},
+                 big_endian{0x9bdc06a7u}, big_endian{0xc19bf174u},
+                 big_endian{0xe49b69c1u}, big_endian{0xefbe4786u},
+                 big_endian{0x0fc19dc6u}, big_endian{0x240ca1ccu},
+                 big_endian{0x2de92c6fu}, big_endian{0x4a7484aau},
+                 big_endian{0x5cb0a9dcu}, big_endian{0x76f988dau},
+                 big_endian{0x983e5152u}, big_endian{0xa831c66du},
+                 big_endian{0xb00327c8u}, big_endian{0xbf597fc7u},
+                 big_endian{0xc6e00bf3u}, big_endian{0xd5a79147u},
+                 big_endian{0x06ca6351u}, big_endian{0x14292967u},
+                 big_endian{0x27b70a85u}, big_endian{0x2e1b2138u},
+                 big_endian{0x4d2c6dfcu}, big_endian{0x53380d13u},
+                 big_endian{0x650a7354u}, big_endian{0x766a0abbu},
+                 big_endian{0x81c2c92eu}, big_endian{0x92722c85u},
+                 big_endian{0xa2bfe8a1u}, big_endian{0xa81a664bu},
+                 big_endian{0xc24b8b70u}, big_endian{0xc76c51a3u},
+                 big_endian{0xd192e819u}, big_endian{0xd6990624u},
+                 big_endian{0xf40e3585u}, big_endian{0x106aa070u},
+                 big_endian{0x19a4c116u}, big_endian{0x1e376c08u},
+                 big_endian{0x2748774cu}, big_endian{0x34b0bcb5u},
+                 big_endian{0x391c0cb3u}, big_endian{0x4ed8aa4au},
+                 big_endian{0x5b9cca4fu}, big_endian{0x682e6ff3u},
+                 big_endian{0x748f82eeu}, big_endian{0x78a5636fu},
+                 big_endian{0x84c87814u}, big_endian{0x8cc70208u},
+                 big_endian{0x90befffau}, big_endian{0xa4506cebu},
+                 big_endian{0xbef9a3f7u}, big_endian{0xc67178f2u}};
+
+    constexpr auto original_message = to_bytes<Object>();
+    constexpr auto chunk_size = 512 / CHAR_BIT;
+    constexpr auto message = to_bytes<
+        original_message,
+        std::byte{0x80},
+        std::array<std::byte,
+                   align(original_message.size() + sizeof(std::byte{0x80}),
+                         chunk_size) -
+                       original_message.size() - sizeof(std::byte{0x80}) -
+                       sizeof(std::uint64_t{original_message.size()})>{},
+        big_endian<std::uint64_t>{original_message.size() * CHAR_BIT}>();
+
+    for (auto chunk :
+         from_bytes<message,
+                    std::array<std::array<big_endian<std::uint32_t>, 16>,
+                               message.size() / chunk_size>>()) {
+        std::array<big_endian<std::uint32_t>, 64> w;
+        std::copy(std::begin(chunk), std::end(chunk), std::begin(w));
+
+        for (std::size_t i = 16; i < w.size(); ++i) {
+            auto s0 = rotate_right(w[i - 15], 7) ^
+                      rotate_right(w[i - 15], 18) ^ (w[i - 15] >> 3);
+            auto s1 = rotate_right(w[i - 2], 17) ^
+                      rotate_right(w[i - 2], 19) ^ (w[i - 2] >> 10);
+            w[i] = w[i - 16] + s0 + w[i - 7] + s1;
+        }
+
+        auto a = h0;
+        auto b = h1;
+        auto c = h2;
+        auto d = h3;
+        auto e = h4;
+        auto f = h5;
+        auto g = h6;
+        auto h = h7;
+
+        for (std::size_t i = 0; i < w.size(); ++i) {
+            auto s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^
+                      rotate_right(e, 25);
+            auto ch = (e & f) ^ ((~e) & g);
+            auto temp1 = h + s1 + ch + k[i] + w[i];
+            auto s0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^
+                      rotate_right(a, 22);
+            auto maj = (a & b) ^ (a & c) ^ (b & c);
+            auto temp2 = s0 + maj;
+
+            h = g;
+            g = f;
+            f = e;
+            e = d + temp1;
+            d = c;
+            c = b;
+            b = a;
+            a = temp1 + temp2;
+        }
+
+        h0 = h0 + a;
+        h1 = h1 + b;
+        h2 = h2 + c;
+        h3 = h3 + d;
+        h4 = h4 + e;
+        h5 = h5 + f;
+        h6 = h6 + g;
+        h7 = h7 + h;
+    }
+
+    std::array<std::byte, 32> digest_data;
+    out{digest_data}(h0, h1, h2, h3, h4, h5, h6, h7).or_throw();
+
+    Digest digest;
+    in{digest_data}(digest).or_throw();
+    return digest;
+}
+
+inline namespace literals
+{
+inline namespace string_literals
+{
+template <string_literal String>
+constexpr auto operator""_s()
+{
+    return String;
+}
+
+template <string_literal String>
+constexpr auto operator""_b()
+{
+    return to_bytes<String>();
+}
+
+template <string_literal String>
+constexpr auto operator""_decode_hex()
+{
+    constexpr auto tolower = [](auto c) {
+        if ('A' <= c && c <= 'Z') {
+            return decltype(c)(c - 'A' + 'a');
+        }
+        return c;
+    };
+
+    static_assert(String.size() % 2 == 0);
+
+    static_assert(
+        std::find_if(std::begin(String), std::end(String), [&](auto c) {
+            return !(('0' <= c && c <= '9') ||
+                     ('a' <= tolower(c) && tolower(c) <= 'f'));
+        }) == std::end(String));
+
+    auto hex = [](auto c) {
+        if ('a' <= c) {
+            return c - 'a' + 0xa;
+        } else {
+            return c - '0';
+        }
+    };
+
+    std::array<std::byte, String.size() / 2> data;
+    for (std::size_t i = 0; auto & b : data) {
+        auto left = tolower(String[i]);
+        auto right = tolower(String[i + 1]);
+        b = std::byte((hex(left) << (CHAR_BIT/2)) | hex(right));
+        i += 2;
+    }
+    return data;
+}
+
+template <string_literal String>
+constexpr auto operator""_sha1()
+{
+    return sha1<String>();
+}
+
+template <string_literal String>
+constexpr auto operator""_sha256()
+{
+    return sha256<String>();
+}
+
+template <string_literal String>
+constexpr auto operator""_sha1_int()
+{
+    return id_v<sha1<String>(), sizeof(int)>;
+}
+
+template <string_literal String>
+constexpr auto operator""_sha256_int()
+{
+    return id_v<sha256<String>(), sizeof(int)>;
+}
+} // namespace string_literals
+} // namespace literals
+
+template <typename... Arguments>
+using vector1b = sized_t<std::vector<Arguments...>, unsigned char>;
+template <typename... Arguments>
+using vector2b = sized_t<std::vector<Arguments...>, std::uint16_t>;
+template <typename... Arguments>
+using vector4b = sized_t<std::vector<Arguments...>, std::uint32_t>;
+template <typename... Arguments>
+using vector8b = sized_t<std::vector<Arguments...>, std::uint64_t>;
+template <typename... Arguments>
+using static_vector = unsized_t<std::vector<Arguments...>>;
+template <typename... Arguments>
+using native_vector = sized_t<std::vector<Arguments...>, typename std::vector<Arguments...>::size_type>;
+
+template <typename... Arguments>
+using span1b = sized_t<std::span<Arguments...>, unsigned char>;
+template <typename... Arguments>
+using span2b = sized_t<std::span<Arguments...>, std::uint16_t>;
+template <typename... Arguments>
+using span4b = sized_t<std::span<Arguments...>, std::uint32_t>;
+template <typename... Arguments>
+using span8b = sized_t<std::span<Arguments...>, std::uint64_t>;
+template <typename... Arguments>
+using static_span = unsized_t<std::span<Arguments...>>;
+template <typename... Arguments>
+using native_span = sized_t<std::span<Arguments...>, typename std::span<Arguments...>::size_type>;
+
+using string1b = sized_t<std::string, unsigned char>;
+using string2b = sized_t<std::string, std::uint16_t>;
+using string4b = sized_t<std::string, std::uint32_t>;
+using string8b = sized_t<std::string, std::uint64_t>;
+using static_string = unsized_t<std::string>;
+using native_string = sized_t<std::string, std::string::size_type>;
+
+using string_view1b = sized_t<std::string_view, unsigned char>;
+using string_view2b = sized_t<std::string_view, std::uint16_t>;
+using string_view4b = sized_t<std::string_view, std::uint32_t>;
+using string_view8b = sized_t<std::string_view, std::uint64_t>;
+using static_string_view = unsized_t<std::string_view>;
+using native_string_view = sized_t<std::string_view, std::string_view::size_type>;
+
+using wstring1b = sized_t<std::wstring, unsigned char>;
+using wstring2b = sized_t<std::wstring, std::uint16_t>;
+using wstring4b = sized_t<std::wstring, std::uint32_t>;
+using wstring8b = sized_t<std::wstring, std::uint64_t>;
+using static_wstring = unsized_t<std::wstring>;
+using native_wstring = sized_t<std::wstring, std::wstring::size_type>;
+
+using wstring_view1b = sized_t<std::wstring_view, unsigned char>;
+using wstring_view2b = sized_t<std::wstring_view, std::uint16_t>;
+using wstring_view4b = sized_t<std::wstring_view, std::uint32_t>;
+using wstring_view8b = sized_t<std::wstring_view, std::uint64_t>;
+using static_wstring_view = unsized_t<std::wstring_view>;
+using native_wstring_view = sized_t<std::wstring_view, std::wstring_view::size_type>;
+
+using u8string1b = sized_t<std::u8string, unsigned char>;
+using u8string2b = sized_t<std::u8string, std::uint16_t>;
+using u8string4b = sized_t<std::u8string, std::uint32_t>;
+using u8string8b = sized_t<std::u8string, std::uint64_t>;
+using static_u8string = unsized_t<std::u8string>;
+using native_u8string = sized_t<std::u8string, std::u8string::size_type>;
+
+using u8string_view1b = sized_t<std::u8string_view, unsigned char>;
+using u8string_view2b = sized_t<std::u8string_view, std::uint16_t>;
+using u8string_view4b = sized_t<std::u8string_view, std::uint32_t>;
+using u8string_view8b = sized_t<std::u8string_view, std::uint64_t>;
+using static_u8string_view = unsized_t<std::u8string_view>;
+using native_u8string_view = sized_t<std::u8string_view, std::u8string_view::size_type>;
+
+using u16string1b = sized_t<std::u16string, unsigned char>;
+using u16string2b = sized_t<std::u16string, std::uint16_t>;
+using u16string4b = sized_t<std::u16string, std::uint32_t>;
+using u16string8b = sized_t<std::u16string, std::uint64_t>;
+using static_u16string = unsized_t<std::u16string>;
+using native_u16string = sized_t<std::u16string, std::u16string::size_type>;
+
+using u16string_view1b = sized_t<std::u16string_view, unsigned char>;
+using u16string_view2b = sized_t<std::u16string_view, std::uint16_t>;
+using u16string_view4b = sized_t<std::u16string_view, std::uint32_t>;
+using u16string_view8b = sized_t<std::u16string_view, std::uint64_t>;
+using static_u16string_view = unsized_t<std::u16string_view>;
+using native_u16string_view = sized_t<std::u16string_view, std::u16string_view::size_type>;
+
+using u32string1b = sized_t<std::u32string, unsigned char>;
+using u32string2b = sized_t<std::u32string, std::uint16_t>;
+using u32string4b = sized_t<std::u32string, std::uint32_t>;
+using u32string8b = sized_t<std::u32string, std::uint64_t>;
+using static_u32string = unsized_t<std::u32string>;
+using native_u32string = sized_t<std::u32string, std::u32string::size_type>;
+
+using u32string_view1b = sized_t<std::u32string_view, unsigned char>;
+using u32string_view2b = sized_t<std::u32string_view, std::uint16_t>;
+using u32string_view4b = sized_t<std::u32string_view, std::uint32_t>;
+using u32string_view8b = sized_t<std::u32string_view, std::uint64_t>;
+using static_u32string_view = unsized_t<std::u32string_view>;
+using native_u32string_view = sized_t<std::u32string_view, std::u32string_view::size_type>;
+
+} // namespace zpp::bits
+
+#endif // ZPP_BITS_H
+
index e1d2f72369af5c5a78d214208e1f43fd4180fcad..01f642a3870259502694b6f91ffc248f3531cd57 100644 (file)
@@ -774,12 +774,6 @@ int RadosBucket::put_info(const DoutPrefixProvider* dpp, bool exclusive, ceph::r
   return store->getRados()->put_bucket_instance_info(info, exclusive, mtime, &attrs, dpp, y);
 }
 
-/* Make sure to call get_bucket_info() if you need it first */
-bool RadosBucket::is_owner(User* user)
-{
-  return (info.owner.compare(user->get_id()) == 0);
-}
-
 int RadosBucket::check_empty(const DoutPrefixProvider* dpp, optional_yield y)
 {
   return store->getRados()->check_bucket_empty(dpp, info, y);
index 28a37ce7dbf18437489f1aa1856da9415a11ea3d..cdb4ad95e21db6de3ebd05c51544e47043e625e9 100644 (file)
@@ -571,7 +571,6 @@ class RadosBucket : public StoreBucket {
     virtual int check_bucket_shards(const DoutPrefixProvider* dpp, optional_yield y) override;
     virtual int chown(const DoutPrefixProvider* dpp, User& new_user, optional_yield y) override;
     virtual int put_info(const DoutPrefixProvider* dpp, bool exclusive, ceph::real_time mtime, optional_yield y) override;
-    virtual bool is_owner(User* user) override;
     virtual int check_empty(const DoutPrefixProvider* dpp, optional_yield y) override;
     virtual int check_quota(const DoutPrefixProvider *dpp, RGWQuota& quota, uint64_t obj_size, optional_yield y, bool check_size_only = false) override;
     virtual int merge_and_store_attrs(const DoutPrefixProvider* dpp, Attrs& attrs, optional_yield y) override;
index 366a122eb33746884ce82bff37fceb8c0b6a19d3..57ad9f77f4f54e68fbf25d2cddcdc7576f210029 100644 (file)
@@ -3355,7 +3355,7 @@ void RGWCreateBucket::execute(optional_yield y)
     }
   }
 
-  s->bucket_owner.set_id(s->user->get_id()); /* XXX dang use s->bucket->owner */
+  s->bucket_owner.set_id(s->user->get_id());
   s->bucket_owner.set_name(s->user->get_display_name());
 
   string zonegroup_id;
@@ -4639,7 +4639,7 @@ void RGWPostObj::execute(optional_yield y)
       cs_info.compression_type = plugin->get_type_name();
       cs_info.orig_size = s->obj_size;
       cs_info.compressor_message = compressor->get_compressor_message();
-      cs_info.blocks = move(compressor->get_compression_blocks());
+      cs_info.blocks = std::move(compressor->get_compression_blocks());
       encode(cs_info, tmp);
       emplace_attr(RGW_ATTR_COMPRESSION, std::move(tmp));
     }
@@ -4896,7 +4896,6 @@ void RGWPutMetadataObject::pre_exec()
 
 void RGWPutMetadataObject::execute(optional_yield y)
 {
-  rgw_obj target_obj;
   rgw::sal::Attrs attrs, rmattrs;
 
   s->object->set_atomic();
@@ -4912,7 +4911,7 @@ void RGWPutMetadataObject::execute(optional_yield y)
   }
 
   /* check if obj exists, read orig attrs */
-  op_ret = s->object->get_obj_attrs(s->yield, s, &target_obj);
+  op_ret = s->object->get_obj_attrs(s->yield, s);
   if (op_ret < 0) {
     return;
   }
index c7a2371ec8b8744ee92b58ee2d093aff535ccb41..030395dd7a9f7e9419508bc7d8e584d6c3bf88fb 100644 (file)
@@ -589,7 +589,7 @@ send_data:
 
 int RGWGetObj_ObjStore_S3::get_decrypt_filter(std::unique_ptr<RGWGetObj_Filter> *filter, RGWGetObj_Filter* cb, bufferlist* manifest_bl)
 {
-  if (skip_decrypt) { // bypass decryption for multisite sync requests
+  if (skip_decrypt || !manifest_bl) { // bypass decryption for multisite sync requests
     return 0;
   }
 
index 042eab0be7257ee8c2451e6f870eaa40c6df9bd8..a99f18428d66d4b412cf6fa8403323953f3e0303 100644 (file)
@@ -56,6 +56,9 @@ extern rgw::sal::Driver* newMotrStore(CephContext *cct);
 #ifdef WITH_RADOSGW_DAOS
 extern rgw::sal::Driver* newDaosStore(CephContext *cct);
 #endif
+#ifdef WITH_RADOSGW_POSIX
+extern rgw::sal::Driver* newPOSIXDriver(rgw::sal::Driver* next);
+#endif
 extern rgw::sal::Driver* newBaseFilter(rgw::sal::Driver* next);
 #ifdef WITH_RADOSGW_D4N
 extern rgw::sal::Driver* newD4NFilter(rgw::sal::Driver* next);
@@ -217,6 +220,7 @@ rgw::sal::Driver* DriverManager::init_storage_provider(const DoutPrefixProvider*
     }
   }
 #endif
+  ldpp_dout(dpp, 20) << "Filter name: " << cfg.filter_name << dendl;
 
   if (cfg.filter_name.compare("base") == 0) {
     rgw::sal::Driver* next = driver;
@@ -240,6 +244,19 @@ rgw::sal::Driver* DriverManager::init_storage_provider(const DoutPrefixProvider*
     }
   }
 #endif
+#ifdef WITH_RADOSGW_POSIX
+  else if (cfg.filter_name.compare("posix") == 0) {
+    rgw::sal::Driver* next = driver;
+    ldpp_dout(dpp, 20) << "Creating POSIX driver" << dendl;
+    driver = newPOSIXDriver(next);
+
+    if (driver->initialize(cct, dpp) < 0) {
+      delete driver;
+      delete next;
+      return nullptr;
+    }
+  }
+#endif
 
   return driver;
 }
@@ -365,6 +382,8 @@ DriverManager::Config DriverManager::get_config(bool admin, CephContext* cct)
   const auto& config_filter = g_conf().get_val<std::string>("rgw_filter");
   if (config_filter == "base") {
     cfg.filter_name = "base";
+  } else if (config_filter == "posix") {
+    cfg.filter_name = "posix";
   }
 #ifdef WITH_RADOSGW_D4N
   else if (config_filter == "d4n") {
index 9886bd5b590962727e62abb3a728f3457cf25902..80a619f122c19567a218719d4c94862b40dbd3ee 100644 (file)
@@ -339,12 +339,6 @@ namespace rgw::sal {
 
   }
 
-  /* Make sure to call get_bucket_info() if you need it first */
-  bool DBBucket::is_owner(User* user)
-  {
-    return (info.owner.compare(user->get_id()) == 0);
-  }
-
   int DBBucket::check_empty(const DoutPrefixProvider *dpp, optional_yield y)
   {
     /* XXX: Check if bucket contains any objects */
@@ -572,6 +566,9 @@ namespace rgw::sal {
 
   bool DBZone::has_zonegroup_api(const std::string& api) const
   {
+    if (api == "default")
+      return true;
+
     return false;
   }
 
@@ -1721,7 +1718,11 @@ namespace rgw::sal {
   int DBStore::get_zonegroup(const std::string& id, std::unique_ptr<ZoneGroup>* zg)
   {
     /* XXX: for now only one zonegroup supported */
-    ZoneGroup* group = new DBZoneGroup(this, std::make_unique<RGWZoneGroup>());
+    std::unique_ptr<RGWZoneGroup> rzg =
+        std::make_unique<RGWZoneGroup>("default", "default");
+    rzg->api_name = "default";
+    rzg->is_master = true;
+    ZoneGroup* group = new DBZoneGroup(this, std::move(rzg));
     if (!group)
       return -ENOMEM;
 
index 38b14d0d28c1ca4f17168da94b72647c776f3225..2c84f709872132cdcecbafd088c64d129cebe430 100644 (file)
@@ -204,7 +204,6 @@ protected:
       virtual int check_bucket_shards(const DoutPrefixProvider *dpp, optional_yield y) override;
       virtual int chown(const DoutPrefixProvider *dpp, User& new_user, optional_yield y) override;
       virtual int put_info(const DoutPrefixProvider *dpp, bool exclusive, ceph::real_time mtime, optional_yield y) override;
-      virtual bool is_owner(User* user) override;
       virtual int check_empty(const DoutPrefixProvider *dpp, optional_yield y) override;
       virtual int check_quota(const DoutPrefixProvider *dpp, RGWQuota& quota, uint64_t obj_size, optional_yield y, bool check_size_only = false) override;
       virtual int merge_and_store_attrs(const DoutPrefixProvider *dpp, Attrs& attrs, optional_yield y) override;
@@ -318,7 +317,10 @@ protected:
     public:
       DBZone(DBStore* _store) : store(_store) {
        realm = new RGWRealm();
-        zonegroup = new DBZoneGroup(store, std::make_unique<RGWZoneGroup>());
+       std::unique_ptr<RGWZoneGroup> rzg = std::make_unique<RGWZoneGroup>("default", "default");
+       rzg->api_name = "default";
+       rzg->is_master = true;
+        zonegroup = new DBZoneGroup(store, std::move(rzg));
         zone_public_config = new RGWZone();
         zone_params = new RGWZoneParams();
         current_period = new RGWPeriod();
index b9d9be4c6e5cc532bf57850185a0b2da5c611526..be11b87163227cf4c17f51b62b86532787f6f427 100644 (file)
@@ -120,6 +120,7 @@ class StoreBucket : public Bucket {
     virtual int set_attrs(Attrs a) override { attrs = a; return 0; }
     virtual void set_owner(rgw::sal::User* _owner) override {
       owner = _owner;
+      info.owner = owner->get_id();
     }
     virtual void set_count(uint64_t _count) override {
       ent.count = _count;
@@ -128,6 +129,8 @@ class StoreBucket : public Bucket {
       ent.size = _size;
     }
     virtual User* get_owner(void) override { return owner; };
+    /* Make sure to call get_bucket_info() if you need it first */
+    virtual bool is_owner(User* user) override { return (info.owner.compare(user->get_id()) == 0); }
     virtual ACLOwner get_acl_owner(void) override { return ACLOwner(info.owner); };
     virtual bool empty() const override { return info.bucket.name.empty(); }
     virtual const std::string& get_name() const override { return info.bucket.name; }
@@ -176,6 +179,7 @@ class StoreBucket : public Bucket {
         optional_yield y, const DoutPrefixProvider *dpp) override {return 0;}
 
     friend class BucketList;
+
   protected:
     virtual void set_ent(RGWBucketEnt& _ent) { ent = _ent; info.bucket = ent.bucket; info.placement_rule = ent.placement_rule; }
 };
index 0f99597c21e230d616edc06d1cb63c3feb068e77..2743802f830aacee930f2a459aad72c75156d6bd 100644 (file)
@@ -315,3 +315,14 @@ target_link_libraries(radosgw-cr-test ${rgw_libs} librados
   OATH::OATH
   ${CURL_LIBRARIES} ${EXPAT_LIBRARIES} ${BLKID_LIBRARIES}
   GTest::GTest)
+
+# unittest_posix_bucket_cache
+add_executable(unittest_posix_bucket_cache
+       test_posix_bucket_cache.cc)
+add_ceph_unittest(unittest_posix_bucket_cache)
+target_compile_definitions(unittest_posix_bucket_cache PUBLIC LMDB_SAFE_NO_CPP_UTILITIES)
+target_include_directories(unittest_posix_bucket_cache
+  SYSTEM PRIVATE "${CMAKE_SOURCE_DIR}/src/rgw"
+  SYSTEM PRIVATE "${CMAKE_SOURCE_DIR}/src/rgw/driver/posix")
+target_link_libraries(unittest_posix_bucket_cache  ${UNITTEST_LIBS}
+  ${rgw_libs} ${LMDB_LIBRARIES})
diff --git a/src/test/rgw/test_posix_bucket_cache.cc b/src/test/rgw/test_posix_bucket_cache.cc
new file mode 100644 (file)
index 0000000..258650a
--- /dev/null
@@ -0,0 +1,428 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+#include "bucket_cache.h"
+#include <iostream>
+#include <fstream>
+#include <filesystem>
+#include <string>
+#include <string_view>
+#include <random>
+#include <ranges>
+#include <thread>
+#include <stdint.h>
+
+#undef FMT_HEADER_ONLY
+#define FMT_HEADER_ONLY 1
+#include <fmt/format.h>
+
+#include <gtest/gtest.h>
+
+using namespace std::chrono_literals;
+
+namespace {
+
+  namespace sf = std::filesystem; 
+
+  std::string bucket_root = "bucket_root";
+  std::string database_root = "lmdb_root";
+  std::string bucket1_name = "stanley";
+  std::string bucket1_marker = ""; // start at the beginning
+
+  std::random_device rd;
+  std::mt19937 mt(rd());
+
+  DoutPrefixProvider* dpp{nullptr};
+  std::string tdir1{"tdir1"};
+  std::string tdir2{"tdir2"};
+  std::uniform_int_distribution<> dist_1m(1, 1000000);
+  std::vector<std::string> bvec;
+
+  class MockSalDriver
+  {
+  public:
+    /* called by BucketCache layer when a new object is discovered
+     * by inotify or similar */
+    int mint_listing_entry(
+      const std::string& bucket, rgw_bucket_dir_entry& bde /* OUT */) {
+
+      return 0;
+    }
+  }; /* MockSalDriver */
+
+  class MockSalBucket
+  {
+    std::string name;
+  public:
+    MockSalBucket(const std::string& name)
+      : name(name)
+      {}
+    const std::string& get_name() {
+      return name;
+    }
+
+    using fill_cache_cb_t = file::listing::fill_cache_cb_t;
+
+    int fill_cache(const DoutPrefixProvider* dpp, optional_yield y, fill_cache_cb_t cb) {
+      sf::path rp{bucket_root};
+      sf::path bp{rp / name};
+      if (! (sf::exists(rp) && sf::is_directory(rp))) {
+       std::cerr << fmt::format("{} bucket {} invalid", __func__, name)
+                 << std::endl;
+       exit(1);
+      }
+      for (const auto& dir_entry : sf::directory_iterator{bp}) {
+       rgw_bucket_dir_entry bde{};
+       auto fname = dir_entry.path().filename().string();
+       bde.key.name = fname;
+       cb(dpp, bde);
+      }
+      return 0;
+    } /* fill_cache */
+
+  }; /* MockSalBucket */
+
+  using BucketCache = file::listing::BucketCache<MockSalDriver, MockSalBucket>;
+  MockSalDriver sal_driver;
+  BucketCache* bucket_cache{nullptr};
+
+} // anonymous ns
+
+namespace sf = std::filesystem;
+
+TEST(BucketCache, SetupTDir1)
+{
+  sf::path tp{sf::path{bucket_root} / tdir1};
+  sf::remove_all(tp);
+  sf::create_directory(tp);
+
+  /* generate 100K unique files in random order */
+  std::string fbase{"file_"};
+  for (int ix = 0; ix < 100000; ++ix) {
+  retry:
+    auto n = dist_1m(mt);
+    sf::path ttp{tp / fmt::format("{}{}", fbase, n)};
+    if (sf::exists(ttp)) {
+      goto retry;
+    } else {
+      std::ofstream ofs(ttp);
+      ofs << "data for " << ttp << std::endl;
+      ofs.close();
+    }
+  } /* for 100K */
+} /* SetupTDir1 */
+
+TEST(BucketCache, InitBucketCache)
+{
+  bucket_cache = new BucketCache{&sal_driver, bucket_root, database_root}; // default tuning
+}
+
+auto func = [](const rgw_bucket_dir_entry& bde) -> bool
+  {
+    //std::cout << fmt::format("called back with {}", bde.key.name) << std::endl;
+    return true;
+  };
+
+TEST(BucketCache, ListTDir1)
+{
+  MockSalBucket sb{tdir1};
+  std::string marker = bucket1_marker;
+  (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, func);
+}
+
+
+TEST(BucketCache, SetupEmpty)
+{
+  sf::path tp{sf::path{bucket_root} / tdir2};
+  sf::remove_all(tp);
+  sf::create_directory(tp);
+
+  /* generate no objects in tdir2 */
+
+} /* SetupEmpty */
+
+TEST(BucketCache, ListEmpty)
+{
+  MockSalBucket sb{tdir2};
+  std::string marker = bucket1_marker;
+  (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, func);
+}
+
+TEST(BucketCache, ListThreads) /* clocked at 21ms on lemon, and yes,
+                               * it did list 100K entries per thread */
+{
+  auto nthreads = 15;
+  std::vector<std::thread> threads;
+
+  auto func = [](const rgw_bucket_dir_entry& bde) -> int
+    {
+      //std::cout << fmt::format("called back with {}", bde.key.name) << std::endl;
+      return 0;
+    };
+
+  MockSalBucket sb{tdir1};
+  std::string marker = bucket1_marker;
+
+  for (int ix = 0; ix < nthreads; ++ix) {
+    threads.push_back(std::thread([&]() {
+      (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, func);
+    }));
+  }
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+TEST(BucketCache, SetupRecycle1)
+{
+  int nbuckets = 5;
+  int nfiles = 10;
+
+  bvec = [&]() {
+    std::vector<std::string> v;
+    for (int ix = 0; ix < nbuckets; ++ix) {
+      v.push_back(fmt::format("recyle_{}", ix));
+    }
+    return v;
+  }();
+
+  for (auto& bucket : bvec) {
+    sf::path tp{sf::path{bucket_root} / bucket};
+    sf::remove_all(tp);
+    sf::create_directory(tp);
+
+    std::string fbase{"file_"};
+    for (int ix = 0; ix < nfiles; ++ix) {
+    retry:
+      auto n = dist_1m(mt);
+      sf::path ttp{tp / fmt::format("{}{}", fbase, n)};
+      if (sf::exists(ttp)) {
+       goto retry;
+      } else {
+       std::ofstream ofs(ttp);
+       ofs << "data for " << ttp << std::endl;
+       ofs.close();
+      }
+    } /* for buckets */
+  }
+} /* SetupRecycle1 */
+
+TEST(BucketCache, InitBucketCacheRecycle1)
+{
+  bucket_cache = new BucketCache{&sal_driver, bucket_root, database_root, 1, 1, 1, 1};
+}
+
+TEST(BucketCache, ListNRecycle1)
+{
+  /* the effect is to allocate a Bucket cache entry once, then recycle n-1 times */
+  for (auto& bucket : bvec) {
+    MockSalBucket sb{bucket};
+    std::string marker = bucket1_marker;
+    (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, func);
+  }
+  ASSERT_EQ(bucket_cache->recycle_count, 4);
+}
+
+TEST(BucketCache, TearDownBucketCacheRecycle1)
+{
+  delete bucket_cache;
+  bucket_cache = nullptr;
+}
+
+TEST(BucketCache, InitBucketCacheRecyclePartitions1)
+{
+  bucket_cache = new BucketCache{&sal_driver, bucket_root, database_root, 1, 1, 5 /* max partitions */, 1};
+}
+
+TEST(BucketCache, ListNRecyclePartitions1)
+{
+  /* the effect is to allocate a Bucket cache entry once, then recycle
+   * n-1 times--in addition, 5 cache partitions are mapped to 1 lru
+   * lane--verifying independence */
+  for (auto& bucket : bvec) {
+    MockSalBucket sb{bucket};
+    std::string marker = bucket1_marker;
+    (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, func);
+  }
+  ASSERT_EQ(bucket_cache->recycle_count, 4);
+}
+
+TEST(BucketCache, TearDownBucketCacheRecyclePartitions1)
+{
+  delete bucket_cache;
+  bucket_cache = nullptr;
+}
+
+TEST(BucketCache, SetupMarker1)
+{
+  int nfiles = 20;
+  std::string bucket{"marker1"};
+
+  sf::path tp{sf::path{bucket_root} / bucket};
+  sf::remove_all(tp);
+  sf::create_directory(tp);
+
+  std::string fbase{"file_"};
+  for (int ix = 0; ix < nfiles; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    std::ofstream ofs(ttp);
+    ofs << "data for " << ttp << std::endl;
+    ofs.close();
+  }
+} /* SetupMarker1 */
+
+TEST(BucketCache, InitBucketCacheMarker1)
+{
+  bucket_cache = new BucketCache{&sal_driver, bucket_root, database_root};
+}
+
+TEST(BucketCache, ListMarker1)
+{
+  std::string bucket{"marker1"};
+  std::string marker{"file_18"}; // midpoint+1
+  std::vector<std::string> names;
+
+  auto f = [&](const rgw_bucket_dir_entry& bde) -> int {
+    //std::cout << fmt::format("called back with {}", bde.key.name) << std::endl;
+    names.push_back(bde.key.name);
+    return true;
+  };
+
+  MockSalBucket sb{bucket};
+  (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, f);
+
+  ASSERT_EQ(names.size(), 10);
+  ASSERT_EQ(*names.begin(), "file_18");
+  ASSERT_EQ(*names.rbegin(), "file_9");
+}
+
+TEST(BucketCache, TearDownBucketCacheMarker1)
+{
+  delete bucket_cache;
+  bucket_cache = nullptr;
+}
+
+TEST(BucketCache, SetupInotify1)
+{
+  int nfiles = 20;
+  std::string bucket{"inotify1"};
+
+  sf::path tp{sf::path{bucket_root} / bucket};
+  sf::remove_all(tp);
+  sf::create_directory(tp);
+
+  std::string fbase{"file_"};
+  for (int ix = 0; ix < nfiles; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    std::ofstream ofs(ttp);
+    ofs << "data for " << ttp << std::endl;
+    ofs.close();
+  }
+} /* SetupInotify1 */
+
+TEST(BucketCache, InitBucketCacheInotify1)
+{
+  bucket_cache = new BucketCache{&sal_driver, bucket_root, database_root};
+}
+
+TEST(BucketCache, ListInotify1)
+{
+  std::string bucket{"inotify1"};
+  std::string marker{""};
+  std::vector<std::string> names;
+
+  auto f = [&](const rgw_bucket_dir_entry& bde) -> int {
+    //std::cout << fmt::format("called back with {}", bde.key.name) << std::endl;
+    names.push_back(bde.key.name);
+    return true;
+  };
+
+  MockSalBucket sb{bucket};
+
+  (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, f);
+  ASSERT_EQ(names.size(), 20);
+} /* ListInotify1 */
+
+TEST(BucketCache, UpdateInotify1)
+{
+  int nfiles = 10;
+  std::string bucket{"inotify1"};
+
+  sf::path tp{sf::path{bucket_root} / bucket};
+
+  /* add some */
+  std::string fbase{"upfile_"};
+  for (int ix = 0; ix < nfiles; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    std::ofstream ofs(ttp);
+    ofs << "data for " << ttp << std::endl;
+    ofs.close();
+  }
+
+  /* remove some */
+  fbase = "file_";
+  for (int ix = 5; ix < 10; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    sf::remove(ttp);
+  }
+
+  /* this step is async, temporally consistent */
+  std::cout << "waiting 1000ms for cache sync" << std::endl;
+  std::this_thread::sleep_for(1000ms);
+} /* SetupInotify1 */
+
+TEST(BucketCache, List2Inotify1)
+{
+  std::string bucket{"inotify1"};
+  std::string marker{""};
+  std::vector<std::string> names;
+
+  auto f = [&](const rgw_bucket_dir_entry& bde) -> int {
+    //std::cout << fmt::format("called back with {}", bde.key.name) << std::endl;
+    names.push_back(bde.key.name);
+    return true;
+  };
+
+  MockSalBucket sb{bucket};
+  (void) bucket_cache->list_bucket(dpp, null_yield, &sb, marker, f);
+  ASSERT_EQ(names.size(), 25);
+
+  /* check these */
+  sf::path tp{sf::path{bucket_root} / bucket};
+
+  std::string fbase{"upfile_"};
+  for (int ix = 0; ix < 10; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    ASSERT_TRUE(sf::exists(ttp));
+  }
+
+  /* remove some */
+  fbase = "file_";
+  for (int ix = 5; ix < 10; ++ix) {
+    sf::path ttp{tp / fmt::format("{}{}", fbase, ix)};
+    ASSERT_FALSE(sf::exists(ttp));
+  }
+} /* List2Inotify1 */
+
+TEST(BucketCache, TearDownInotify1)
+{
+  delete bucket_cache;
+  bucket_cache = nullptr;
+}
+
+int main (int argc, char *argv[])
+{
+
+  sf::path br{sf::path{bucket_root}};
+  sf::create_directory(br);
+  sf::remove_all(br);
+  sf::create_directory(br);
+
+  sf::path lr{sf::path{database_root}};
+  sf::create_directory(lr);
+  sf::remove_all(lr);
+  sf::create_directory(lr);
+
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}