/// /// \file packarray.cpp /// A packable array class, to ease the storing and retrieving /// of many POD values in a BLOB (blob data represented by /// std::string). /// /* Copyright 2005 Chris Frey . To God be the glory. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 in the COPYING file at the root directory of this project for more details. */ // // Design goals: // - template - specify POD (piece of data) type: int, long, double, // even struct // // - endian conversion - support a template conversion between C++ type // and representation in the blob / std::string // // - array sizing - provide hard size limit (throw exceptions on out of // bounds) or allow automatic growing *and* shrinking // - shrunken sizes: any zero values at the end of the array // can be eliminated from the write // // Limits: // - byte is the smallest size - bit fields not supported // #include #include #include /////////////////////////////////////////////////////////////////////////////// // forward declarations template class pack_traits; template > class basic_packarray; typedef basic_packarray double_pack; typedef basic_packarray int_pack; typedef basic_packarray long_pack; typedef basic_packarray short_pack; /////////////////////////////////////////////////////////////////////////////// // endian templates // // Default templates allow any struct to perform endian conversions, // if they implement to_buf() and to_c() members. These conversions must // occur IN PLACE, just like any other endian conversion! // template Type c2buf(const Type &t) { Type ret = t; ret.to_buf(); return ret; } template Type buf2c(const Type &t) { Type ret = t; ret.to_c(); return ret; } // specializations for common types // From glibc: #if 1 #define pa_bswap_64(x) \ ((((x) & 0xff00000000000000ull) >> 56) \ | (((x) & 0x00ff000000000000ull) >> 40) \ | (((x) & 0x0000ff0000000000ull) >> 24) \ | (((x) & 0x000000ff00000000ull) >> 8) \ | (((x) & 0x00000000ff000000ull) << 8) \ | (((x) & 0x0000000000ff0000ull) << 24) \ | (((x) & 0x000000000000ff00ull) << 40) \ | (((x) & 0x00000000000000ffull) << 56)) #else #define pa_bswap_64(x) __bswap_64(x) #endif // need the union to avoid aliasing rule violations // http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html union DoubleConverter { double Double; uint64_t Uint; }; template <> double c2buf(const double &d) { if( 1 != htons(1) ) { // little endian - conversion required DoubleConverter dc; dc.Double = d; dc.Uint = pa_bswap_64(dc.Uint); return dc.Double; } else return d; } template <> double buf2c(const double &d) { if( 1 != htons(1) ) { // little endian - conversion required DoubleConverter dc; dc.Double = d; dc.Uint = pa_bswap_64(dc.Uint); return dc.Double; } else return d; } // int template <> int c2buf(const int &i) { return htonl(i); } template <> int buf2c(const int &i) { return ntohl(i); } // long template <> long c2buf(const long &i) { return htonl(i); } template <> long buf2c(const long &i) { return ntohl(i); } // short template <> short c2buf(const short &i) { return htons(i); } template <> short buf2c(const short &i) { return ntohs(i); } /////////////////////////////////////////////////////////////////////////////// // template declarations // // pack_traits // /// Traits class for basic_packarray<>. Handle endian conversions, /// type sizing, index value conversionss. Also governs whether the /// pack array grows on demand or not. /// template class pack_traits { public: static Type c2buf(const Type &t) { // default to no endian conversion for now return ::c2buf(t); } static Type buf2c(const Type &t) { // default to no endian conversion for now return ::buf2c(t); } static size_t type_size() { return sizeof(Type); } static size_t field_index(size_t byte_offset) { return byte_offset / type_size(); } static size_t buffer_index(size_t _field_index) { return _field_index * type_size(); } static bool strict_size() { return StrictSize; } }; // // basic_packarray // /// Main pack array class. Allows packing of Type objects into buffer /// of type BufferType using Traits. Type can be any copy-constructable, /// POD object with the proper endian conversion members. The entire /// object is saved as-is to the buffer. /// /// BufferType is normally std::string, but can be any buffer object with /// the same API. /// template class basic_packarray { // data storage mutable BufferType data_; mutable bool converted_; //< true if all Types in data_ //< are in host byte order /// Return index to end of non-zero data in data_, using the size /// of Type as the increment. i.e. if last non-zero byte in data_ /// is offset 3, and the size of Type is 4 bytes, then return /// ((3 - 0) / 4) + 1 = 1 (1 being the Type-size offset of the end) size_t find_end() const { size_t end = data_.size(); while( end && data_[end] == 0 ) end--; return Traits::field_index(end) + 1; } /// Check size of data_ buffer and increase it if necessary. /// Never decreases the size. /// /// May throw std::out_of_range if in strict-size mode. /// void check_size(size_t field_index) const { size_t required_size = Traits::buffer_index(field_index) + Traits::type_size(); if( required_size > data_.size() ) { if( Traits::strict_size() ) { throw std::out_of_range("index out of range " "in basic_packarray"); } else { data_.resize(required_size, 0); } } } /// Adjust data_ buffer to smallest possible size, with enough /// space to hold all types (i.e. data_.size() % type_size() must be 0) void resize() const { // find_end() will return field_index 1 past end... // then convert that to buffer_index, which will be // 1 byte past end, which equals the size of the buffer // we wish to store size_t limited_size = Traits::buffer_index(find_end()); data_.resize(limited_size, 0); } /// Convert to network byte order static void c2buf(BufferType &buf) { for( size_t i = 0; i < buf.size(); i += Traits::type_size() ){ Type &t = reinterpret_cast (buf[i]); t = Traits::c2buf(t); } } /// Convert to host byte order static void buf2c(BufferType &buf) { for( size_t i = 0; i < buf.size(); i += Traits::type_size() ){ Type &t = reinterpret_cast (buf[i]); t = Traits::buf2c(t); } } /// Convert to host byte order if necessary void convert() const { if( !converted_ ) { buf2c(data_); converted_ = true; } } public: ////////////////////////////////////////////////////////////////////// // constructors // Nothing explicit here, since static_cast conversion is desired // // All constructors accept data in buffer format, with each item // in network byte order. basic_packarray() : converted_(true) { } basic_packarray(const char *data, size_t size) : data_(data, size), converted_(false) { resize(); } basic_packarray(const BufferType &data) : data_(data), converted_(false) { resize(); } ////////////////////////////////////////////////////////////////////// // array access Type& operator[] (size_t field_index) { check_size(field_index); convert(); size_t i = Traits::buffer_index(field_index); return reinterpret_cast (data_[i]); } const Type& operator[] (size_t field_index) const { check_size(field_index); convert(); size_t i = Traits::buffer_index(field_index); return reinterpret_cast (data_[i]); } ////////////////////////////////////////////////////////////////////// // buffer conversion /// Return BufferType in proper condition for storage. /// Size of buffer is trimmed at this stage, to exclude any trailing /// zeros. BufferType get() const { resize(); BufferType buffer = data_; if( converted_ ) { // in host byte order, convert back to network c2buf(buffer); } return buffer; } }; #ifdef __TEST_MODE__ #include #include /////////////////////////////////////////////////////////////////////////////// // And the unit tests! std::ostream& operator<< (std::ostream &os, const std::string &str) { os << "Size: " << std::setbase(10) << str.size() << std::endl; os << "buffer: "; for( size_t i = 0; i < str.size(); i++ ) os << std::setw(2) << std::setbase(16) << (unsigned int)(unsigned char)str.data()[i] << " "; return os; } template void test(T &tpack) { using namespace std; cout << "Original object:" << endl; cout << tpack.get() << endl; cout << "tpack[0]: " << tpack[0] << endl; cout << "tpack[1]: " << tpack[1] << endl; cout << "tpack[2]: " << tpack[2] << endl; T copy = tpack.get(); cout << "Copy:" << endl; cout << "copy[0]: " << copy[0] << endl; cout << "copy[1]: " << copy[1] << endl; cout << "copy[2]: " << copy[2] << endl; if( tpack[0] == copy[0] && tpack[1] == copy[1] && tpack[2] == copy[2] && tpack.get() == copy.get() ) cout << "Comparison succeeded" << endl; else throw std::logic_error("Bad comparison"); } int main() { double_pack dpack; dpack[0] = 42.42; dpack[1] = 6.12; dpack[2] = 7.5; test(dpack); int_pack ipack; ipack[0] = 5; ipack[1] = 6; ipack[2] = 7; test(ipack); short_pack spack; spack[0] = 5; spack[1] = 6; spack[2] = 7; test(spack); #ifdef __COMPILE_TEST_MODE__ // this shouldn't compile, with const correctness const int_pack &pr = ipack; pr[0] = 1; #endif } #endif