/* PIP - Platform Independent Primitives Byte array Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@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 "pibytearray.h" #include "pistringlist.h" #include //! \class PIByteArray //! //! \~\brief //! \~english The PIByteArray class provides an array of bytes //! \~russian Класс PIByteArray представляет собой массив байтов //! //! \~\details //! \~english //! %PIByteArray used to store raw bytes. //! It can be constructed from any data and size. //! You can use %PIByteArray as binary stream //! to serialize/deserialize any objects and data. //! This class based on PIDeque and provide some handle function //! to manipulate it. //! \~russian //! %PIByteArray используется для хранения байтов. //! Он может быть сконструирован из любых даных. //! Можно использовать %PIByteArray как потоковый объект //! для сериализации/десериализации любых типов и данных. //! Этот класс наследован от PIDeque и предоставляет набор //! удобных методов для работы с байтами. //! //! \~english \section PIByteArray_sec0 Usage //! \~russian \section PIByteArray_sec0 Использование //! \~english //! %PIByteArray can be used to store custom data and manipulate it. There are many //! stream operators to store/restore common types to byte array. Store operators //! places data at the end of array, restore operators takes data from the beginning //! of array. //! In addition there are Hex and Base64 convertions //! \~russian //! %PIByteArray может быть использован для сохранения любых данных и работы с ними. //! Он предоставляет множество операторов для сохранения/извлечения общих типов. //! Операторы сохранения добавляют данные в конец массива, а операторы извлечения //! берут данные из его начала. //! //! \~english //! One of the major usage of %PIByteArray is stream functions. You can form binary //! packet from many types (also dynamic types, e.g. PIVector) with one line: //! \~russian //! Один из основных сценариев использования %PIByteArray - это потоковый объект. //! Можно сформировать пакет бинарных данных из многих типов (также и контейнеров, //! например, PIVector) в одну строку: //! \~\snippet pibytearray.cpp 0 //! //! \~english //! Or you can descibe stream operator of your own type and store/restore vectors of //! your type: //! \~russian //! Также можно описать операторы сохранения/извлечения для собственных типов: //! \~\snippet pibytearray.cpp 1 //! //! \~english //! For store/restore custom data blocks there is PIByteArray::RawData class. Stream //! operators of this class simply store/restore data block to/from byte array: //! \~russian //! Для сохранения/извлечения блоков произвольных данных используется класс PIByteArray::RawData. //! Потоковые операторы для него просто сохраняют/извлекают блоки байтов: //! \~\snippet pibytearray.cpp 2 //! //! \~english \section PIByteArray_sec1 Attention //! \~russian \section PIByteArray_sec1 Внимание //! \~english //! Stream operator of %PIByteArray store byte array as vector, not simply append //! content of byte array. This operators useful to transmit custom data as %PIByteArray //! packed into parent byte array, e.g. to form packet from %PIByteArray. //! To append one byte array to another use funtion \a append(). //! \~russian //! Потоковый оператор для типа %PIByteArray сохраняет его как контейнер, //! а не просто добавляет его содержимое в конец. Этот оператор полезен для управляемой //! упаковки произвольных данных в виде %PIByteArray. //! Для добавления содержимого одного байтового массива к другому используется //! метов \a append(). //! \~\snippet pibytearray.cpp 3 //! static const uchar base64Table[64] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f}; static const uchar base64InvTable[256] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x0, 0x0, 0x3F, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; struct base64HelpStruct { base64HelpStruct() {v = 0;} inline void setBytes(const uchar * r, int size = 3) { v = 0; switch (size) { case 3: v |= r[2]; case 2: v |= r[1] << 8; case 1: v |= r[0] << 16; } } inline void getBytes(uchar * r) { r[0] = (v >> 16) & 0xFF; r[1] = (v >> 8) & 0xFF; r[2] = v & 0xFF; } inline void setAscii(const uchar * r, int size = 4) { v = 0; switch (size) { case 4: v |= (base64InvTable[r[3]] & 0x3F); case 3: v |= (base64InvTable[r[2]] & 0x3F) << 6; case 2: v |= (base64InvTable[r[1]] & 0x3F) << 12; case 1: v |= (base64InvTable[r[0]] & 0x3F) << 18; } } inline void getAscii(uchar * r) { r[0] = base64Table[(v >> 18) & 0x3F]; r[1] = base64Table[(v >> 12) & 0x3F]; r[2] = base64Table[(v >> 6) & 0x3F]; r[3] = base64Table[ v & 0x3F]; } uint v; }; PIByteArray &PIByteArray::convertToBase64() { return *this = toBase64(); } PIByteArray &PIByteArray::convertFromBase64() { return *this = fromBase64(*this); } PIByteArray PIByteArray::toBase64() const { if (isEmpty()) return PIByteArray(); base64HelpStruct hs; PIByteArray ret; int sz = (size_s() / 3) * 3, ri = -1; uchar t[4]; ret.resize(((size_s() - 1) / 3 + 1) * 4); for (int i = 0; i < sz; i += 3) { hs.setBytes(data(i)); hs.getAscii(t); ret[++ri] = (t[0]); ret[++ri] = (t[1]); ret[++ri] = (t[2]); ret[++ri] = (t[3]); } int der = size_s() % 3; switch (der) { case 1: hs.setBytes(data(sz), 1); hs.getAscii(t); ret[++ri] = (t[0]); ret[++ri] = (t[1]); ret[++ri] = ('='); ret[++ri] = ('='); break; case 2: hs.setBytes(data(sz), 2); hs.getAscii(t); ret[++ri] = (t[0]); ret[++ri] = (t[1]); ret[++ri] = (t[2]); ret[++ri] = ('='); break; default: break; } return ret; } PIByteArray PIByteArray::fromBase64(const PIByteArray & base64) { if (base64.isEmpty()) return PIByteArray(); base64HelpStruct hs; PIByteArray ret; int sz = base64.size_s(), ind = -1; uchar t[4]; ret.resize(sz / 4 * 3); for (int i = 0; i < sz; i += 4) { hs.setAscii(base64.data(i)); hs.getBytes(t); ret[++ind] = (t[0]); ret[++ind] = (t[1]); ret[++ind] = (t[2]); } if (base64.back() == '=') ret.pop_back(); if (sz > 1) if (base64[sz - 2] == '=') ret.pop_back(); return ret; } PIByteArray PIByteArray::fromBase64(const PIString & base64) { return fromBase64(base64.toByteArray()); } PIByteArray & PIByteArray::compressRLE(uchar threshold) { PIByteArray t; uchar fb, clen, mlen = 255 - threshold; for (uint i = 0; i < size();) { fb = at(i); clen = 1; while (at(++i) == fb) { ++clen; if (clen == mlen) break; } if (clen > 1) { t.push_back(threshold + clen); t.push_back(fb); continue; } if (fb >= threshold) { t.push_back(threshold + 1); t.push_back(fb); } else t.push_back(fb); } *this = t; return *this; } PIByteArray & PIByteArray::decompressRLE(uchar threshold) { PIByteArray t; uchar fb, clen; for (uint i = 0; i < size(); ++i) { fb = at(i); if (fb >= threshold) { clen = fb - threshold; fb = at(++i); for (uint j = 0; j < clen; ++j) t.push_back(fb); continue; } else t.push_back(fb); } *this = t; return *this; } //! \~\details //! \~english //! This is simple sum of all bytes, if "inverse" then add 1 and inverse. //! Pseudocode: //! \~russian //! Это простая сумма всех байтов, если "inverse", то ещё добавляется 1 и инвертируется результат. //! Псевдокод: //! \~\code //! for (i) //! sum += at(i); //! if (inverse) return ~(sum + 1); //! else return sum; //! \endcode uchar PIByteArray::checksumPlain8(bool inverse) const { uchar c = 0; int sz = size_s(); for (int i = 0; i < sz; ++i) c += at(i); if (inverse) c = ~(c + 1); return c; } //! \~\details //! \~english //! This is sum of all bytes multiplied by index+1, if inverse then add 1 and inverse. //! Pseudocode: //! \~russian //! Это простая сумма всех байтов, умноженных на индекс+1, если "inverse", то ещё добавляется 1 и инвертируется результат. //! Псевдокод: //! \~\code //! for (i) //! sum += at(i) * (i + 1); //! if (inverse) return ~(sum + 1); //! else return sum; //! \endcode uint PIByteArray::checksumPlain32(bool inverse) const { uint c = 0; int sz = size_s(); for (int i = 0; i < sz; ++i) c += at(i) * (i + 1); if (inverse) c = ~(c + 1); return c; } uint PIByteArray::hash() const { return piHashData(data(), size_s()); } PIString PIByteArray::toString(int base) const { PIString ret; int sz = size_s(); for (int i = 0; i < sz; ++i) { if (i > 0) ret += " "; if (base == 2) ret += "b"; if (base == 8) ret += "0"; if (base == 16) ret += "0x"; ret += PIString::fromNumber(at(i), base); } return ret; } PIString PIByteArray::toHex() const { PIByteArray hex(size() * 2); uchar *hexData = hex.data(); const uchar *d = data(); for (int i = 0; i < size_s(); ++i) { int j = (d[i] >> 4) & 0xf; if (j <= 9) hexData[i*2] = (j + '0'); else hexData[i*2] = (j + 'a' - 10); j = d[i] & 0xf; if (j <= 9) hexData[i*2+1] = (j + '0'); else hexData[i*2+1] = (j + 'a' - 10); } return PIString(hex); } PIByteArray PIByteArray::fromUserInput(PIString str) { PIByteArray ret; if (str.trim().isEmpty()) return ret; str.replaceAll("\n", " ").replaceAll("\t", " ").replaceAll(" ", " "); PIStringList bl(str.split(" ")); bool ok(false); piForeachC (PIString & b, bl) { int bv = b.toInt(-1, &ok); if (ok) ret << uchar(bv); } return ret; } PIByteArray PIByteArray::fromHex(PIString str) { PIByteArray hexEncoded = str.toByteArray(); PIByteArray res((hexEncoded.size() + 1)/ 2); uchar *result = res.data() + res.size(); bool odd_digit = true; for (int i = hexEncoded.size() - 1; i >= 0; --i) { int ch = hexEncoded.at(i); int tmp; if (ch >= '0' && ch <= '9') tmp = ch - '0'; else if (ch >= 'a' && ch <= 'f') tmp = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') tmp = ch - 'A' + 10; else continue; if (odd_digit) { --result; *result = tmp; odd_digit = false; } else { *result |= tmp << 4; odd_digit = true; } } res.remove(0, result - res.data()); return res; } PICout operator <<(PICout s, const PIByteArray & ba) { s.space(); s.setControl(0, true); s << "{"; for (uint i = 0; i < ba.size(); ++i) { s << ba[i]; if (i < ba.size() - 1) s << ", "; } s << "}"; s.restoreControl(); return s; } #ifdef PIP_STD_IOSTREAM std::ostream &operator <<(std::ostream & s, const PIByteArray & ba) { s << "{"; for (uint i = 0; i < ba.size(); ++i) { s << ba[i]; if (i < ba.size() - 1) s << ", "; } s << "}"; return s; } #endif PIByteArray & operator >>(PIByteArray & s, PIByteArray & v) { if (s.size_s() < 4) { s.clear(); v.clear(); return s; } assert(s.size_s() >= 4); int sz = 0; s >> sz; if (sz > s.size_s()) { piCout << "[PIByteArray] Warning: operator >> wants too much data, discard!" << sz << s.size_s(); s.clear(); v.clear(); return s; } v.resize(sz); if (sz > 0) { memcpy(v.data(), s.data(), sz); s.remove(0, sz); } return s; } PIByteArray & operator <<(PIByteArray & s, const PIByteArray & v) { s << int(v.size_s()); int os = s.size_s(); if (v.size_s() > 0) { s.enlarge(v.size_s()); memcpy(s.data(os), v.data(), v.size()); } return s; }