262 lines
8.8 KiB
C++
262 lines
8.8 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"
|
||
|
||
//! \addtogroup Core
|
||
//! \{
|
||
//! \class PIChunkStream pichunkstream.h
|
||
//! \brief
|
||
//! \~english Class for binary de/serialization
|
||
//! \~russian Класс для бинарной де/сериализации
|
||
//!
|
||
//! \~english \section PIChunkStream_sec0 Synopsis
|
||
//! \~russian \section PIChunkStream_sec0 Краткий обзор
|
||
//! \~english
|
||
//! 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.
|
||
//!
|
||
//! \~russian
|
||
//! Этот класс предоставляет очень удобный механизм для сохранения и извлечения значений
|
||
//! в/из \a PIByteArray. Главным плюсом является то, что данные не будут зависеть от порядка
|
||
//! и наличия значений.
|
||
//!
|
||
//! \~english \section PIChunkStream_sec1 Mechanism
|
||
//! \~russian \section PIChunkStream_sec1 Механизм
|
||
//! \~english
|
||
//! %PIChunkStream works with items called "chunk". Chunk is an ID, size and any value that
|
||
//! can be stored and restored to/from %PIChunkStream with stream operators << and >>.
|
||
//!
|
||
//! To construct %PIChunkStream for writing data use any non-default constructor. Empty constructor
|
||
//! creates internal 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
|
||
//! readed chunk. Then you can get value of this chunk with functions \a getData() or \a get(),
|
||
//! but you should definitely know type of this value. You can read from byte array
|
||
//! while \a atEnd() if false.
|
||
//!
|
||
//! \~russian
|
||
//! %PIChunkStream работает с элементами под названием "чанк". Чанк имеет ID, размер и значение,
|
||
//! и может быть записан или прочитан в/из %PIChunkStream с помощью операторов << и >>.
|
||
//!
|
||
//! Для создания потока на запись используется любой не-умолчальный конструктор. Пустой конструктор
|
||
//! создает внутренний буфер, который можно получить с помощью метода \a data().
|
||
//! Непустой конструктор работает с переданным байтовым массивом.
|
||
//!
|
||
//! Для чтения чанков из байтового массива используется метод \a read(), который возвращает
|
||
//! ID прочитанного чанка. Получить значение этого чанка далее можно с помощью методов \a getData() или get(),
|
||
//! но тип значения должен быть известен. Читать из потока можно пока метод \a atEnd() возвращает ложь.
|
||
//!
|
||
//! \~english \section PIChunkStream_sec2 Examples
|
||
//! \~russian \section PIChunkStream_sec2 Пример
|
||
//!
|
||
//! \~english Using simple operator and cascade serialization:
|
||
//! \~russian Использование простого оператора и каскадная сериализация:
|
||
//!
|
||
//! \~english Prepare your structs to work with %PIChunkStream:
|
||
//! \~russian Подготовка своей структуры для работы с %PIChunkStream:
|
||
//! \~\snippet pichunkstream.cpp struct
|
||
//! \~english Old-style writing to %PIChunkStream:
|
||
//! \~russian Старый стиль использования %PIChunkStream:
|
||
//! \~\snippet pichunkstream.cpp write
|
||
//! \~english Fastest reading from %PIChunkStream:
|
||
//! \~russian Самое быстрое чтение из %PIChunkStream:
|
||
//! \~\snippet pichunkstream.cpp read
|
||
//!
|
||
//! \~english And next code show how to serialize your struct with %PIChunkStream:
|
||
//! \~russian Следующий код показывает, как сериализовать свою структуру в %PIChunkStream:
|
||
//! \~\snippet pichunkstream.cpp write_new
|
||
//!
|
||
//! \~english ... and deserialize:
|
||
//! \~russian ... в десериализовать:
|
||
//! \~\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();
|
||
}
|
||
|
||
|
||
PIByteArray PIChunkStream::data() const {
|
||
if (first_byte_taken) {
|
||
PIByteArray ret(*data_);
|
||
ret.prepend((uchar)(0x80 | version_));
|
||
return ret;
|
||
}
|
||
return *data_;
|
||
}
|
||
|
||
|
||
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::replaceChunk(int id, const PIByteArray & v) {
|
||
if (!data_map.contains(id)) return;
|
||
auto & pos(data_map[id]);
|
||
PIByteArray nsba;
|
||
writeVInt(nsba, v.size());
|
||
int size_mod = (v.size_s() + nsba.size_s()) - (pos.length + pos.size_bytes);
|
||
pos.length = v.size_s();
|
||
if (size_mod != 0) {
|
||
auto it = data_map.makeIterator();
|
||
while (it.next())
|
||
if (it.valueRef().start > pos.start)
|
||
it.valueRef().start += size_mod;
|
||
if (size_mod > 0)
|
||
data_->insert(pos.start, PIByteArray(size_mod));
|
||
else
|
||
data_->remove(pos.start, -size_mod);
|
||
}
|
||
memcpy(data_->data(pos.start - pos.size_bytes), nsba.data(), nsba.size());
|
||
pos.start += nsba.size_s() - pos.size_bytes;
|
||
memcpy(data_->data(pos.start), v.data(), pos.length);
|
||
}
|
||
|
||
|
||
void PIChunkStream::readAll() {
|
||
data_map.clear();
|
||
if (!data_) return;
|
||
int pos = 0, sz = data_->size_s(), hsz = 0;
|
||
uint csz = 0, cid = 0;
|
||
PIByteArray hdr;
|
||
while (pos < sz) {
|
||
pos += peekVInt((Version)version_, data_, pos, hdr, cid);
|
||
hsz = peekVInt((Version)version_, data_, pos, hdr, csz);
|
||
pos += hsz;
|
||
data_map[cid] = CacheEntry(pos, csz, hsz);
|
||
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() {
|
||
first_byte_taken = false;
|
||
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(); first_byte_taken = true; 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);
|
||
}
|