/** @file

  I/O classes

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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

      http://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.

  @section watermark Watermark

  Watermarks can be used as an interface between the data transferring
  layer (VConnection) and the user layer (a state machine).  Watermarks
  should be used when you need to have at least a certain amount of data
  to make some determination.  For example, when parsing a string, one
  might wish to ensure that an entire line will come in before consuming
  the data.  In such a case, the water_mark should be set to the largest
  possible size of the string. (appropriate error handling should take
  care of excessively long strings).

  In all other cases, especially when all data will be consumed, the
  water_mark should be set to 0 (the default).

 */

#pragma once
#define I_IOBuffer_h

#include "tscore/Allocator.h"
#include "tscore/Ptr.h"
#include "tscore/ink_assert.h"
#include "tscore/ink_resource.h"

struct MIOBufferAccessor;

class MIOBuffer;
class IOBufferReader;
class VIO;

enum AllocType {
  NO_ALLOC,
  MEMALIGNED,
  DEFAULT_ALLOC,
};

#define DEFAULT_BUFFER_NUMBER               128
#define DEFAULT_HUGE_BUFFER_NUMBER          32
#define MAX_MIOBUFFER_READERS               5
#define DEFAULT_BUFFER_ALIGNMENT            8192 // should be disk/page size
#define DEFAULT_BUFFER_BASE_SIZE            128
#define DEFAULT_PLUGIN_VC_BUFFER_WATER_MARK 0 // ensure we retain existing behavior

////////////////////////////////////////////////
// These are defines so that code that used 2 //
// for buffer size index when 2 was 2K will   //
// still work if it uses BUFFER_SIZE_INDEX_2K //
// instead.                                   //
////////////////////////////////////////////////
#define BUFFER_SIZE_INDEX_128  0
#define BUFFER_SIZE_INDEX_256  1
#define BUFFER_SIZE_INDEX_512  2
#define BUFFER_SIZE_INDEX_1K   3
#define BUFFER_SIZE_INDEX_2K   4
#define BUFFER_SIZE_INDEX_4K   5
#define BUFFER_SIZE_INDEX_8K   6
#define BUFFER_SIZE_INDEX_16K  7
#define BUFFER_SIZE_INDEX_32K  8
#define BUFFER_SIZE_INDEX_64K  9
#define BUFFER_SIZE_INDEX_128K 10
#define BUFFER_SIZE_INDEX_256K 11
#define BUFFER_SIZE_INDEX_512K 12
#define BUFFER_SIZE_INDEX_1M   13
#define BUFFER_SIZE_INDEX_2M   14
#define MAX_BUFFER_SIZE_INDEX  14
#define DEFAULT_BUFFER_SIZES   (MAX_BUFFER_SIZE_INDEX + 1)

#define BUFFER_SIZE_FOR_INDEX(_i) (DEFAULT_BUFFER_BASE_SIZE * (1 << (_i)))
#define DEFAULT_SMALL_BUFFER_SIZE BUFFER_SIZE_INDEX_512
#define DEFAULT_LARGE_BUFFER_SIZE BUFFER_SIZE_INDEX_4K
#define DEFAULT_TS_BUFFER_SIZE    BUFFER_SIZE_INDEX_8K
#define DEFAULT_MAX_BUFFER_SIZE   BUFFER_SIZE_FOR_INDEX(MAX_BUFFER_SIZE_INDEX)
#define MIN_IOBUFFER_SIZE         BUFFER_SIZE_INDEX_128
#define MAX_IOBUFFER_SIZE         (DEFAULT_BUFFER_SIZES - 1)

#define BUFFER_SIZE_ALLOCATED(_i) (BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_i) || BUFFER_SIZE_INDEX_IS_XMALLOCED(_i))

#define BUFFER_SIZE_NOT_ALLOCATED                        DEFAULT_BUFFER_SIZES
#define BUFFER_SIZE_INDEX_IS_XMALLOCED(_size_index)      (_size_index < 0)
#define BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_size_index) (((uint64_t)_size_index) < DEFAULT_BUFFER_SIZES)
#define BUFFER_SIZE_INDEX_IS_CONSTANT(_size_index)       (_size_index >= DEFAULT_BUFFER_SIZES)

#define BUFFER_SIZE_FOR_XMALLOC(_size) (-(_size))
[[nodiscard]] constexpr int64_t
BUFFER_SIZE_INDEX_FOR_XMALLOC_SIZE(int64_t size)
{
  // Positive size indices are interpreted as a BUFFER_SIZE_INDEX_*.
  // Negative size indices are interpreted as a malloc size.
  // A zero size index is BUFFER_SIZE_INDEX_128, which causes this buffer to be freed incorrectly.
  ink_release_assert(size > 0 && "Zero-length xmalloc buffer causes heap corruption!");
  return -size;
}

#define BUFFER_SIZE_FOR_CONSTANT(_size)            (_size - DEFAULT_BUFFER_SIZES)
#define BUFFER_SIZE_INDEX_FOR_CONSTANT_SIZE(_size) (_size + DEFAULT_BUFFER_SIZES)

#if TS_USE_ALLOCATOR_METRICS
extern MeteredAllocator<FreelistAllocator> ioBufAllocator[DEFAULT_BUFFER_SIZES];
#else
extern FreelistAllocator ioBufAllocator[DEFAULT_BUFFER_SIZES];
#endif

void init_buffer_allocators(int iobuffer_advice, int chunk_sizes[DEFAULT_BUFFER_SIZES], bool use_hugepages);
void init_buffer_allocators(int iobuffer_advice);

bool parse_buffer_chunk_sizes(const char *s, int chunk_sizes[DEFAULT_BUFFER_SIZES]);

/**
  A reference counted wrapper around fast allocated or malloced memory.
  The IOBufferData class provides two basic services around a portion
  of allocated memory.

  First, it is a reference counted object and ...

  @remarks The AllocType enum, is used to define the type of allocation
  for the memory this IOBufferData object manages.

  <table>
    <tr>
      <td align="center">AllocType</td>
      <td align="center">Meaning</td>
    </tr>
    <tr>
      <td>NO_ALLOC</td>
      <td></td>
    </tr>
    <tr>
      <td>MEMALIGNED</td>
      <td></td>
    </tr>
    <tr>
      <td>DEFAULT_ALLOC</td>
      <td></td>
    </tr>
  </table>

 */
class IOBufferData : public RefCountObj
{
public:
  /**
    The size of the memory allocated by this IOBufferData. Calculates
    the amount of memory allocated by this IOBufferData.

    @return number of bytes allocated for the '_data' member.

  */
  int64_t block_size();

  /**
    Frees the memory managed by this IOBufferData.  Deallocates the
    memory previously allocated by this IOBufferData object. It frees
    the memory pointed to by '_data' according to the '_mem_type' and
    '_size_index' members.

  */
  void dealloc();

  /**
    Allocates memory and sets this IOBufferData to point to it.
    Allocates memory according to the size_index and type
    parameters. Any previously allocated memory pointed to by
    this IOBufferData is deallocated.

    @param size_index
    @param type of allocation to use; see remarks section.
  */
  void alloc(int64_t size_index, AllocType type = DEFAULT_ALLOC);

  /**
    Provides access to the allocated memory. Returns the address of the
    allocated memory handled by this IOBufferData.

    @return address of the memory handled by this IOBufferData.

  */
  char *
  data()
  {
    return _data;
  }

  /**
    Cast operator. Provided as a convenience, the cast to a char* applied
    to the IOBufferData returns the address of the memory handled by the
    IOBuffer data. In this manner, objects of this class can be used as
    parameter to functions requiring a char*.

  */
  operator char *() { return _data; }
  /**
    Frees the IOBufferData object and its underlying memory. Deallocates
    the memory managed by this IOBufferData and then frees itself. You
    should not use this object or reference after this call.

  */
  void free() override;

  int64_t _size_index;

  /**
    Type of allocation used for the managed memory. Stores the type of
    allocation used for the memory currently managed by the IOBufferData
    object. Do not set or modify this value directly. Instead use the
    alloc or dealloc methods.

  */
  AllocType _mem_type = NO_ALLOC;

  /**
    Points to the allocated memory. This member stores the address of
    the allocated memory. You should not modify its value directly,
    instead use the alloc or dealloc methods.

  */
  char *_data = nullptr;

  const char *_location = nullptr;

  /**
    Constructor. Initializes state for a IOBufferData object. Do not use
    this method. Use one of the functions with the 'new_' prefix instead.

  */
  IOBufferData() : _size_index(BUFFER_SIZE_NOT_ALLOCATED) {}

  // noncopyable, declaration only
  IOBufferData(const IOBufferData &)            = delete;
  IOBufferData &operator=(const IOBufferData &) = delete;
};

extern ClassAllocator<IOBufferData, false> ioDataAllocator;

/**
  A linkable portion of IOBufferData. IOBufferBlock is a chainable
  buffer block descriptor. The IOBufferBlock represents both the used
  and available space in the underlying block. The IOBufferBlock is not
  shareable between buffers but rather represents what part of the data
  block is both in use and usable by the MIOBuffer it is attached to.

*/
class IOBufferBlock : public RefCountObj
{
public:
  /**
    Access the actual data. Provides access to rhe underlying data
    managed by the IOBufferData.

    @return pointer to the underlying data.

  */
  char *
  buf()
  {
    return data->_data;
  }

  /**
    Beginning of the inuse section. Returns the position in the buffer
    where the inuse area begins.

    @return pointer to the start of the inuse section.

  */
  char *
  start()
  {
    return _start;
  }

  /**
    End of the used space. Returns a pointer to end of the used space
    in the data buffer represented by this block.

    @return pointer to the end of the inuse portion of the block.

  */
  char *
  end()
  {
    return _end;
  }

  /**
    End of the data buffer. Returns a pointer to end of the data buffer
    represented by this block.

  */
  char *
  buf_end()
  {
    return _buf_end;
  }

  /**
    Size of the inuse area. Returns the size of the current inuse area.

    @return bytes occupied by the inuse area.

  */
  int64_t
  size()
  {
    return static_cast<int64_t>(_end - _start);
  }

  /**
    Size of the data available for reading. Returns the size of the data
    available for reading in the inuse area.

    @return bytes available for reading from the inuse area.

  */
  int64_t
  read_avail() const
  {
    return static_cast<int64_t>(_end - _start);
  }

  /**
    Space available in the buffer. Returns the number of bytes that can
    be written to the data buffer.

    @return space available for writing in this IOBufferBlock.
  */
  int64_t
  write_avail()
  {
    return static_cast<int64_t>(_buf_end - _end);
  }

  /**
    Size of the memory allocated by the underlying IOBufferData.
    Computes the size of the entire block, which includes the used and
    available areas. It is the memory allocated by the IOBufferData
    referenced by this IOBufferBlock.

    @return bytes allocated to the IOBufferData referenced by this
      IOBufferBlock.

  */
  int64_t
  block_size()
  {
    return data->block_size();
  }

  /**
    Decrease the size of the inuse area. Moves forward the start of
    the inuse area. This also decreases the number of available bytes
    for reading.

    @param len bytes to consume or positions to skip for the start of
      the inuse area.

  */
  void consume(int64_t len);

  /**
    Increase the inuse area of the block. Adds 'len' bytes to the inuse
    area of the block. Data should be copied into the data buffer by
    using end() to find the start of the free space in the data buffer
    before calling fill()

    @param len bytes to increase the inuse area. It must be less than
      or equal to the value of write_avail().

  */
  void fill(int64_t len);

  /**
    Reset the inuse area. The start and end of the inuse area are reset
    but the actual IOBufferData referenced by this IOBufferBlock is not
    modified.  This effectively reduces the number of bytes available
    for reading to zero, and the number of bytes available for writing
    to the size of the entire buffer.

  */
  void reset();

  /**
    Create a copy of the IOBufferBlock. Creates and returns a copy of this
    IOBufferBlock that references the same data that this IOBufferBlock
    (it does not allocate an another buffer). The cloned block will not
    have a writable space since the original IOBufferBlock maintains the
    ownership for writing data to the block.

    @return copy of this IOBufferBlock.

  */
  IOBufferBlock *clone() const;

  /**
    Clear the IOBufferData this IOBufferBlock handles. Clears this
    IOBufferBlock's reference to the data buffer (IOBufferData). You can
    use alloc after this call to allocate an IOBufferData associated to
    this IOBufferBlock.

  */
  void clear();

  /**
    Allocate a data buffer. Allocates a data buffer for this IOBufferBlock
    based on index 'i'.  Index values are described in the remarks
    section in MIOBuffer.

  */
  void alloc(int64_t i);

  /**
    Clear the IOBufferData this IOBufferBlock handles. Clears this
    IOBufferBlock's reference to the data buffer (IOBufferData).

  */
  void dealloc();

  /**
    Set or replace this IOBufferBlock's IOBufferData member. Sets this
    IOBufferBlock's IOBufferData member to point to the IOBufferData
    passed in. You can optionally specify the inuse area with the 'len'
    argument and an offset for the start.

    @param d new IOBufferData this IOBufferBlock references.
    @param len in use area to set. It must be less than or equal to the
      length of the block size *IOBufferData).
    @param offset bytes to skip from the beginning of the IOBufferData
      and to mark its start.

  */
  void set(IOBufferData *d, int64_t len = 0, int64_t offset = 0);
  void set_internal(void *b, int64_t len, int64_t asize_index);

  /**
    Frees the IOBufferBlock object and its underlying memory.
    Removes the reference to the IOBufferData object and then frees
    itself. You should not use this object or reference after this
    call.

  */
  void free() override;

  char *_start   = nullptr;
  char *_end     = nullptr;
  char *_buf_end = nullptr;

  const char *_location = nullptr;

  /**
    The underlying reference to the allocated memory. A reference to a
    IOBufferData representing the memory allocated to this buffer. Do
    not set or modify its value directly.

  */
  Ptr<IOBufferData> data;

  /**
    Reference to another IOBufferBlock. A reference to another
    IOBufferBlock that allows this object to link to other.

  */
  Ptr<IOBufferBlock> next;

  /**
    Constructor of a IOBufferBlock. Do not use it to create a new object,
    instead call new_IOBufferBlock

  */
  IOBufferBlock();

  // noncopyable
  IOBufferBlock(const IOBufferBlock &)            = delete;
  IOBufferBlock &operator=(const IOBufferBlock &) = delete;
};

extern ClassAllocator<IOBufferBlock, false> ioBlockAllocator;

/** A class for holding a chain of IO buffer blocks.
    This class is intended to be used as a member variable for other classes that
    need to anchor an IO Buffer chain but don't need the full @c MIOBuffer machinery.
    That is, the owner is the only reader/writer of the data.

    This does not handle incremental reads or writes well. The intent is that data is
    placed in the instance, held for a while, then used and discarded.

    @note Contrast also with @c IOBufferReader which is similar but requires an
    @c MIOBuffer as its owner.
*/
class IOBufferChain
{
  using self_type = IOBufferChain; ///< Self reference type.

public:
  /// Default constructor - construct empty chain.
  IOBufferChain() = default;
  /// Shallow copy.
  self_type &operator=(self_type const &that);

  /// Shallow append.
  self_type &operator+=(self_type const &that);

  /// Number of bytes of content.
  int64_t length() const;

  /// Copy a chain of @a blocks in to this object up to @a length bytes.
  /// If @a offset is greater than 0 that many bytes are skipped. Those bytes do not count
  /// as part of @a length.
  /// This creates a new chain using existing data blocks. This
  /// breaks the original chain so that changes there (such as appending blocks)
  /// is not reflected in this chain.
  /// @return The number of bytes written to the chain.
  int64_t write(IOBufferBlock *blocks, int64_t length, int64_t offset = 0);

  /// Add the content of a buffer block.
  /// The buffer block is unchanged.
  int64_t write(IOBufferData *data, int64_t length = 0, int64_t offset = 0);

  /// Remove @a size bytes of content from the front of the chain.
  /// @return The actual number of bytes removed.
  int64_t consume(int64_t size);

  /// Clear current chain.
  void clear();

  /// Get the first block.
  IOBufferBlock       *head();
  IOBufferBlock const *head() const;

  /// STL Container support.

  /// Block iterator.
  /// @internal The reason for this is to override the increment operator.
  class const_iterator : public std::forward_iterator_tag
  {
    using self_type = const_iterator; ///< Self reference type.
  protected:
    /// Current buffer block.
    IOBufferBlock *_b = nullptr;

  public:
    using value_type = const IOBufferBlock; ///< Iterator value type.

    const_iterator() = default; ///< Default constructor.

    /// Copy constructor.
    // cppcheck-suppress noExplicitConstructor; copy constructor
    const_iterator(self_type const &that);

    /// Assignment.
    self_type &operator=(self_type const &that);

    /// Equality.
    bool operator==(self_type const &that) const;
    /// Inequality.
    bool operator!=(self_type const &that) const;

    value_type &operator*() const;
    value_type *operator->() const;

    self_type &operator++();
    self_type  operator++(int);
  };

  class iterator : public const_iterator
  {
    using self_type = iterator; ///< Self reference type.
  public:
    using value_type = IOBufferBlock; ///< Dereferenced type.

    value_type &operator*() const;
    value_type *operator->() const;
  };

  using value_type = IOBufferBlock;

  iterator       begin();
  const_iterator begin() const;

  iterator       end();
  const_iterator end() const;

protected:
  /// Append @a block.
  void append(IOBufferBlock *block);

  /// Head of buffer block chain.
  Ptr<IOBufferBlock> _head;
  /// Tail of the block chain.
  IOBufferBlock *_tail = nullptr;
  /// The amount of data of interest.
  /// Not necessarily the amount of data in the chain of blocks.
  int64_t _len = 0;
};

/**
  An independent reader from an MIOBuffer. A reader for a set of
  IOBufferBlocks. The IOBufferReader represents the place where a given
  consumer of buffer data is reading from. It provides a uniform interface
  for easily accessing the data contained in a list of IOBufferBlocks
  associated with the IOBufferReader.

  IOBufferReaders are the abstraction that determine when data blocks
  can be removed from the buffer.

*/
class IOBufferReader
{
public:
  /**
    Start of unconsumed data. Returns a pointer to first unconsumed data
    on the buffer for this reader. A null pointer indicates no data is
    available. It uses the current start_offset value.

    @return pointer to the start of the unconsumed data.

  */
  char *start();

  /**
    End of inuse area of the first block with unconsumed data. Returns a
    pointer to the end of the first block with unconsumed data for this
    reader. A nullptr pointer indicates there are no blocks with unconsumed
    data for this reader.

    @return pointer to the end of the first block with unconsumed data.

  */
  char *end();

  /**
    Amount of data available across all of the IOBufferBlocks. Returns the
    number of unconsumed bytes of data available to this reader across
    all remaining IOBufferBlocks. It subtracts the current start_offset
    value from the total.

    @return bytes of data available across all the buffers.

  */
  int64_t read_avail();

  /** Check if there is more than @a size bytes available to read.
      @return @c true if more than @a size byte are available.
  */
  bool is_read_avail_more_than(int64_t size);

  /**
    Number of IOBufferBlocks with data in the block list. Returns the
    number of IOBufferBlocks on the block list with data remaining for
    this reader.

    @return number of blocks with data for this reader.

  */
  int block_count();

  /**
    Amount of data available in the first buffer with data for this
    reader.  Returns the number of unconsumed bytes of data available
    on the first IOBufferBlock with data for this reader.

    @return number of unconsumed bytes of data available in the first
      buffer.

  */
  int64_t block_read_avail();

  /** Get a view of the data available to read.
   *
   * @return A view encompassing currently available readable data.
   */
  std::string_view block_read_view();

  void skip_empty_blocks();

  /**
    Clears all fields in this IOBuffeReader, rendering it unusable. Drops
    the reference to the IOBufferBlock list, the accessor, MIOBuffer and
    resets this reader's state. You have to set those fields in order
    to use this object again.

  */
  void clear();

  /**
    Instruct the reader to reset the IOBufferBlock list. Resets the
    reader to the point to the start of the block where new data will
    be written. After this call, the start_offset field is set to zero
    and the list of IOBufferBlocks is set using the associated MIOBuffer.

  */
  void reset();

  /**
    Consume a number of bytes from this reader's IOBufferBlock
    list. Advances the current position in the IOBufferBlock list of
    this reader by n bytes.

    @param n number of bytes to consume. It must be less than or equal
      to read_avail().

  */
  void consume(int64_t n);

  /**
    Create another reader with access to the same data as this
    IOBufferReader. Allocates a new reader with the same state as this
    IOBufferReader. This means that the new reader will point to the same
    list of IOBufferBlocks and to the same buffer position as this reader.

    @return new reader with the same state as this.

  */
  IOBufferReader *clone();

  /**
    Deallocate this reader. Removes and deallocates this reader from
    the underlying MIOBuffer. This IOBufferReader object must not be
    used after this call.

  */
  void dealloc();

  /**
    Get a pointer to the first block with data. Returns a pointer to
    the first IOBufferBlock in the block chain with data available for
    this reader

    @return pointer to the first IOBufferBlock in the list with data
      available for this reader.

  */
  IOBufferBlock *get_current_block();

  /**
    Consult this reader's MIOBuffer writable space. Queries the MIOBuffer
    associated with this reader about the amount of writable space
    available without adding any blocks on the buffer and returns true
    if it is less than the water mark.

    @return true if the MIOBuffer associated with this IOBufferReader
      returns true in MIOBuffer::current_low_water().

  */
  bool current_low_water();

  /**
    Queries the underlying MIOBuffer about. Returns true if the amount
    of writable space after adding a block on the underlying MIOBuffer
    is less than its water mark. This function call may add blocks to
    the MIOBuffer (see MIOBuffer::low_water()).

    @return result of MIOBuffer::low_water() on the MIOBuffer for
      this reader.

  */
  bool low_water();

  /**
    To see if the amount of data available to the reader is greater than
    the MIOBuffer's water mark. Indicates whether the amount of data
    available to this reader exceeds the water mark for this reader's
    MIOBuffer.

    @return true if the amount of data exceeds the MIOBuffer's water mark.

  */
  bool high_water();

  /**
    Perform a memchr() across the list of IOBufferBlocks. Returns the
    offset from the current start point of the reader to the first
    occurrence of character 'c' in the buffer.

    @param c character to look for.
    @param len number of characters to check. If len exceeds the number
      of bytes available on the buffer or INT64_MAX is passed in, the
      number of bytes available to the reader is used. It is independent
      of the offset value.
    @param offset number of the bytes to skip over before beginning
      the operation.
    @return -1 if c is not found, otherwise position of the first
      occurrence.

  */
  int64_t memchr(char c, int64_t len = INT64_MAX, int64_t offset = 0);

  /**
    Copies and consumes data. Copies len bytes of data from the buffer
    into the supplied buffer, which must be allocated prior to the call
    and it must be at large enough for the requested bytes. Once the
    data is copied, it consumed from the reader.

    @param buf in which to place the data.
    @param len bytes to copy and consume. If 'len' exceeds the bytes
      available to the reader, the number of bytes available is used
      instead.

    @return number of bytes copied and consumed.

  */
  int64_t read(void *buf, int64_t len);

  /**
    Copy data but do not consume it. Copies 'len' bytes of data from
    the current buffer into the supplied buffer. The copy skips the
    number of bytes specified by 'offset' beyond the current point of
    the reader. It also takes into account the current start_offset value.

    @param buf in which to place the data. The pointer is modified after
      the call and points one position after the end of the data copied.
    @param len bytes to copy. If len exceeds the bytes available to the
      reader or INT64_MAX is passed in, the number of bytes available is
      used instead. No data is consumed from the reader in this operation.
    @param offset bytes to skip from the current position. The parameter
      is modified after the call.
    @return pointer to one position after the end of the data copied. The
      parameter buf is set to this value also.

  */
  char *memcpy(void *buf, int64_t len = INT64_MAX, int64_t offset = 0);

  /**
    Subscript operator. Returns a reference to the character at the
    specified position. You must ensure that it is within an appropriate
    range.

    @param i positions beyond the current point of the reader. It must
      be less than the number of the bytes available to the reader.

    @return reference to the character in that position.

  */
  char &operator[](int64_t i);

  MIOBuffer *
  writer() const
  {
    return mbuf;
  }
  MIOBuffer *
  allocated() const
  {
    return mbuf;
  }

  MIOBufferAccessor *accessor = nullptr; // pointer back to the accessor

  /**
    Back pointer to this object's MIOBuffer. A pointer back to the
    MIOBuffer this reader is allocated from.

  */
  MIOBuffer         *mbuf = nullptr;
  Ptr<IOBufferBlock> block;

  /**
    Offset beyond the shared start(). The start_offset is used in the
    calls that copy or consume data and is an offset at the beginning
    of the available data.

  */
  int64_t start_offset = 0;
  int64_t size_limit   = INT64_MAX;

  IOBufferReader() {}
};

/**
  A multiple reader, single writer memory buffer. MIOBuffers are at
  the center of all IOCore data transfer. MIOBuffers are the data
  buffers used to transfer data to and from VConnections. A MIOBuffer
  points to a list of IOBufferBlocks which in turn point to IOBufferData
  structures that in turn point to the actual data. MIOBuffer allows one
  producer and multiple consumers. The buffer fills up according the
  amount of data outstanding for the slowest consumer. Thus, MIOBuffer
  implements automatic flow control between readers of different speeds.
  Data on IOBuffer is immutable. Once written it cannot be modified, only
  deallocated once all consumers have finished with it. Immutability is
  necessary since data can be shared between buffers, which means that
  multiple IOBufferBlock objects may reference the same data but only
  one will have ownership for writing.

*/
class MIOBuffer
{
public:
  /**
    Increase writer's inuse area. Instructs the writer associated with
    this MIOBuffer to increase the inuse area of the block by as much as
    'len' bytes.

    @param len number of bytes to add to the inuse area of the block.

  */
  void fill(int64_t len);

  /**
    Adds a block to the end of the block list. The block added to list
    must be writable by this buffer and must not be writable by any
    other buffer.

  */
  void append_block(IOBufferBlock *b);

  /**
    Adds a new block to the end of the block list. The size is determined
    by asize_index. See the remarks section for a mapping of indexes to
    buffer block sizes.

  */
  void append_block(int64_t asize_index);

  /**
    Adds a new block to the end of the block list. Note that this does nothing when the next block of the current writer exists.
    The block size is the same as specified size when the buffer was allocated.
  */
  void add_block();

  /**
    Deprecated

    TODO: remove this function. Because ats_xmalloc() doesn't exist anymore.

    Adds by reference len bytes of data pointed to by b to the end
    of the buffer.  b MUST be a pointer to the beginning of  block
    allocated from the ats_xmalloc() routine. The data will be deallocated
    by the buffer once all readers on the buffer have consumed it.

  */
  void append_xmalloced(void *b, int64_t len);

  /**
    Adds by reference len bytes of data pointed to by b to the end of the
    buffer. b MUST be a pointer to the beginning of  block allocated from
    ioBufAllocator of the corresponding index for fast_size_index. The
    data will be deallocated by the buffer once all readers on the buffer
    have consumed it.

  */
  void append_fast_allocated(void *b, int64_t len, int64_t fast_size_index);

  /**
    Adds the nbytes worth of data pointed by rbuf to the buffer. The
    data is copied into the buffer. write() does not respect watermarks
    or buffer size limits. Users of write must implement their own flow
    control. Returns the number of bytes added.

  */
  int64_t write(const void *rbuf, int64_t nbytes);

  /**
    Add by data from IOBufferReader r to the this buffer by reference. If
    len is INT64_MAX, all available data on the reader is added. If len is
    less than INT64_MAX, the smaller of len or the amount of data on the
    buffer is added. If offset is greater than zero, than the offset
    bytes of data at the front of the reader are skipped. Bytes skipped
    by offset reduce the number of bytes available on the reader used
    in the amount of data to add computation. write() does not respect
    watermarks or buffer size limits. Users of write must implement
    their own flow control. Returns the number of bytes added. Each
    write() call creates a new IOBufferBlock, even if it is for one
    byte. As such, it's necessary to exercise caution in any code that
    repeatedly transfers data from one buffer to another, especially if
    the data is being read over the network as it may be coming in very
    small chunks. Because deallocation of outstanding buffer blocks is
    recursive, it's possible to overrun the stack if too many blocks
    have been added to the buffer chain. It's imperative that users
    both implement their own flow control to prevent too many bytes
    from becoming outstanding on a buffer that the write() call is
    being used and that care be taken to ensure the transfers are of a
    minimum size. Should it be necessary to make a large number of small
    transfers, it's preferable to use a interface that copies the data
    rather than sharing blocks to prevent a build of blocks on the buffer.

  */
  int64_t write(IOBufferReader *r, int64_t len = INT64_MAX, int64_t offset = 0);

  /** Copy data from the @a chain to this buffer.
      New IOBufferBlocks are allocated so this gets a copy of the data that is independent of the source.
      @a offset bytes are skipped at the start of the @a chain. The length is bounded by @a len and the
      size in the @a chain.

      @return the number of bytes copied.

      @internal I do not like counting @a offset against @a bytes but that's how @c write works...
  */
  int64_t write(IOBufferChain const *chain, int64_t len = INT64_MAX, int64_t offset = 0);

  /**
    Returns a pointer to the first writable block on the block chain.
    Returns nullptr if there are not currently any writable blocks on the
    block list.
  */
  IOBufferBlock *
  first_write_block()
  {
    if (_writer) {
      if (_writer->next && !_writer->write_avail()) {
        return _writer->next.get();
      }
      ink_assert(!_writer->next || !_writer->next->read_avail());
      return _writer.get();
    }

    return nullptr;
  }

  char *
  buf()
  {
    IOBufferBlock *b = first_write_block();
    return b ? b->buf() : nullptr;
  }

  char *
  buf_end()
  {
    return first_write_block()->buf_end();
  }

  char *
  start()
  {
    return first_write_block()->start();
  }

  char *
  end()
  {
    return first_write_block()->end();
  }

  /**
    Returns the amount of space of available for writing on the first
    writable block on the block chain (the one that would be returned
    by first_write_block()).

  */
  int64_t block_write_avail();

  /**
    Returns the amount of space of available for writing on all writable
    blocks currently on the block chain.  Will NOT add blocks to the
    block chain.

  */
  int64_t current_write_avail();

  /**
    Adds blocks for writing if the watermark criteria are met. Returns
    the amount of space of available for writing on all writable blocks
    on the block chain after a block due to the watermark criteria.

  */
  int64_t write_avail();

  /**
    Returns the default data block size for this buffer.

  */
  int64_t block_size();

  /**
    Returns true if amount of the data outstanding on the buffer exceeds
    the watermark.

  */
  bool
  high_water()
  {
    return is_max_read_avail_more_than(this->water_mark);
  }

  /**
    Returns true if the amount of writable space after adding a block on
    the buffer is less than the water mark. Since this function relies
    on write_avail() it may add blocks.

  */
  bool
  low_water()
  {
    return write_avail() <= water_mark;
  }

  /**
    Returns true if amount the amount writable space without adding and
    blocks on the buffer is less than the water mark.

  */
  bool
  current_low_water()
  {
    return current_write_avail() <= water_mark;
  }

  /**
    Allocates a new IOBuffer reader and sets it's its 'accessor' field
    to point to 'anAccessor'.

  */
  IOBufferReader *alloc_accessor(MIOBufferAccessor *anAccessor);

  /**
    Allocates an IOBufferReader for this buffer. IOBufferReaders hold
    data on the buffer for different consumers. IOBufferReaders are
    REQUIRED when using buffer. alloc_reader() MUST ONLY be a called
    on newly allocated buffers. Calling on a buffer with data already
    placed on it will result in the reader starting at an indeterminate
    place on the buffer.

  */
  IOBufferReader *alloc_reader();

  /**
    Allocates a new reader on this buffer and places it's starting
    point at the same place as reader r. r MUST be a pointer to a reader
    previous allocated from this buffer.

  */
  IOBufferReader *clone_reader(IOBufferReader *r);

  /**
    Deallocates reader e from this buffer. e MUST be a pointer to a reader
    previous allocated from this buffer. Reader need to allocated when a
    particularly consumer is being removed from the buffer but the buffer
    is still in use. Deallocation is not necessary when the buffer is
    being freed as all outstanding readers are automatically deallocated.

  */
  void dealloc_reader(IOBufferReader *e);

  /**
    Deallocates all outstanding readers on the buffer.

  */
  void dealloc_all_readers();

  void    set(void *b, int64_t len);
  void    alloc(int64_t i);
  void    append_block_internal(IOBufferBlock *b);
  int64_t write(IOBufferBlock const *b, int64_t len, int64_t offset);

  // internal interface

  /**
    Get the maximum amount of available data across all of the readers.
    If there're no allocated reader, return available data size of current writer.

    This calls IOBufferReader::read_avail() and it could be expensive when it has a ton of IOBufferBlock.
    The `is_max_read_avail(int64_t size)` is preferred if possible.

    @return maximum amount of available data
   */
  int64_t max_read_avail();

  /**
    Check if there is more than @a size bytes available to read.

    @return @c true if more than @a size byte are available.
  */
  bool is_max_read_avail_more_than(int64_t size);

  int  max_block_count();
  void check_add_block();

  IOBufferBlock *get_current_block();

  void
  reset()
  {
    if (_writer) {
      _writer->reset();
    }
    for (auto &reader : readers) {
      if (reader.allocated()) {
        reader.reset();
      }
    }
  }

  void
  init_readers()
  {
    for (auto &reader : readers) {
      if (reader.allocated() && !reader.block) {
        reader.block = _writer;
      }
    }
  }

  void
  dealloc()
  {
    _writer = nullptr;
    dealloc_all_readers();
  }

  void
  clear()
  {
    dealloc();
    size_index = BUFFER_SIZE_NOT_ALLOCATED;
    water_mark = 0;
  }

  int64_t size_index;

  /**
    Determines when to stop writing or reading. The watermark is the
    level to which the producer (filler) is required to fill the buffer
    before it can expect the reader to consume any data.  A watermark
    of zero means that the reader will consume any amount of data,
    no matter how small.

  */
  int64_t water_mark;

  Ptr<IOBufferBlock> _writer;
  IOBufferReader     readers[MAX_MIOBUFFER_READERS];

  const char *_location = nullptr;

  explicit MIOBuffer(int64_t default_size_index);
  MIOBuffer();
  ~MIOBuffer();
};

/**
  A wrapper for either a reader or a writer of an MIOBuffer.

*/
struct MIOBufferAccessor {
  IOBufferReader *
  reader() const
  {
    return entry;
  }

  MIOBuffer *
  writer() const
  {
    return mbuf;
  }

  int64_t
  block_size() const
  {
    return mbuf->block_size();
  }

  void reader_for(IOBufferReader *abuf);
  void reader_for(MIOBuffer *abuf);
  void writer_for(MIOBuffer *abuf);

  void
  clear()
  {
    mbuf  = nullptr;
    entry = nullptr;
  }

  MIOBufferAccessor() {}

  ~MIOBufferAccessor();

#ifdef DEBUG
  const char *name = nullptr;
#endif

  // noncopyable
  MIOBufferAccessor(const MIOBufferAccessor &)            = delete;
  MIOBufferAccessor &operator=(const MIOBufferAccessor &) = delete;

private:
  MIOBuffer      *mbuf  = nullptr;
  IOBufferReader *entry = nullptr;
};

extern MIOBuffer *new_MIOBuffer_internal(const char *loc, int64_t size_index);

class MIOBuffer_tracker
{
  const char *loc;

public:
  explicit MIOBuffer_tracker(const char *_loc) : loc(_loc) {}
  MIOBuffer *
  operator()(int64_t size_index)
  {
    return new_MIOBuffer_internal(loc, size_index);
  }
};

extern MIOBuffer *new_empty_MIOBuffer_internal(const char *loc, int64_t size_index);

class Empty_MIOBuffer_tracker
{
  const char *loc;

public:
  explicit Empty_MIOBuffer_tracker(const char *_loc) : loc(_loc) {}
  MIOBuffer *
  operator()(int64_t size_index)
  {
    return new_empty_MIOBuffer_internal(loc, size_index);
  }
};

/// MIOBuffer allocator/deallocator
#define new_MIOBuffer       MIOBuffer_tracker(RES_PATH("memory/IOBuffer/"))
#define new_empty_MIOBuffer Empty_MIOBuffer_tracker(RES_PATH("memory/IOBuffer/"))
extern void free_MIOBuffer(MIOBuffer *mio);
//////////////////////////////////////////////////////////////////////

extern IOBufferBlock *new_IOBufferBlock_internal(const char *loc);

extern IOBufferBlock *new_IOBufferBlock_internal(const char *loc, IOBufferData *d, int64_t len = 0, int64_t offset = 0);

class IOBufferBlock_tracker
{
  const char *loc;

public:
  explicit IOBufferBlock_tracker(const char *_loc) : loc(_loc) {}
  IOBufferBlock *
  operator()()
  {
    return new_IOBufferBlock_internal(loc);
  }
  IOBufferBlock *
  operator()(Ptr<IOBufferData> &d, int64_t len = 0, int64_t offset = 0)
  {
    return new_IOBufferBlock_internal(loc, d.get(), len, offset);
  }
};

/// IOBufferBlock allocator
#define new_IOBufferBlock IOBufferBlock_tracker(RES_PATH("memory/IOBuffer/"))
////////////////////////////////////////////////////////////

extern IOBufferData *new_IOBufferData_internal(const char *location, int64_t size_index, AllocType type = DEFAULT_ALLOC);

extern IOBufferData *new_xmalloc_IOBufferData_internal(const char *location, void *b, int64_t size);

class IOBufferData_tracker
{
  const char *loc;

public:
  explicit IOBufferData_tracker(const char *_loc) : loc(_loc) {}
  IOBufferData *
  operator()(int64_t size_index, AllocType type = DEFAULT_ALLOC)
  {
    return new_IOBufferData_internal(loc, size_index, type);
  }
};

// TODO: remove new_xmalloc_IOBufferData. Because ats_xmalloc() doesn't exist anymore.
#define new_IOBufferData                  IOBufferData_tracker(RES_PATH("memory/IOBuffer/"))
#define new_xmalloc_IOBufferData(b, size) new_xmalloc_IOBufferData_internal(RES_PATH("memory/IOBuffer/"), (b), (size))

extern int64_t iobuffer_size_to_index(int64_t size, int64_t max);
extern int64_t index_to_buffer_size(int64_t idx);
/**
  Clone a IOBufferBlock chain. Used to snarf a IOBufferBlock chain
  w/o copy.

  @param b head of source IOBufferBlock chain.
  @param offset # bytes in the beginning to skip.
  @param len bytes to copy from source.
  @return ptr to head of new IOBufferBlock chain.

*/
extern IOBufferBlock *iobufferblock_clone(IOBufferBlock *b, int64_t offset, int64_t len);
/**
  Skip over specified bytes in chain. Used for dropping references.

  @param b head of source IOBufferBlock chain.
  @param poffset originally offset in b, finally offset in returned
    IOBufferBlock.
  @param plen value of write is subtracted from plen in the function.
  @param write bytes to skip.
  @return ptr to head of new IOBufferBlock chain.

*/
extern IOBufferBlock *iobufferblock_skip(IOBufferBlock *b, int64_t *poffset, int64_t *plen, int64_t write);

inline IOBufferChain &
IOBufferChain::operator=(self_type const &that)
{
  _head = that._head;
  _tail = that._tail;
  _len  = that._len;
  return *this;
}

inline IOBufferChain &
IOBufferChain::operator+=(self_type const &that)
{
  if (_head == nullptr) {
    *this = that;
  } else {
    _tail->next  = that._head;
    _tail        = that._tail;
    _len        += that._len;
  }
  return *this;
}

inline int64_t
IOBufferChain::length() const
{
  return _len;
}

inline IOBufferBlock const *
IOBufferChain::head() const
{
  return _head.get();
}

inline IOBufferBlock *
IOBufferChain::head()
{
  return _head.get();
}

inline void
IOBufferChain::clear()
{
  _head = nullptr;
  _tail = nullptr;
  _len  = 0;
}

inline IOBufferChain::const_iterator::const_iterator(self_type const &that) : _b(that._b) {}

inline IOBufferChain::const_iterator &
IOBufferChain::const_iterator::operator=(self_type const &that)
{
  _b = that._b;
  return *this;
}

inline bool
IOBufferChain::const_iterator::operator==(self_type const &that) const
{
  return _b == that._b;
}

inline bool
IOBufferChain::const_iterator::operator!=(self_type const &that) const
{
  return _b != that._b;
}

inline IOBufferChain::const_iterator::value_type &
IOBufferChain::const_iterator::operator*() const
{
  return *_b;
}

inline IOBufferChain::const_iterator::value_type *
IOBufferChain::const_iterator::operator->() const
{
  return _b;
}

inline IOBufferChain::const_iterator &
IOBufferChain::const_iterator::operator++()
{
  _b = _b->next.get();
  return *this;
}

inline IOBufferChain::const_iterator
IOBufferChain::const_iterator::operator++(int)
{
  self_type pre{*this};
  ++*this;
  return pre;
}

inline IOBufferChain::iterator::value_type &
IOBufferChain::iterator::operator*() const
{
  return *_b;
}

inline IOBufferChain::iterator::value_type *
IOBufferChain::iterator::operator->() const
{
  return _b;
}
