Files
pip/libs/main/serialization/pichunkstream.cpp
2022-11-25 21:35:39 +03:00

264 lines
8.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 pichunkstream.h
//! \details
//! \~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_) >> PIMemoryBlock(last_data.data(), last_data.size_s());
break;
default: break;
}
return last_id;
}
int PIChunkStream::peekVInt(Version version_, uchar * data_, int sz, uint & ret) {
switch (version_) {
case Version_1:
memcpy(&ret, data_, 4);
return 4;
case Version_2: {
PIByteArray hdr(data_, piMini(4, sz));
hdr.resize(4);
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.value().start > pos.start) {
it.value().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;
while (pos < sz) {
pos += peekVInt((Version)version_, data_->data(pos), data_->size_s() - pos, cid);
hsz = peekVInt((Version)version_, data_->data(pos), data_->size_s() - pos, 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];
uchar abc;
s >> bytes[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);
}