text: /*
   * Copyright 2014-2019 Real Logic Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * https://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #ifndef AERON_CONCURRENT_ATOMIC_BUFFER_H
  #define AERON_CONCURRENT_ATOMIC_BUFFER_H
  
  #include <cstdint>
  #include <string.h>
  #include <string>
  #include <array>
  #include <util/Exceptions.h>
  #include <util/StringUtil.h>
  #include <util/Index.h>
  
  #include <util/MacroUtil.h>
  
  #include "Atomic64.h"
  
  namespace aeron { namespace concurrent {
  
  /**
   * Wraps, but does not own, a buffer of memory for providing atomic operations. This is for providing a view.
   */
  class AtomicBuffer
  {
  public:
      constexpr AtomicBuffer() noexcept :
          m_buffer(nullptr),
          m_length(0)
      {
      }
  
      /**
       * Wrap a buffer of memory for a given length.
       *
       * @param buffer to be wrapped.
       * @param length of the buffer for bounds checking.
       */
      AtomicBuffer(std::uint8_t *buffer, size_t length) :
          m_buffer(buffer),
          m_length(static_cast<util::index_t>(length))
      {
  #if !defined(DISABLE_BOUNDS_CHECKS)
          if (AERON_COND_EXPECT(length > static_cast<size_t>(std::numeric_limits<util::index_t>::max()), true))
          {
              throw aeron::util::OutOfBoundsException(
                  aeron::util::strPrintf("length out of bounds[%p]: length=%lld", this, static_cast<long long>(length)),
                  SOURCEINFO);
          }
  #endif
      }
  
      /**
       * Wrap a buffer of memory for a given length and initialise the contents.
       *
       * @param buffer       to be wrapped.
       * @param length       of the buffer for bounds checking.
       * @param initialValue to set the memory too.
       */
      AtomicBuffer(std::uint8_t *buffer, size_t length, std::uint8_t initialValue) :
          m_buffer(buffer),
          m_length(static_cast<util::index_t>(length))
      {
  #if !defined(DISABLE_BOUNDS_CHECKS)
          if (AERON_COND_EXPECT(length > static_cast<size_t>(std::numeric_limits<util::index_t>::max()), true))
          {
              throw aeron::util::OutOfBoundsException(
                  aeron::util::strPrintf("length out of bounds[%p]. length=%lld", this, static_cast<long long>(length)),
                  SOURCEINFO);
          }
  #endif
  
          setMemory(0, static_cast<size_t>(length), initialValue);
      }
  
      template<size_t N>
      AtomicBuffer(std::array<std::uint8_t, N>& buffer)
      {
          wrap(buffer);
      }
  
      template<size_t N>
      AtomicBuffer(std::array<std::uint8_t, N>& buffer, std::uint8_t initialValue)
      {
          wrap(buffer);
          buffer.fill(initialValue);
      }
  
  #if COND_MOCK
      AtomicBuffer(const AtomicBuffer&) = default;
      AtomicBuffer& operator=(const AtomicBuffer&) = default;
      virtual ~AtomicBuffer() = default;
  #endif
  
      /**
       * Wrap a buffer of memory for a given length.
       *
       * @param buffer to be wrapped.
       * @param length of the buffer for bounds checking.
       */
      inline void wrap(std::uint8_t* buffer, size_t length)
      {
  #if !defined(DISABLE_BOUNDS_CHECKS)
          if (AERON_COND_EXPECT(length > static_cast<size_t>(std::numeric_limits<util::index_t>::max()), true))
          {
              throw aeron::util::OutOfBoundsException(
                  aeron::util::strPrintf("length out of bounds[%p]: length=%lld", this, static_cast<long long>(length)),
                  SOURCEINFO);
          }
  #endif
  
          m_buffer = buffer;
          m_length = static_cast<util::index_t>(length);
      }
  
      /**
       * Wrap an existing AtomicBuffer.
       *
       * @param buffer from which the address and length are used.
       */
      inline void wrap(const AtomicBuffer& buffer)
      {
          m_buffer = buffer.m_buffer;
          m_length = buffer.m_length;
      }
  
      template<size_t N>
      inline void wrap(std::array<std::uint8_t, N>& buffer)
      {
          static_assert(
              N <= std::numeric_limits<util::index_t>::max(),
              "requires the array to have a size that fits in an index_t");
  
          m_buffer = buffer.data();
          m_length = static_cast<util::index_t>(N);
      }
  
      /**
       * The capacity of the underlying buffer.
       *
       * @return the capacity of the underlying buffer.
       */
      inline util::index_t capacity() const
      {
          return m_length;
      }
  
      /**
       * Update the capacity of the underlying buffer.
       *
       * @param length to be used for the new capacity.
       */
      inline void capacity(size_t length)
      {
  #if !defined(DISABLE_BOUNDS_CHECKS)
          if (AERON_COND_EXPECT(length > static_cast<size_t>(std::numeric_limits<util::index_t>::max()), true))
          {
              throw aeron::util::OutOfBoundsException(
                  aeron::util::strPrintf("length out of bounds[%p]: length=%lld", this, static_cast<long long>(length)),
                  SOURCEINFO);
          }
  #endif
  
          m_length = static_cast<util::index_t>(length);
      }
  
      /**
       * Get a pointer to the underlying buffer.
       *
       * @return a pointer to the underlying buffer
       */
      inline std::uint8_t * buffer() const
      {
          return m_buffer;
      }
  
      inline char * sbeData() const
      {
          return reinterpret_cast<char *>(m_buffer);
      }
  
      template <typename struct_t>
      struct_t& overlayStruct()
      {
          boundsCheck(0, sizeof(struct_t));
          return *reinterpret_cast<struct_t*>(m_buffer);
      }
  
      template <typename struct_t>
      struct_t& overlayStruct(util::index_t offset)
      {
          boundsCheck(offset, sizeof(struct_t));
          return *reinterpret_cast<struct_t*>(m_buffer + offset);
      }
  
      template <typename struct_t>
      const struct_t& overlayStruct(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(struct_t));
          return *reinterpret_cast<struct_t*>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL void putInt64(util::index_t offset, std::int64_t v)
      {
          boundsCheck(offset, sizeof(std::int64_t));
          *reinterpret_cast<std::int64_t *>(m_buffer + offset) = v;
      }
  
      inline COND_MOCK_VIRTUAL std::int64_t getInt64(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::int64_t));
          return *reinterpret_cast<std::int64_t *>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL void putInt32(util::index_t offset, std::int32_t v)
      {
          boundsCheck(offset, sizeof(std::int32_t));
          *reinterpret_cast<std::int32_t *>(m_buffer + offset) = v;
      }
  
      inline COND_MOCK_VIRTUAL std::int32_t getInt32(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::int32_t));
          return *reinterpret_cast<std::int32_t *>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL std::int16_t getInt16(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::int16_t));
          return *reinterpret_cast<std::int16_t *>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL void putInt16(util::index_t offset, std::int16_t v)
      {
          boundsCheck(offset, sizeof(std::int16_t));
          *reinterpret_cast<std::int16_t *>(m_buffer + offset) = v;
      }
  
      inline COND_MOCK_VIRTUAL std::uint16_t getUInt16(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::uint16_t));
          return *reinterpret_cast<std::uint16_t *>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL void putUInt16(util::index_t offset, std::uint16_t v)
      {
          boundsCheck(offset, sizeof(std::uint16_t));
          *reinterpret_cast<std::uint16_t *>(m_buffer + offset) = v;
      }
  
      inline std::uint8_t getUInt8(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::uint8_t));
          return *reinterpret_cast<std::uint8_t *>(m_buffer + offset);
      }
  
      inline COND_MOCK_VIRTUAL void putUInt8(util::index_t offset, std::uint8_t v)
      {
          boundsCheck(offset, sizeof(std::uint8_t));
          *reinterpret_cast<std::uint8_t *>(m_buffer + offset) = v;
      }
  
      inline COND_MOCK_VIRTUAL void putInt64Ordered(util::index_t offset, std::int64_t v)
      {
          boundsCheck(offset, sizeof(std::int64_t));
          atomic::putInt64Ordered((volatile std::int64_t*)(m_buffer + offset), v);
      }
  
      inline COND_MOCK_VIRTUAL std::int64_t getInt64Volatile(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::int64_t));
          return atomic::getInt64Volatile((volatile std::int64_t*)(m_buffer + offset));
      }
  
      inline COND_MOCK_VIRTUAL void putInt32Ordered(util::index_t offset, std::int32_t v)
      {
          boundsCheck(offset, sizeof(std::int32_t));
          atomic::putInt32Ordered((volatile std::int32_t*)(m_buffer + offset), v);
      }
  
      inline COND_MOCK_VIRTUAL std::int32_t getInt32Volatile(util::index_t offset) const
      {
          boundsCheck(offset, sizeof(std::int32_t));
          return atomic::getInt32Volatile((volatile std::int32_t*)(m_buffer + offset));
      }
  
      inline void putInt64Atomic(util::index_t offset, std::int64_t v)
      {
          boundsCheck(offset, sizeof(std::int64_t));
          atomic::putInt64Atomic((volatile std::int64_t*)(m_buffer + offset), v);
      }
  
      inline void putInt32Atomic(util::index_t offset, std::int32_t v)
      {
          boundsCheck(offset, sizeof(std::int32_t));
          atomic::putInt32Atomic((volatile std::int32_t*)(m_buffer + offset), v);
      }
  
      /**
       * Single threaded increment with release semantics.
       *
       * @param offset in the buffer of the word.
       * @param delta  for to be applied to the value.
       */
      inline void addInt64Ordered(util::index_t offset, std::int64_t delta)
      {
          boundsCheck(offset, sizeof(std::int64_t));
  
          const std::int64_t value = getInt64(offset);
          atomic::putInt64Ordered((volatile std::int64_t*)(m_buffer + offset), value + delta);
      }
  
      inline bool compareAndSetInt64(util::index_t offset, std::int64_t expectedValue, std::int64_t updateValue)
      {
          boundsCheck(offset, sizeof(std::int64_t));
          std::int64_t original = atomic::cmpxchg((volatile std::int64_t*)(m_buffer + offset), expectedValue, updateValue);
          return original == expectedValue;
      }
  
      /**
       * Multi threaded increment.
       *
       * @param offset in the buffer of the word.
       * @param delta  for to be applied to the value.
       * @return the value before applying the delta.
       */
      inline COND_MOCK_VIRTUAL std::int64_t getAndAddInt64(util::index_t offset, std::int64_t delta)
      {
          boundsCheck(offset, sizeof(std::int64_t));
          return atomic::getAndAddInt64((volatile std::int64_t*)(m_buffer + offset), delta);
      }
  
      /**
       * Single threaded add with release semantics.
       *
       * @param offset in the buffer of the word.
       * @param delta  for to be applied to the value.
       */
      inline void addInt32Ordered(util::index_t offset, std::int32_t delta)
      {
          boundsCheck(offset, sizeof(std::int32_t));
  
          const std::int32_t value = getInt32(offset);
          atomic::putInt32Ordered((volatile std::int32_t*)(m_buffer + offset), value + delta);
      }
  
      inline bool compareAndSetInt32(util::index_t offset, std::int32_t expectedValue, std::int32_t updateValue)
      {
          boundsCheck(offset, sizeof(std::int32_t));
          std::int32_t original = atomic::cmpxchg((volatile std::int32_t*)(m_buffer + offset), expectedValue, updateValue);
          return original == expectedValue;
      }
  
      /**
       * Multi threaded increment.
       *
       * @param offset in the buffer of the word.
       * @param delta  for to be applied to the value.
       * @return the value before applying the delta.
       */
      inline COND_MOCK_VIRTUAL std::int32_t getAndAddInt32(util::index_t offset, std::int32_t delta)
      {
          boundsCheck(offset, sizeof(std::int32_t));
          return atomic::getAndAddInt32((volatile std::int32_t*)(m_buffer + offset), delta);
      }
  
      inline COND_MOCK_VIRTUAL void putBytes(
          util::index_t index, const concurrent::AtomicBuffer& srcBuffer, util::index_t srcIndex, util::index_t length)
      {
          boundsCheck(index, length);
          srcBuffer.boundsCheck(srcIndex, length);
          ::memcpy(m_buffer + index, srcBuffer.m_buffer + srcIndex, length);
      }
  
      inline COND_MOCK_VIRTUAL void putBytes(util::index_t index, const std::uint8_t *srcBuffer, util::index_t length)
      {
          boundsCheck(index, length);
          ::memcpy(m_buffer + index, srcBuffer, length);
      }
  
      inline void getBytes(util::index_t index, std::uint8_t *dst, util::index_t length) const
      {
          boundsCheck(index, length);
          ::memcpy(dst, m_buffer + index, length);
      }
  
      inline void setMemory(util::index_t offset , size_t length, std::uint8_t value)
      {
          boundsCheck(offset, length);
          ::memset(m_buffer + offset, value, length);
      }
  
      inline std::string getString(util::index_t offset) const
      {
          std::int32_t length;
  
          boundsCheck(offset, sizeof(length));
          ::memcpy(reinterpret_cast<char *>(&length), m_buffer + offset, sizeof(length));
  
          return getStringWithoutLength(offset + sizeof(std::int32_t), (size_t) length);
      }
  
      inline std::string getStringWithoutLength(util::index_t offset, size_t length) const
      {
          boundsCheck(offset, length);
          return std::string(m_buffer + offset, m_buffer + offset + length);
      }
  
      inline std::int32_t getStringLength(util::index_t offset) const
      {
          std::int32_t length;
  
          boundsCheck(offset, sizeof(length));
          ::memcpy(reinterpret_cast<char *>(&length), m_buffer + offset, sizeof(length));
  
          return length;
      }
  
      std::int32_t putString(util::index_t offset, const std::string &value)
      {
          std::int32_t length = static_cast<std::int32_t>(value.length());
  
          boundsCheck(offset, value.length() + sizeof(length));
  
          ::memcpy(m_buffer + offset, reinterpret_cast<const char *>(&length), sizeof(length));
          ::memcpy(m_buffer + offset + sizeof(length), value.c_str(), value.length());
  
          return static_cast<std::int32_t>(sizeof(std::int32_t)) + length;
      }
  
      std::int32_t COND_MOCK_VIRTUAL putStringWithoutLength(util::index_t offset, const std::string &value)
      {
          boundsCheck(offset, value.length());
          ::memcpy(m_buffer + offset, value.c_str(), value.length());
          return static_cast<std::int32_t>(value.length());
      }
  
      inline void boundsCheck(util::index_t index, std::uint64_t length) const
      {
  #if !defined(DISABLE_BOUNDS_CHECKS)
          if (AERON_COND_EXPECT(index < 0 || (static_cast<std::uint64_t>(m_length) - index) < length, false))
          {
              throw aeron::util::OutOfBoundsException(
                  aeron::util::strPrintf(
                      "index out of bounds[%p]: index=%lld + %lld, capacity=%lld",
                      this, static_cast<long long>(index), length, static_cast<long long>(m_length)),
                  SOURCEINFO);
          }
  #endif
      }
  
  private:
      // The internal length type used by the atomic buffer
      typedef std::uint32_t length_t;
  
      std::uint8_t *m_buffer;
      length_t m_length;
  };
  
  }}
  
  #endif
  