aboutsummaryrefslogblamecommitdiff
path: root/contrib/epee/src/byte_slice.cpp
blob: 216049e5b00fc3630fd7a7594070b71c1bc9bb61 (plain) (tree)
















































































































































































































                                                                                               
// Copyright (c) 2019, 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.

#include <atomic>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <utility>

#include "byte_slice.h"

namespace epee
{
  struct byte_slice_data
  {
    byte_slice_data() noexcept
      : ref_count(1)
    {}

    virtual ~byte_slice_data() noexcept
    {}

    std::atomic<std::size_t> ref_count;
  };

  void release_byte_slice::operator()(byte_slice_data* ptr) const noexcept
  {
    if (ptr && --(ptr->ref_count) == 0)
    {
      ptr->~byte_slice_data();
      free(ptr);
    }
  }

  namespace
  {
    template<typename T>
    struct adapted_byte_slice final : byte_slice_data
    {
      explicit adapted_byte_slice(T&& buffer)
        : byte_slice_data(), buffer(std::move(buffer))
      {}

      virtual ~adapted_byte_slice() noexcept final override
      {}

      const T buffer;
    };

    // bytes "follow" this structure in memory slab
    struct raw_byte_slice final : byte_slice_data
    {
      raw_byte_slice() noexcept
        : byte_slice_data()
      {}

      virtual ~raw_byte_slice() noexcept final override
      {}
    };

    /* This technique is not-standard, but allows for the reference count and
       memory for the bytes (when given a list of spans) to be allocated in a
       single call. In that situation, the dynamic sized bytes are after/behind
       the raw_byte_slice class. The C runtime has to track the number of bytes
       allocated regardless, so free'ing is relatively easy. */

    template<typename T, typename... U>
    std::unique_ptr<T, release_byte_slice> allocate_slice(std::size_t extra_bytes, U&&... args)
    {
      if (std::numeric_limits<std::size_t>::max() - sizeof(T) < extra_bytes)
        throw std::bad_alloc{};

      void* const ptr = malloc(sizeof(T) + extra_bytes);
      if (ptr == nullptr)
        throw std::bad_alloc{};

      try
      {
        new (ptr) T{std::forward<U>(args)...};
      }
      catch (...)
      {
        free(ptr);
        throw;
      }
      return std::unique_ptr<T, release_byte_slice>{reinterpret_cast<T*>(ptr)};
    }
  } // anonymous

  byte_slice::byte_slice(byte_slice_data* storage, span<const std::uint8_t> portion) noexcept
    : storage_(storage), portion_(portion)
  {
    if (storage_)
      ++(storage_->ref_count);
  }

  template<typename T>
  byte_slice::byte_slice(const adapt_buffer, T&& buffer)
    : storage_(nullptr), portion_(to_byte_span(to_span(buffer)))
  {
    if (!buffer.empty())
      storage_ = allocate_slice<adapted_byte_slice<T>>(0, std::move(buffer));
  }

  byte_slice::byte_slice(std::initializer_list<span<const std::uint8_t>> sources)
    : byte_slice()
  {
    std::size_t space_needed = 0;
    for (const auto source : sources)
      space_needed += source.size();

    if (space_needed)
    {
      auto storage = allocate_slice<raw_byte_slice>(space_needed);
      span<std::uint8_t> out{reinterpret_cast<std::uint8_t*>(storage.get() + 1), space_needed};
      portion_ = {out.data(), out.size()};

      for (const auto source : sources)
      {
        std::memcpy(out.data(), source.data(), source.size());
        if (out.remove_prefix(source.size()) < source.size())
          throw std::bad_alloc{}; // size_t overflow on space_needed
      }
      storage_ = std::move(storage);
    }
  }

  byte_slice::byte_slice(std::string&& buffer)
    : byte_slice(adapt_buffer{}, std::move(buffer))
  {}

  byte_slice::byte_slice(std::vector<std::uint8_t>&& buffer)
    : byte_slice(adapt_buffer{}, std::move(buffer))
  {}

  byte_slice::byte_slice(byte_slice&& source) noexcept
    : storage_(std::move(source.storage_)), portion_(source.portion_)
  {
    source.portion_ = epee::span<const std::uint8_t>{};
  }

  byte_slice& byte_slice::operator=(byte_slice&& source) noexcept
  {
    storage_ = std::move(source.storage_);
    portion_ = source.portion_;
    if (source.storage_ == nullptr)
      source.portion_ = epee::span<const std::uint8_t>{};

    return *this;
  }

  std::size_t byte_slice::remove_prefix(std::size_t max_bytes) noexcept
  {
    max_bytes = portion_.remove_prefix(max_bytes);
    if (portion_.empty())
      storage_ = nullptr;
    return max_bytes;
  }

  byte_slice byte_slice::take_slice(const std::size_t max_bytes) noexcept
  {
    byte_slice out{};
    std::uint8_t const* const ptr = data();
    out.portion_ = {ptr, portion_.remove_prefix(max_bytes)};

    if (portion_.empty())
      out.storage_ = std::move(storage_); // no atomic inc/dec
    else
      out = {storage_.get(), out.portion_};

    return out;
  }

  byte_slice byte_slice::get_slice(const std::size_t begin, const std::size_t end) const
  {
    if (end < begin || portion_.size() < end)
      throw std::out_of_range{"bad slice range"};

    if (begin == end)
      return {};
    return {storage_.get(), {portion_.begin() + begin, end - begin}};
  }
} // epee