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;