/* PIP - Platform Independent Primitives Byte array Copyright (C) 2019 Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "pibytearray.h" #include "pistring.h" #include /*! \class PIByteArray * \brief Byte array * \details This class based on PIDeque and provide some handle function * to manipulate it. * * \section PIByteArray_sec0 Usage * %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 Base 64 convertions and checksums: * * plain 8-bit * * plain 32-bit * * 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: * \snippet pibytearray.cpp 0 * * Or you can descibe stream operator of your own type and store/restore vectors of * your type: * \snippet pibytearray.cpp 1 * * 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. * \snippet pibytearray.cpp 2 * * \section PIByteArray_sec1 Attention * 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(). * \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; } uchar PIByteArray::checksumPlain8() const { uchar c = 0; int sz = size_s(); for (int i = 0; i < sz; ++i) c += at(i); c = ~(c + 1); return c; } uint PIByteArray::checksumPlain32() const { uint c = 0; int sz = size_s(); for (int i = 0; i < sz; ++i) c += at(i) * (i + 1); c = ~(c + 1); return c; } uint PIByteArray::hash() const { return piHash(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(), v.size()); s.remove(0, v.size()); } return s; }