/*
 * Copyright (c) 2005,2006,2007 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */
#ifndef BUFFER_H
#define BUFFER_H

#include "ns3/assert.h"

#include <ostream>
#include <stdint.h>
#include <vector>

#define BUFFER_FREE_LIST 1

namespace ns3
{

/**
 * @ingroup packet
 *
 * @brief automatically resized byte buffer
 *
 * This represents a buffer of bytes. Its size is
 * automatically adjusted to hold any data prepended
 * or appended by the user. Its implementation is optimized
 * to ensure that the number of buffer resizes is minimized,
 * by creating new Buffers of the maximum size ever used.
 * The correct maximum size is learned at runtime during use by
 * recording the maximum size of each packet.
 *
 * @internal
 * The implementation of the Buffer class uses a COW (Copy On Write)
 * technique to ensure that the underlying data buffer which holds
 * the data bytes is shared among a lot of Buffer instances despite
 * data being added or removed from them.
 *
 * When multiple Buffer instances hold a reference to the same
 * underlying BufferData object, they must be able to detect when
 * the operation they want to perform should trigger a copy of the
 * BufferData. If the BufferData::m_count field is one, it means that
 * there exist only one instance of Buffer which references the
 * BufferData instance so, it is safe to modify it. It is also
 * safe to modify the content of a BufferData if the modification
 * falls outside of the "dirty area" defined by the BufferData.
 * In every other case, the BufferData must be copied before
 * being modified.
 *
 * To understand the way the Buffer::Add and Buffer::Remove methods
 * work, you first need to understand the "virtual offsets" used to
 * keep track of the content of buffers. Each Buffer instance
 * contains real data bytes in its BufferData instance but it also
 * contains "virtual zero data" which typically is used to represent
 * application-level payload. No memory is allocated to store the
 * zero bytes of application-level payload unless the user fragments
 * a Buffer: this application-level payload is kept track of with
 * a pair of integers which describe where in the buffer content
 * the "virtual zero area" starts and ends.
 *
 * @verbatim
 * ***: unused bytes
 * xxx: bytes "added" at the front of the zero area
 * ...: bytes "added" at the back of the zero area
 * 000: virtual zero bytes
 *
 * Real byte buffer:      |********xxxxxxxxxxxx.........*****|
 *                        |--------^ m_start
 *                        |-------------------^ m_zeroAreaStart
 *                        |-----------------------------^ m_end - (m_zeroAreaEnd - m_zeroAreaStart)
 * Virtual byte buffer:           |xxxxxxxxxxxx0000000000000.........|
 *                        |--------^ m_start
 *                        |--------------------^ m_zeroAreaStart
 *                        |---------------------------------^ m_zeroAreaEnd
 *                        |------------------------------------------^ m_end
 * @endverbatim
 *
 * A simple state invariant is that m_start <= m_zeroStart <= m_zeroEnd <= m_end
 */
class Buffer
{
  public:
    /**
     * @brief iterator in a Buffer instance
     */
    class Iterator
    {
      public:
        inline Iterator();
        /**
         * go forward by one byte
         */
        inline void Next();
        /**
         * go backward by one byte
         */
        inline void Prev();
        /**
         * @param delta number of bytes to go forward
         */
        inline void Next(uint32_t delta);
        /**
         * @param delta number of bytes to go backward
         */
        inline void Prev(uint32_t delta);
        /**
         * @param o the second iterator
         * @return number of bytes included between the two iterators
         *
         * This method works only if the two iterators point
         * to the same underlying buffer. Debug builds ensure
         * this with an assert.
         *
         * @hidecaller
         */
        uint32_t GetDistanceFrom(const Iterator& o) const;

        /**
         * @return true if this iterator points to the end of the byte array.
         *     false otherwise.
         */
        bool IsEnd() const;
        /**
         * @return true if this iterator points to the start of the byte array.
         *     false otherwise.
         */
        bool IsStart() const;

        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by one byte.
         *
         * @hidecaller
         */
        inline void WriteU8(uint8_t data);
        /**
         * @param data data to write in buffer
         * @param len number of times data must be written in buffer
         *
         * Write the data in buffer len times and advance the iterator position
         * by len byte.
         *
         * @hidecaller
         */
        inline void WriteU8(uint8_t data, uint32_t len);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by two bytes. The format of the data written in the byte
         * buffer is non-portable. We only ensure that readU16 will
         * return exactly what we wrote with writeU16 if the program
         * is run on the same machine.
         */
        void WriteU16(uint16_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by four bytes. The format of the data written in the byte
         * buffer is non-portable. We only ensure that readU32 will
         * return exactly what we wrote with writeU32 if the program
         * is run on the same machine.
         */
        void WriteU32(uint32_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by eight bytes. The format of the data written in the byte
         * buffer is non-portable. We only ensure that readU64 will
         * return exactly what we wrote with writeU64 if the program
         * is run on the same machine.
         */
        void WriteU64(uint64_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by two bytes. The data is written in least significant byte order and the
         * input data is expected to be in host order.
         */
        void WriteHtolsbU16(uint16_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by four bytes. The data is written in least significant byte order and the
         * input data is expected to be in host order.
         */
        void WriteHtolsbU32(uint32_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by eight bytes. The data is written in least significant byte order and the
         * input data is expected to be in host order.
         */
        void WriteHtolsbU64(uint64_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by two bytes. The data is written in network order and the
         * input data is expected to be in host order.
         *
         * @hidecaller
         */
        inline void WriteHtonU16(uint16_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by four bytes. The data is written in network order and the
         * input data is expected to be in host order.
         */
        inline void WriteHtonU32(uint32_t data);
        /**
         * @param data data to write in buffer
         *
         * Write the data in buffer and advance the iterator position
         * by eight bytes. The data is written in network order and the
         * input data is expected to be in host order.
         */
        void WriteHtonU64(uint64_t data);
        /**
         * @param buffer a byte buffer to copy in the internal buffer.
         * @param size number of bytes to copy.
         *
         * Write the data in buffer and advance the iterator position
         * by size bytes.
         */
        void Write(const uint8_t* buffer, uint32_t size);
        /**
         * @param start the start of the data to copy
         * @param end the end of the data to copy
         *
         * Write the data delimited by start and end in internal buffer
         * and advance the iterator position by the number of bytes
         * copied.
         * The input iterators _must_ not point to the same Buffer as
         * we do to avoid overlapping copies. This is enforced
         * in debug builds by asserts.
         */
        void Write(Iterator start, Iterator end);

        /**
         * @return the byte read in the buffer.
         *
         * Read data, but do not advance the Iterator read.
         */
        inline uint8_t PeekU8();

        /**
         * @return the byte read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         *
         * @hidecaller
         */
        inline uint8_t ReadU8();
        /**
         * @return the two bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in the format written by writeU16.
         */
        inline uint16_t ReadU16();
        /**
         * @return the four bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in the format written by writeU32.
         */
        uint32_t ReadU32();
        /**
         * @return the eight bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in the format written by writeU64.
         */
        uint64_t ReadU64();
        /**
         * @return the two bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in network format and returned in host format.
         *
         * @hidecaller
         */
        inline uint16_t ReadNtohU16();
        /**
         * @return the four bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in network format and returned in host format.
         */
        inline uint32_t ReadNtohU32();
        /**
         * @return the eight bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in network format and returned in host format.
         */
        uint64_t ReadNtohU64();
        /**
         * @return the two bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in least significant byte format and returned in host format.
         */
        uint16_t ReadLsbtohU16();
        /**
         * @return the four bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in least significant byte format and returned in host format.
         */
        uint32_t ReadLsbtohU32();
        /**
         * @return the eight bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in least significant byte format and returned in host format.
         */
        uint64_t ReadLsbtohU64();
        /**
         * @param buffer buffer to copy data into
         * @param size number of bytes to copy
         *
         * Copy size bytes of data from the internal buffer to the
         * input buffer and advance the Iterator by the number of
         * bytes read.
         */
        void Read(uint8_t* buffer, uint32_t size);

        /**
         * @param start start iterator of the buffer to copy data into
         * @param size  number of bytes to copy
         *
         * Copy size bytes of data from the internal buffer to the input buffer via
         * the provided iterator and advance the Iterator by the number of bytes
         * read.
         */
        inline void Read(Iterator start, uint32_t size);

        /**
         * @brief Calculate the checksum.
         * @param size size of the buffer.
         * @return checksum
         */
        uint16_t CalculateIpChecksum(uint16_t size);

        /**
         * @brief Calculate the checksum.
         * @param size size of the buffer.
         * @param initialChecksum initial value
         * @return checksum
         */
        uint16_t CalculateIpChecksum(uint16_t size, uint32_t initialChecksum);

        /**
         * @returns the size of the underlying buffer we are iterating
         */
        uint32_t GetSize() const;

        /**
         * @returns the size left to read of the underlying buffer we are iterating
         */
        uint32_t GetRemainingSize() const;

      private:
        /// Friend class
        friend class Buffer;
        /**
         * Constructor - initializes the iterator to point to the buffer start
         *
         * @param buffer the buffer this iterator refers to
         */
        inline Iterator(const Buffer* buffer);
        /**
         * Constructor - initializes the iterator to point to the buffer end
         *
         * @param buffer the buffer this iterator refers to
         * @param dummy not used param
         */
        inline Iterator(const Buffer* buffer, bool dummy);
        /**
         * Initializes the iterator values
         *
         * @param buffer the buffer this iterator refers to
         */
        inline void Construct(const Buffer* buffer);
        /**
         * Checks that the [start, end) is not in the "virtual zero area".
         *
         * @param start start buffer position
         * @param end end buffer position
         * @returns true if [start, end) is not in the "virtual zero area".
         */
        bool CheckNoZero(uint32_t start, uint32_t end) const;
        /**
         * Checks that the buffer position is not in the "virtual zero area".
         *
         * @param i buffer position
         * @returns true if not in the "virtual zero area".
         */
        bool Check(uint32_t i) const;
        /**
         * @return the two bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in network format and returned in host format.
         *
         * @warning this is the slow version, please use ReadNtohU16 ()
         */
        uint16_t SlowReadNtohU16();
        /**
         * @return the four bytes read in the buffer.
         *
         * Read data and advance the Iterator by the number of bytes
         * read.
         * The data is read in network format and returned in host format.
         *
         * @warning this is the slow version, please use ReadNtohU32 ()
         */
        uint32_t SlowReadNtohU32();
        /**
         * @brief Returns an appropriate message indicating a read error
         * @returns the error message
         */
        std::string GetReadErrorMessage() const;
        /**
         * @brief Returns an appropriate message indicating a write error
         *
         * The message depends on the actual Buffer::Iterator status.
         *
         * @returns the error message
         */
        std::string GetWriteErrorMessage() const;

        /**
         * offset in virtual bytes from the start of the data buffer to the
         * start of the "virtual zero area".
         */
        uint32_t m_zeroStart;
        /**
         * offset in virtual bytes from the start of the data buffer to the
         * end of the "virtual zero area".
         */
        uint32_t m_zeroEnd;
        /**
         * offset in virtual bytes from the start of the data buffer to the
         * start of the data which can be read by this iterator
         */
        uint32_t m_dataStart;
        /**
         * offset in virtual bytes from the start of the data buffer to the
         * end of the data which can be read by this iterator
         */
        uint32_t m_dataEnd;
        /**
         * offset in virtual bytes from the start of the data buffer to the
         * current position represented by this iterator.
         */
        uint32_t m_current;
        /**
         * a pointer to the underlying byte buffer. All offsets are relative
         * to this pointer.
         */
        uint8_t* m_data;
    };

    /**
     * @return the number of bytes stored in this buffer.
     */
    inline uint32_t GetSize() const;

    /**
     * @return a pointer to the start of the internal
     * byte buffer.
     *
     * The returned pointer points to an area of
     * memory which is ns3::Buffer::GetSize () bytes big.
     * Please, try to never ever use this method. It is really
     * evil and is present only for a few specific uses.
     */
    const uint8_t* PeekData() const;

    /**
     * @param start size to reserve
     *
     * Add bytes at the start of the Buffer. The
     * content of these bytes is undefined but debugging
     * builds initialize them to 0x33.
     * Any call to this method invalidates any Iterator
     * pointing to this Buffer.
     */
    void AddAtStart(uint32_t start);
    /**
     * @param end size to reserve
     *
     * Add bytes at the end of the Buffer. The
     * content of these bytes is undefined but debugging
     * builds initialize them to 0x33.
     * Any call to this method invalidates any Iterator
     * pointing to this Buffer.
     */
    void AddAtEnd(uint32_t end);

    /**
     * @param o the buffer to append to the end of this buffer.
     *
     * Add bytes at the end of the Buffer.
     * Any call to this method invalidates any Iterator
     * pointing to this Buffer.
     */
    void AddAtEnd(const Buffer& o);
    /**
     * @param start size to remove
     *
     * Remove bytes at the start of the Buffer.
     * Any call to this method invalidates any Iterator
     * pointing to this Buffer.
     */
    void RemoveAtStart(uint32_t start);
    /**
     * @param end size to remove
     *
     * Remove bytes at the end of the Buffer.
     * Any call to this method invalidates any Iterator
     * pointing to this Buffer.
     */
    void RemoveAtEnd(uint32_t end);

    /**
     * @param start offset from start of packet
     * @param length
     *
     * @return a fragment of size length starting at offset
     * start.
     */
    Buffer CreateFragment(uint32_t start, uint32_t length) const;

    /**
     * @return an Iterator which points to the
     * start of this Buffer.
     */
    inline Buffer::Iterator Begin() const;
    /**
     * @return an Iterator which points to the
     * end of this Buffer.
     */
    inline Buffer::Iterator End() const;

    /**
     * @brief Return the number of bytes required for serialization.
     * @return the number of bytes.
     */
    uint32_t GetSerializedSize() const;

    /**
     * @return zero if buffer not large enough
     * @param buffer points to serialization buffer
     * @param maxSize max number of bytes to write
     *
     * This buffer's contents are serialized into the raw
     * character buffer parameter. Note: The zero length
     * data is not copied entirely. Only the length of
     * zero byte data is serialized.
     */
    uint32_t Serialize(uint8_t* buffer, uint32_t maxSize) const;

    /**
     * @return zero if a complete buffer is not deserialized
     * @param buffer points to buffer for deserialization
     * @param size number of bytes to deserialize
     *
     * The raw character buffer is deserialized and all the
     * data is placed into this buffer.
     */
    uint32_t Deserialize(const uint8_t* buffer, uint32_t size);

    /**
     * Copy the specified amount of data from the buffer to the given output stream.
     *
     * @param os the output stream
     * @param size the maximum amount of bytes to copy. If zero, nothing is copied.
     */
    void CopyData(std::ostream* os, uint32_t size) const;

    /**
     * Copy the specified amount of data from the buffer to the given buffer.
     *
     * @param buffer the output buffer
     * @param size the maximum amount of bytes to copy. If zero, nothing is copied.
     * @returns the amount of bytes copied
     */
    uint32_t CopyData(uint8_t* buffer, uint32_t size) const;

    /**
     * @brief Copy constructor
     * @param o the buffer to copy
     */
    inline Buffer(const Buffer& o);
    /**
     * @brief Assignment operator
     * @param o the buffer to copy
     * @return a reference to the buffer
     */
    Buffer& operator=(const Buffer& o);
    Buffer();
    /**
     * @brief Constructor
     *
     * The buffer will be initialized with zeroes up to its size.
     *
     * @param dataSize the buffer size
     */
    Buffer(uint32_t dataSize);
    /**
     * @brief Constructor
     *
     * If initialize is set to true, the buffer will be initialized
     * with zeroes up to its size.
     *
     * @param dataSize the buffer size.
     * @param initialize initialize the buffer with zeroes.
     */
    Buffer(uint32_t dataSize, bool initialize);
    ~Buffer();

  private:
    /**
     * This data structure is variable-sized through its last member whose size
     * is determined at allocation time and stored in the m_size field.
     *
     * The so-called "dirty area" describes the area in the buffer which
     * has been reserved and used by a user. Multiple Buffer instances
     * may reference the same Buffer::Data object instance and may
     * reference different parts of the underlying byte buffer. The
     * "dirty area" is union of all the areas referenced by the Buffer
     * instances which reference the same BufferData instance.
     * New user data can be safely written only outside of the "dirty
     * area" if the reference count is higher than 1 (that is, if
     * more than one Buffer instance references the same BufferData).
     */
    struct Data
    {
        /**
         * The reference count of an instance of this data structure.
         * Each buffer which references an instance holds a count.
         */
        uint32_t m_count;
        /**
         * the size of the m_data field below.
         */
        uint32_t m_size;
        /**
         * offset from the start of the m_data field below to the
         * start of the area in which user bytes were written.
         */
        uint32_t m_dirtyStart;
        /**
         * offset from the start of the m_data field below to the
         * end of the area in which user bytes were written.
         */
        uint32_t m_dirtyEnd;
        /**
         * The real data buffer holds _at least_ one byte.
         * Its real size is stored in the m_size field.
         */
        uint8_t m_data[1];
    };

    /**
     * @brief Create a full copy of the buffer, including
     * all the internal structures.
     *
     * @returns a copy of the buffer
     */
    Buffer CreateFullCopy() const;

    /**
     * @brief Transform a "Virtual byte buffer" into a "Real byte buffer"
     */
    void TransformIntoRealBuffer() const;
    /**
     * @brief Checks the internal buffer structures consistency
     *
     * Used only for debugging purposes.
     *
     * @returns true if the buffer status is consistent.
     */
    bool CheckInternalState() const;

    /**
     * @brief Initializes the buffer with a number of zeroes.
     *
     * @param zeroSize the zeroes size
     */
    void Initialize(uint32_t zeroSize);

    /**
     * @brief Get the buffer real size.
     * @warning The real size is the actual memory used by the buffer.
     * @returns the memory used by the buffer.
     */
    uint32_t GetInternalSize() const;

    /**
     * @brief Get the buffer end position.
     * @returns the buffer end index.
     */
    uint32_t GetInternalEnd() const;

    /**
     * @brief Recycle the buffer memory
     * @param data the buffer data storage
     */
    static void Recycle(Buffer::Data* data);
    /**
     * @brief Create a buffer data storage
     * @param size the storage size to create
     * @returns a pointer to the created buffer storage
     */
    static Buffer::Data* Create(uint32_t size);
    /**
     * @brief Allocate a buffer data storage
     * @param reqSize the storage size to create
     * @returns a pointer to the allocated buffer storage
     */
    static Buffer::Data* Allocate(uint32_t reqSize);
    /**
     * @brief Deallocate the buffer memory
     * @param data the buffer data storage
     */
    static void Deallocate(Buffer::Data* data);

    Data* m_data; //!< the buffer data storage

    /**
     * keep track of the maximum value of m_zeroAreaStart across
     * the lifetime of a Buffer instance. This variable is used
     * purely as a source of information for the heuristics which
     * decide on the position of the zero area in new buffers.
     * It is read from the Buffer destructor to update the global
     * heuristic data and these global heuristic data are used from
     * the Buffer constructor to choose an initial value for
     * m_zeroAreaStart.
     */
    uint32_t m_maxZeroAreaStart;
    /**
     * location in a newly-allocated buffer where you should start
     * writing data. i.e., m_start should be initialized to this
     * value.
     */
    static uint32_t g_recommendedStart;

    /**
     * offset to the start of the virtual zero area from the start
     * of m_data->m_data
     */
    uint32_t m_zeroAreaStart;
    /**
     * offset to the end of the virtual zero area from the start
     * of m_data->m_data
     */
    uint32_t m_zeroAreaEnd;
    /**
     * offset to the start of the data referenced by this Buffer
     * instance from the start of m_data->m_data
     */
    uint32_t m_start;
    /**
     * offset to the end of the data referenced by this Buffer
     * instance from the start of m_data->m_data
     */
    uint32_t m_end;

#ifdef BUFFER_FREE_LIST
    /// Container for buffer data
    typedef std::vector<Buffer::Data*> FreeList;

    /// Local static destructor structure
    struct LocalStaticDestructor
    {
        ~LocalStaticDestructor();
    };

    static uint32_t g_maxSize;                            //!< Max observed data size
    static FreeList* g_freeList;                          //!< Buffer data container
    static LocalStaticDestructor g_localStaticDestructor; //!< Local static destructor
#endif
};

} // namespace ns3

#include "ns3/assert.h"

#include <cstring>

namespace ns3
{

Buffer::Iterator::Iterator()
    : m_zeroStart(0),
      m_zeroEnd(0),
      m_dataStart(0),
      m_dataEnd(0),
      m_current(0),
      m_data(nullptr)
{
}

Buffer::Iterator::Iterator(const Buffer* buffer)
{
    Construct(buffer);
    m_current = m_dataStart;
}

Buffer::Iterator::Iterator(const Buffer* buffer, bool dummy)
{
    Construct(buffer);
    m_current = m_dataEnd;
}

void
Buffer::Iterator::Construct(const Buffer* buffer)
{
    m_zeroStart = buffer->m_zeroAreaStart;
    m_zeroEnd = buffer->m_zeroAreaEnd;
    m_dataStart = buffer->m_start;
    m_dataEnd = buffer->m_end;
    m_data = buffer->m_data->m_data;
}

void
Buffer::Iterator::Next()
{
    NS_ASSERT(m_current + 1 <= m_dataEnd);
    m_current++;
}

void
Buffer::Iterator::Prev()
{
    NS_ASSERT(m_current >= 1);
    m_current--;
}

void
Buffer::Iterator::Next(uint32_t delta)
{
    NS_ASSERT(m_current + delta <= m_dataEnd);
    m_current += delta;
}

void
Buffer::Iterator::Prev(uint32_t delta)
{
    NS_ASSERT(m_current >= delta);
    m_current -= delta;
}

void
Buffer::Iterator::WriteU8(uint8_t data)
{
    NS_ASSERT_MSG(Check(m_current), GetWriteErrorMessage());

    if (m_current < m_zeroStart)
    {
        m_data[m_current] = data;
        m_current++;
    }
    else
    {
        m_data[m_current - (m_zeroEnd - m_zeroStart)] = data;
        m_current++;
    }
}

void
Buffer::Iterator::WriteU8(uint8_t data, uint32_t len)
{
    NS_ASSERT_MSG(CheckNoZero(m_current, m_current + len), GetWriteErrorMessage());
    if (m_current <= m_zeroStart)
    {
        std::memset(&(m_data[m_current]), data, len);
        m_current += len;
    }
    else
    {
        uint8_t* buffer = &m_data[m_current - (m_zeroEnd - m_zeroStart)];
        std::memset(buffer, data, len);
        m_current += len;
    }
}

void
Buffer::Iterator::WriteHtonU16(uint16_t data)
{
    NS_ASSERT_MSG(CheckNoZero(m_current, m_current + 2), GetWriteErrorMessage());
    uint8_t* buffer;
    if (m_current + 2 <= m_zeroStart)
    {
        buffer = &m_data[m_current];
    }
    else
    {
        buffer = &m_data[m_current - (m_zeroEnd - m_zeroStart)];
    }
    buffer[0] = (data >> 8) & 0xff;
    buffer[1] = (data >> 0) & 0xff;
    m_current += 2;
}

void
Buffer::Iterator::WriteHtonU32(uint32_t data)
{
    NS_ASSERT_MSG(CheckNoZero(m_current, m_current + 4), GetWriteErrorMessage());

    uint8_t* buffer;
    if (m_current + 4 <= m_zeroStart)
    {
        buffer = &m_data[m_current];
    }
    else
    {
        buffer = &m_data[m_current - (m_zeroEnd - m_zeroStart)];
    }
    buffer[0] = (data >> 24) & 0xff;
    buffer[1] = (data >> 16) & 0xff;
    buffer[2] = (data >> 8) & 0xff;
    buffer[3] = (data >> 0) & 0xff;
    m_current += 4;
}

uint16_t
Buffer::Iterator::ReadNtohU16()
{
    uint8_t* buffer;
    if (m_current + 2 <= m_zeroStart)
    {
        buffer = &m_data[m_current];
    }
    else if (m_current >= m_zeroEnd)
    {
        buffer = &m_data[m_current - (m_zeroEnd - m_zeroStart)];
    }
    else
    {
        return SlowReadNtohU16();
    }
    uint16_t retval = 0;
    retval |= buffer[0];
    retval <<= 8;
    retval |= buffer[1];
    m_current += 2;
    return retval;
}

uint32_t
Buffer::Iterator::ReadNtohU32()
{
    uint8_t* buffer;
    if (m_current + 4 <= m_zeroStart)
    {
        buffer = &m_data[m_current];
    }
    else if (m_current >= m_zeroEnd)
    {
        buffer = &m_data[m_current - (m_zeroEnd - m_zeroStart)];
    }
    else
    {
        return SlowReadNtohU32();
    }
    uint32_t retval = 0;
    retval |= buffer[0];
    retval <<= 8;
    retval |= buffer[1];
    retval <<= 8;
    retval |= buffer[2];
    retval <<= 8;
    retval |= buffer[3];
    m_current += 4;
    return retval;
}

uint8_t
Buffer::Iterator::PeekU8()
{
    NS_ASSERT_MSG(m_current >= m_dataStart && m_current < m_dataEnd, GetReadErrorMessage());

    if (m_current < m_zeroStart)
    {
        uint8_t data = m_data[m_current];
        return data;
    }
    else if (m_current < m_zeroEnd)
    {
        return 0;
    }
    else
    {
        uint8_t data = m_data[m_current - (m_zeroEnd - m_zeroStart)];
        return data;
    }
}

uint8_t
Buffer::Iterator::ReadU8()
{
    uint8_t ret = PeekU8();
    m_current++;
    return ret;
}

uint16_t
Buffer::Iterator::ReadU16()
{
    uint8_t byte0 = ReadU8();
    uint8_t byte1 = ReadU8();
    uint16_t data = byte1;
    data <<= 8;
    data |= byte0;

    return data;
}

void
Buffer::Iterator::Read(Buffer::Iterator start, uint32_t size)
{
    Buffer::Iterator end = *this;
    end.Next(size);

    start.Write(*this, end);
}

Buffer::Buffer(const Buffer& o)
    : m_data(o.m_data),
      m_maxZeroAreaStart(o.m_zeroAreaStart),
      m_zeroAreaStart(o.m_zeroAreaStart),
      m_zeroAreaEnd(o.m_zeroAreaEnd),
      m_start(o.m_start),
      m_end(o.m_end)
{
    m_data->m_count++;
    NS_ASSERT(CheckInternalState());
}

uint32_t
Buffer::GetSize() const
{
    return m_end - m_start;
}

Buffer::Iterator
Buffer::Begin() const
{
    NS_ASSERT(CheckInternalState());
    return Buffer::Iterator(this);
}

Buffer::Iterator
Buffer::End() const
{
    NS_ASSERT(CheckInternalState());
    return Buffer::Iterator(this, false);
}

} // namespace ns3

#endif /* BUFFER_H */
