Hi,

Attaching the first draft of CBuffer for licq-newapi. Please tell me what you 
think, if you love it, hate it, I've missed or failed to understand something 
etc. Don't hold back, I can (hopefully) take it :)

Example code:
CBuffer buf;
buf << littleEndian << uint8_t(1) << uint8_t(2) << bigEndian << uint16_t(3) << 
uint32_t(4);

uint32_t data;
buf >> bigEndian >> data;

uint16_t data1[5] = { 1, 2, 3, 4, 5 };
std::vector<uint32_t> data2(2, 0x12345678);

buf.write(data1, data1 + 5);
buf.write(data2.begin(), data2.end());

uint8_t t[10];
buf.read(t, 10);
for (int i = 0; i < 10; i++)
  cout << (int)t[i] << " ";

buf[0] = uint8_t(3);
cout << buf[0] << endl;


* I've tried documenting the code (doxygen style), but there is always room 
for improvements. 
* No unit tests yet, haven't gotten around to do that just yet.
* The code isn't tested that much. This version is more a "design check" than 
a "code check". 

Best regards
// Erik

-- 
If the designers of X-Windows built cars, there would be no fewer 
than five steering wheels hidden about the cockpit, none of which 
followed the same principles -- but you'd be able to shift gears 
with your car stereo. Useful feature, that. 
  -- Marus J. Ranum, Digital Equipment Corporation

Erik Johansson
http://ejohansson.se
/*
 * Copyright (C) 2000, 2004 Herbert Valerio Riedel <[EMAIL PROTECTED]>
 * Copyright (C) 2005 Rocky Bernstein <[EMAIL PROTECTED]>
 * Copyright (C) 2006 Erik Johansson <[EMAIL PROTECTED]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Original copied from the bytesex.h file in the libcdio source.
 */

#ifndef BYTESEX_H
#define BYTESEX_H

#define UINT16_SWAP_LE_BE_C(val) ((uint16_t) ( \
  (((uint16_t) (val) & (uint16_t) 0x00ffU) << 8) | \
  (((uint16_t) (val) & (uint16_t) 0xff00U) >> 8)))

#define UINT32_SWAP_LE_BE_C(val) ((uint32_t) ( \
  (((uint32_t) (val) & (uint32_t) 0x000000ffU) << 24) | \
  (((uint32_t) (val) & (uint32_t) 0x0000ff00U) <<  8) | \
  (((uint32_t) (val) & (uint32_t) 0x00ff0000U) >>  8) | \
  (((uint32_t) (val) & (uint32_t) 0xff000000U) >> 24)))

#ifdef __GNUC__
# include <byteswap.h>
# define UINT16_SWAP_LE_BE bswap_16
# define UINT32_SWAP_LE_BE bswap_32
#else
# define UINT16_SWAP_LE_BE UINT16_SWAP_LE_BE_C
# define UINT32_SWAP_LE_BE UINT32_SWAP_LE_BE_C
#endif

inline static
uint16_t uint16_swap_le_be(const uint16_t val)
{
  return UINT16_SWAP_LE_BE (val);
}

inline static
uint32_t uint32_swap_le_be(const uint32_t val)
{
  return UINT32_SWAP_LE_BE (val);
}

# define UINT8_TO_BE(val)      ((uint8_t) (val))
# define UINT8_TO_LE(val)      ((uint8_t) (val))
#ifdef WORDS_BIGENDIAN
# define UINT16_TO_BE(val)     ((uint16_t) (val))
# define UINT16_TO_LE(val)     ((uint16_t) UINT16_SWAP_LE_BE(val))

# define UINT32_TO_BE(val)     ((uint32_t) (val))
# define UINT32_TO_LE(val)     ((uint32_t) UINT32_SWAP_LE_BE(val))
#else
# define UINT16_TO_BE(val)     ((uint16_t) UINT16_SWAP_LE_BE(val))
# define UINT16_TO_LE(val)     ((uint16_t) (val))

# define UINT32_TO_BE(val)     ((uint32_t) UINT32_SWAP_LE_BE(val))
# define UINT32_TO_LE(val)     ((uint32_t) (val))
#endif

#define UINT8_FROM_BE(val)     (UINT8_TO_BE (val))
#define UINT8_FROM_LE(val)     (UINT8_TO_LE (val))
#define UINT16_FROM_BE(val)    (UINT16_TO_BE (val))
#define UINT16_FROM_LE(val)    (UINT16_TO_LE (val))
#define UINT32_FROM_BE(val)    (UINT32_TO_BE (val))
#define UINT32_FROM_LE(val)    (UINT32_TO_LE (val))
#define UINT64_FROM_BE(val)    (UINT64_TO_BE (val))
#define UINT64_FROM_LE(val)    (UINT64_TO_LE (val))

#define CVT_TO_FUNC(bits) \
  static inline uint ## bits ## _t \
  uint ## bits ## _to_be (uint ## bits ## _t val) \
  { return UINT ## bits ## _TO_BE (val); } \
  static inline uint ## bits ## _t \
  uint ## bits ## _to_le (uint ## bits ## _t val) \
  { return UINT ## bits ## _TO_LE (val); } \

CVT_TO_FUNC(8)
CVT_TO_FUNC(16)
CVT_TO_FUNC(32)

#undef CVT_TO_FUNC

#define uint8_from_be(val)     (uint8_to_be (val))
#define uint8_from_le(val)     (uint8_to_le (val))
#define uint16_from_be(val)    (uint16_to_be (val))
#define uint16_from_le(val)    (uint16_to_le (val))
#define uint32_from_be(val)    (uint32_to_be (val))
#define uint32_from_le(val)    (uint32_to_le (val))

#endif // BYTESEX_H

// kate: space-indent on; indent-width 2; indent-mode cstyle;
// kate: remove-trailing-space on; replace-trailing-space-save on;
/*
 * Copyright (C) 2006 Erik Johansson <[EMAIL PROTECTED]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef BUFFER_H
#define BUFFER_H

#include <iosfwd>
#include <functional>
#include <iterator>
#include <cstddef>
#include <stdint.h>

namespace Licq
{
  /**
   * @class CBuffer buffer.h licq/buffer.h
   * @brief A buffer to hold data that is sent and/or received over the network.
   *
   * The first thing to note is that CBuffer has a sense of byte order. When writing
   * to the buffer (using operator<<) the data is converted to the current byte order.
   * The same thing goes when reading (operator>>). Then the data is assumed to be in
   * the current byte order and when read it is converted to the host's byte order.
   *
   * Changing the byte order for myBuffer to little endian can be done in any one of the
   * four following ways:
   * @code
   * myBuffer << littleEndian;
   * myBuffer >> littleEndian;
   * myBuffer.setByteOrder(CBuffer::ByteOrderLE);
   * littleEndian(myBuffer);
   * @endcode
   * Changing to big endian would be the same, just replacing littleEndian with bigEndian,
   * or CBuffer::ByteOrderLE with CBuffer::ByteOrderBE.
   *
   * It's important to note that there is only @b one current byte order, which applies
   * to both reads and writes. And changing the byte order does not change the order of
   * bytes already in the buffer. Just the way new data is saved and how existing data
   * is interpreted when read.
   */
  class CBuffer
  {
    public:
      CBuffer();
      CBuffer(const CBuffer& other);
      virtual ~CBuffer();

      CBuffer& operator=(const CBuffer& other);

      std::size_t size() const;
      std::size_t sizeToRead() const;
      bool isEmpty() const;
      void clear();
      void reset();

      /// Little or big endian byte order
      enum ByteOrder { ByteOrderLE, ByteOrderBE };

      //@{
      ByteOrder byteOrder() const;
      void setByteOrder(const ByteOrder order);
      //@}

      //@{
      /// Appends @a other to the buffer.
      CBuffer& operator<<(const CBuffer& other);
      CBuffer& operator+=(const CBuffer& other);
      //@}

      //@{
      /// Appends @a data to the buffer.
      CBuffer& operator<<(const uint8_t  data);
      CBuffer& operator<<(const uint16_t data);
      CBuffer& operator<<(const uint32_t data);
      CBuffer& operator<<(const std::string& data);
      //@}

      /// Appends [EMAIL PROTECTED] begin, @a end) to the buffer.
      template<typename Iterator>
          CBuffer& write(Iterator begin, Iterator end);

      //@{
      /// Reads from the buffer to @a data.
      CBuffer& operator>>(uint8_t&  data);
      CBuffer& operator>>(uint16_t& data);
      CBuffer& operator>>(uint32_t& data);
      //@}

      /// Reads at most @a n elements from the buffer to @a pos.
      template<typename Iterator>
          std::size_t read(Iterator pos, std::size_t n);

      //@{
      /// Buffer manipulators. Applies op to the buffer.
      CBuffer& operator<<(void (*op)(CBuffer&));
      CBuffer& operator>>(void (*op)(CBuffer&));
      //@}

      //@{
      /// Returns the element in the buffer at @a index.
      uint8_t& operator[](const std::size_t index);
      const uint8_t operator[](const std::size_t index) const;
      //@}

      friend std::ostream& operator<<(std::ostream& os, const CBuffer& buffer);

    private:
      class CBufferPrivate *d;
  }; // class CBuffer

  /**
   * All elements from @a begin (inclusive) to @a end (exclusive) is appended to
   * the buffer in order (independent of current byte order).
   * But, the order in which the bytes of each element are appended, depends on
   * the current byte order.
   *
   * Example:
   * @code
   * CBuffer myBufferLE, myBufferBE;
   * myBufferBE << bigEndian;
   *
   * uint16_t data1[5] = { 1, 2, 3, 4, 5 };
   * std::vector<uint32_t> data2(2, 0x12345678);
   *
   * myBufferLE.write(data1, data1 + 5);
   * myBufferLE.write(data2.begin(), data2.end());
   *
   * myBufferBE.write(data1, data1 + 5);
   * myBufferBE.write(data2.begin(), data2.end());
   *
   * std::cout << std::hex << "myBufferLE: " << myBufferLE << std::endl;
   * std::cout << std::hex << "myBufferBE: " << myBufferBE << std::endl;
   * @endcode
   * Output: @n
   * myBufferLE: 1 0 2 0 3 0 4 0 5 0 78 56 34 12 78 56 34 12 @n
   * myBufferBE: 0 1 0 2 0 3 0 4 0 5 12 34 56 78 12 34 56 78
   *
   * @b OBS: If you try to call this method with an iterator that has a value type V
   * without the corresponding CBuffer::operator<<(const V), you'll get an compiler
   * error complaining about invalid static_cast.
   */
  template<typename Iterator> CBuffer& CBuffer::write(Iterator begin, Iterator end)
  {
    typedef typename std::iterator_traits<Iterator>::value_type value_type;
    std::for_each(begin, end, std::bind1st(
      std::mem_fun(static_cast<CBuffer& (CBuffer::*)(const value_type)>(&CBuffer::operator<<)), this));
    return *this;
  }

  /**
   * This method reads at most @a n elements from the buffer in order (independent of the
   * current byte order). But, the order in which individual bytes are concatenated
   * to form a element, depends on the byte order.
   *
   * @return number of elements actually read.
   */
  template<typename Iterator> std::size_t CBuffer::read(Iterator pos, std::size_t n)
  {
    std::size_t elements = std::min(n, sizeToRead());
    for (std::size_t i = 0; i < elements; i++, pos++)
      *this >> *pos;
    return elements;
  }

  /// Returns the concatenation of @a x and @a y.
  CBuffer operator+(const CBuffer& x, const CBuffer& y);

  /// Print every element in @a buffer seperated with a space.
  std::ostream& operator<<(std::ostream& os, const CBuffer& buffer);

  //@{
  /// CBuffer manipulator.
  void littleEndian(CBuffer& buffer);
  void bigEndian(CBuffer& buffer);
  //@}

} // namespace Licq

#endif // BUFFER_H

// kate: space-indent on; indent-width 2; indent-mode cstyle;
// kate: remove-trailing-space on; replace-trailing-space-save on;
/*
 * Copyright (C) 2006 Erik Johansson <[EMAIL PROTECTED]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "buffer.h"

/*
// Code to determine if host is little or big endian.
int i = 0x12345678;
if (*(char *)&i == 0x12)
  // big endian, define WORDS_BIGENDIAN
else if (*(char *)&i == 0x78)
  // little endian, undef WORDS_BIGENDIAN
*/

#undef WORDS_BIGENDIAN
#include "bytesex.h"

#include <vector>
#include <cassert>
#include <ostream>
#include <iterator>

namespace Licq
{
  /**
   * @brief Only for use internally by CBuffer.
   */
  class CBufferPrivate
  {
    public:
      CBufferPrivate();

      std::vector<uint8_t> m_data;

      // We store the read pointer as an integer index, since
      // iterators might become invalid after e.g. inserts.
      std::size_t m_readPointer;

      CBuffer::ByteOrder m_byteOrder;
  };
}

using namespace Licq;
using std::size_t;

/// Default constructor. Sets up the buffer with little endian byte order.
CBufferPrivate::CBufferPrivate()
  : m_readPointer(0), m_byteOrder(CBuffer::ByteOrderLE)
{
}

/**
 * @brief Default constructor.
 *
 * Creates an empty buffer with little endian byte order.
 */
CBuffer::CBuffer()
{
  d = new CBufferPrivate;
}

/**
 * @brief Copy constructor.
 *
 * Creates a deep-copy of @a other. The new buffer will be identical to @a other.
 */
CBuffer::CBuffer(const CBuffer& other)
{
  d = new CBufferPrivate(*(other.d));
}

/// Destructor.
CBuffer::~CBuffer()
{
  delete d;
}

/// Creates a deep-copy of @a other and makes this buffer identical to @a other.
CBuffer& CBuffer::operator=(const CBuffer& other)
{
  if (this != &other)
  {
    delete d;
    d = new CBufferPrivate(*(other.d));
  }

  return *this;
}

/**
 * @brief Size of the buffer.
 *
 * Returns the size of the buffer, i.e. the number of elements
 * currently stored in the buffer.
 */
size_t CBuffer::size() const
{
  return d->m_data.size();
}

/**
 * @brief Returns number of elements that hasn't been read yet.
 *
 * Calculated as size() - <# bytes already read>
 */
size_t CBuffer::sizeToRead() const
{
  return d->m_data.size() - d->m_readPointer;
}

/**
 * @brief Returns true if the buffer is empty; otherwise returns false.
 */
bool CBuffer::isEmpty() const
{
  return d->m_data.empty();
}

/**
 * @brief Clears the buffer.
 */
void CBuffer::clear()
{
  d->m_data.clear();
  reset();
}

/**
 * @brief Resets the read pointer to point to the begining of the buffer.
 */
void CBuffer::reset()
{
  d->m_readPointer = 0;
}

/**
 * @brief Returns the current byte order.
 */
CBuffer::ByteOrder CBuffer::byteOrder() const
{
  return d->m_byteOrder;
}

/**
 * @brief Sets the current byte order to @a order.
 *
 * Changing the byte order does not change the order that bytes already in the buffer
 * are orded. Just how following reads and writes will be done.
 */
void CBuffer::setByteOrder(const ByteOrder order)
{
  if (ByteOrderLE == order || ByteOrderBE == order)
    d->m_byteOrder = order;
}

/**
 * Appends @a other to this buffer. If the byte order of this buffer
 * and @a other differs, @a other is append in reversed order.
 */
CBuffer& CBuffer::operator<<(const CBuffer& other)
{
  return (*this += other);
}

CBuffer& CBuffer::operator+=(const CBuffer& other)
{
  d->m_data.reserve(d->m_data.size() + other.d->m_data.size());

  if (d->m_byteOrder == other.d->m_byteOrder)
    std::copy(other.d->m_data.begin(), other.d->m_data.end(), std::back_inserter(d->m_data));
  else
    std::reverse_copy(other.d->m_data.begin(), other.d->m_data.end(), std::back_inserter(d->m_data));

  return *this;
}

/**
 * Stores @a data in the buffer, according to the current byte order.
 */
CBuffer& CBuffer::operator<<(const uint8_t data)
{
  d->m_data.push_back(data);
  return *this;
}

CBuffer& CBuffer::operator<<(const uint16_t data)
{
  uint16_t byteOrdered;
  if (ByteOrderLE == d->m_byteOrder)
    byteOrdered = uint16_to_le(data);
  else // if (ByteOrderBE == d->m_byteOrder)
    byteOrdered = uint16_to_be(data);

  uint8_t *p = reinterpret_cast<uint8_t*>(&byteOrdered);
  return (*this << p[0] << p[1]);
}

CBuffer& CBuffer::operator<<(const uint32_t data)
{
  uint32_t byteOrdered;
  if (ByteOrderLE == d->m_byteOrder)
    byteOrdered = uint32_to_le(data);
  else // if (ByteOrderBE == d->m_byteOrder)
    byteOrdered = uint32_to_be(data);

  uint8_t *p = reinterpret_cast<uint8_t*>(&byteOrdered);
  return (*this << p[0] << p[1] << p[2] << p[3]);
}

/**
 * If the byte order is little endian the string "hello" would
 * be appened as "olleh", i.e. reversed. If it's big endian it
 * would be appened as "hello".
 */
CBuffer& CBuffer::operator<<(const std::string& data)
{
  d->m_data.reserve(d->m_data.size() + data.size());

  if (ByteOrderBE == d->m_byteOrder)
    std::copy(data.begin(), data.end(), std::back_inserter(d->m_data));
  else // if (ByteOrderLE == d->m_byteOrder)
    std::reverse_copy(data.begin(), data.end(), std::back_inserter(d->m_data));

  return *this;
}

/**
 * Reads data from the buffer starting at the current read position and
 * using the current byte order. If enough data is available, it is stored in
 * @a data; otherwise @a data is set to 0.
 *
 * Increments the read pointer with the number of bytes read.
 *
 * @todo Should log somewhere when this happens.
 * @see CBuffer::reset()
 */
CBuffer& CBuffer::operator>>(uint8_t& data)
{
  if (d->m_readPointer + sizeof(uint8_t) <= d->m_data.size())
  {
    data = d->m_data[d->m_readPointer];
    d->m_readPointer += sizeof(uint8_t);
  }
  else
    data = 0;

  return *this;
}

CBuffer& CBuffer::operator>>(uint16_t& data)
{
  if (d->m_readPointer + sizeof(uint16_t) <= d->m_data.size())
  {
    uint8_t raw[2];
    *this >> raw[0] >> raw[1];
    if (ByteOrderBE == d->m_byteOrder)
      std::swap(raw[0], raw[1]);

    data = ((uint16_t) raw[0]) + (((uint16_t) raw[1]) << 8);
  }
  else
    data = 0;

  return *this;
}

CBuffer& CBuffer::operator>>(uint32_t& data)
{
  if (d->m_readPointer + sizeof(uint32_t) <= d->m_data.size())
  {
    uint16_t raw[2];
    *this >> raw[0] >> raw[1];
    if (ByteOrderBE == d->m_byteOrder)
      std::swap(raw[0], raw[1]);

    data = ((uint32_t) raw[0]) + (((uint32_t) raw[1]) << 16);
  }
  else
    data = 0;

  return *this;
}

/**
 * Applies @a op on this buffer.
 */
CBuffer& CBuffer::operator<<(void (*op)(CBuffer&))
{
  (*op)(*this);
  return *this;
}

/**
 * Applies @a op on this buffer.
 */
CBuffer& CBuffer::operator>>(void (*op)(CBuffer&))
{
  (*op)(*this);
  return *this;
}

/**
 * Returns the element at @a index without checking that
 * the index is valid.
 *
 * @todo Log invalid index and/or return 0?
 */
uint8_t& CBuffer::operator[](const std::size_t index)
{
  assert(index < d->m_data.size());
  return d->m_data[index];
}

const uint8_t CBuffer::operator[](const std::size_t index) const
{
  assert(index < d->m_data.size());
  return d->m_data[index];
}

/**
 * @see CBuffer::operator+=
 */
CBuffer Licq::operator+(const CBuffer& x, const CBuffer& y)
{
  CBuffer r(x);
  return r += y;
}

/**
 * Prints the @b whole @a buffer to @a os. Current read position is not
 * respected.
 */
std::ostream& Licq::operator<<(std::ostream& os, const CBuffer& buffer)
{
  std::copy(buffer.d->m_data.begin(), buffer.d->m_data.end(),
            std::ostream_iterator<unsigned int>(os, " "));
  return os;
}

/**
 * All reads and writes done after calling this function is done with
 * little endian byte order. Can be used in the same way as std::endl
 * is used with streams:
 * @code
 * buffer << littleEndian;
 * // or
 * buffer >> littleEndian;
 * @endcode
 *
 * @see CBuffer::setByteOrder
 */
void Licq::littleEndian(CBuffer& buffer)
{
  buffer.setByteOrder(CBuffer::ByteOrderLE);
}

/**
 * Same as littleEndian(), but sets the byte order to big endian.
 */
void Licq::bigEndian(CBuffer& buffer)
{
  buffer.setByteOrder(CBuffer::ByteOrderBE);
}

// kate: space-indent on; indent-width 2; indent-mode cstyle;
// kate: remove-trailing-space on; replace-trailing-space-save on;

Reply via email to