195 lines
5.2 KiB
C++
195 lines
5.2 KiB
C++
/*
|
|
PIP - Platform Independent Primitives
|
|
Binary markup serializator
|
|
Ivan Pelipenko peri4ko@yandex.ru
|
|
|
|
This program 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 3 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "pichunkstream.h"
|
|
|
|
/*! \class PIChunkStream
|
|
* \brief Class for binary serialization
|
|
*
|
|
* \section PIChunkStream_sec0 Synopsis
|
|
* This class provides very handly mechanism to store and restore values to and from
|
|
* \a PIByteArray. The main advantage of using this class is that your binary data
|
|
* become independent from order and collection of your values.
|
|
*
|
|
* \section PIChunkStream_sec1 Mechanism
|
|
* %PIChunkStream works with items called "chunk". Chunk is an ID and any value that
|
|
* can be stored and restored to \a PIByteArray with stream operators << and >>.
|
|
* You can place chunks to stream and read chunks from stream.
|
|
*
|
|
* To construct %PIChunkStream for writing data use any constructor. Empty constructor
|
|
* creates internal empty buffer that can be accessed by function \a data().
|
|
* Non-empty constructor works with given byte array.
|
|
*
|
|
* To read chunks from byte array use function \a read() that returns ID of
|
|
* next chunk. Then you can get value of this chunk with function \a getData(),
|
|
* but you should definitely know type of this value. You can read from byte array
|
|
* while \a atEnd() if false.
|
|
*
|
|
* \section PIChunkStream_sec2 Examples
|
|
*
|
|
* Using simple operator and cascade serialization:
|
|
*
|
|
* Prepare your structs to work with %PIChunkStream:
|
|
* \snippet pichunkstream.cpp struct
|
|
* Old-style writing to %PIChunkStream:
|
|
* \snippet pichunkstream.cpp write
|
|
* Fastest reading from %PIChunkStream:
|
|
* \snippet pichunkstream.cpp read
|
|
*
|
|
* And next code show how to serialize your struct with %PIChunkStream:
|
|
* \snippet pichunkstream.cpp write_new
|
|
*
|
|
* ... and deserialize:
|
|
* \snippet pichunkstream.cpp read_new
|
|
*/
|
|
|
|
|
|
void PIChunkStream::setSource(const PIByteArray & data) {
|
|
data_ = const_cast<PIByteArray*>(&data);
|
|
_init();
|
|
}
|
|
|
|
|
|
void PIChunkStream::setSource(PIByteArray * data) {
|
|
data_ = (data ? data : &tmp_data);
|
|
_init();
|
|
}
|
|
|
|
|
|
int PIChunkStream::read() {
|
|
switch (version_) {
|
|
case Version_1:
|
|
(*data_) >> last_id >> last_data;
|
|
break;
|
|
case Version_2:
|
|
last_id = readVInt(*data_);
|
|
last_data.resize(readVInt(*data_));
|
|
//piCout << last_id << last_data.size();
|
|
(*data_) >> PIByteArray::RawData(last_data.data(), last_data.size_s());
|
|
break;
|
|
default: break;
|
|
}
|
|
return last_id;
|
|
}
|
|
|
|
|
|
int PIChunkStream::peekVInt(Version version_, PIByteArray * data_, int pos, PIByteArray & hdr, uint & ret) {
|
|
switch (version_) {
|
|
case Version_1:
|
|
memcpy(&ret, data_->data(pos), 4);
|
|
return 4;
|
|
case Version_2: {
|
|
hdr.resize(4);
|
|
hdr.fill(uchar(0));
|
|
memcpy(hdr.data(), data_->data(pos), piMini(4, data_->size_s() - pos));
|
|
uchar hsz = 0;
|
|
ret = readVInt(hdr, &hsz);
|
|
return hsz;
|
|
}
|
|
default: break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PIChunkStream::readAll() {
|
|
data_map.clear();
|
|
if (!data_) return;
|
|
int pos = 0, sz = data_->size_s();
|
|
uint csz = 0, cid = 0;
|
|
PIByteArray hdr;
|
|
while (pos < sz) {
|
|
pos += peekVInt((Version)version_, data_, pos, hdr, cid);
|
|
pos += peekVInt((Version)version_, data_, pos, hdr, csz);
|
|
data_map[cid] = PIPair<int, int>(pos, csz);
|
|
pos += csz;
|
|
}
|
|
}
|
|
|
|
|
|
PIChunkStream::~PIChunkStream() {
|
|
}
|
|
|
|
|
|
bool PIChunkStream::extract(PIByteArray & data, bool read_all) {
|
|
if (data.size_s() < 4) return false;
|
|
data >> tmp_data;
|
|
if (tmp_data.size_s() < 4) return false;
|
|
data_ = &tmp_data;
|
|
_init();
|
|
if (read_all)
|
|
readAll();
|
|
return true;
|
|
}
|
|
|
|
|
|
void PIChunkStream::_init() {
|
|
last_id = -1;
|
|
last_data.clear();
|
|
if (!data_->isEmpty()) {
|
|
uchar v = data_->at(0);
|
|
if ((v & 0x80) == 0x80) {
|
|
v &= 0x7f;
|
|
switch (v) {
|
|
case 2: version_ = (uchar)Version_2; data_->pop_front(); break;
|
|
default: version_ = Version_1; break;
|
|
}
|
|
} else
|
|
version_ = Version_1;
|
|
}
|
|
}
|
|
|
|
|
|
uint PIChunkStream::readVInt(PIByteArray & s, uchar * bytes_cnt) {
|
|
if (s.isEmpty()) return 0;
|
|
uchar bytes[4]; s >> bytes[0];
|
|
uchar abc = 0;
|
|
for (abc = 0; abc < 3; ++abc) {
|
|
uchar mask = (0x80 >> abc);
|
|
if ((bytes[0] & mask) == mask) {
|
|
bytes[0] &= (mask - 1);
|
|
s >> bytes[abc + 1];
|
|
} else break;
|
|
}
|
|
if (bytes_cnt) *bytes_cnt = (abc + 1);
|
|
uint ret = 0;
|
|
for (int i = 0; i <= abc; ++i) {
|
|
ret += (bytes[i] << (8 * ((int)abc - i)));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
void PIChunkStream::writeVInt(PIByteArray & s, uint val) {
|
|
if (val > 0xfffffff) return;
|
|
if (val <= 0x7f) {
|
|
s << uchar(val);
|
|
return;
|
|
}
|
|
if (val <= 0x3fff) {
|
|
s << uchar((val >> 8) | 0x80) << uchar(val & 0xff);
|
|
return;
|
|
}
|
|
if (val <= 0x1fffff) {
|
|
s << uchar((val >> 16) | 0xc0) << uchar((val >> 8) & 0xff) << uchar(val & 0xff);
|
|
return;
|
|
}
|
|
s << uchar((val >> 24) | 0xe0) << uchar((val >> 16) & 0xff) << uchar((val >> 8) & 0xff) << uchar(val & 0xff);
|
|
}
|