/* 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); }