/*
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 .
*/
#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(&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_, 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);
}