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;