#ifndef SIGNALSMITH_CBOR_WALKER_H #define SIGNALSMITH_CBOR_WALKER_H #include #include #include #ifndef UINT64_MAX # define UINT64_MAX 0xFFFFFFFFFFFFFFFFull; #endif #include #include #if __cplusplus >= 201703L # define CBOR_WALKER_USE_STRING_VIEW # include #endif #if __cplusplus >= 202002L # define CBOR_WALKER_USE_BIT_CAST # include #endif namespace signalsmith { namespace cbor { struct CborWalker { CborWalker(uint64_t errorCode=ERROR_NOT_INITIALISED) : CborWalker(nullptr, nullptr, errorCode) {} CborWalker(const std::vector &vector) : CborWalker(vector.data(), vector.data() + vector.size()) {} CborWalker(const unsigned char *data, const unsigned char *dataEnd) : data(data), dataEnd(dataEnd) { if (data >= dataEnd) { typeCode = TypeCode::error; additional = ERROR_END_OF_DATA; return; } unsigned char head = *data; typeCode = (TypeCode)(head>>5); unsigned char remainder = head&0x1F; switch (remainder) { case 24: additional = data[1]; dataNext = data + 2; break; case 25: if (typeCode == TypeCode::simple) { #ifdef CBOR_WALKER_HALF_PRECISION_FLOAT // Translated from RFC 8949 Appendix D uint16_t half = ((uint16_t)data[1]<<8) + data[2]; uint16_t exponent = (half>>10)&0x001F; uint16_t mantissa = half&0x03FF; double value; if (exponent == 0) { value = std::ldexpf(mantissa, -24); } else if (exponent == 31) { value = (mantissa == 0) ? INFINITY : NAN; } else { value = std::ldexpf(mantissa + 1024, exponent - 25); } typeCode = TypeCode::float32; float32 = (half&0x8000) ? -value : value; #else float32 = 0; #endif } else { additional = (uint64_t(data[1])<<8)|uint64_t(data[2]); } dataNext = data + 3; break; case 26: if (typeCode == TypeCode::simple) { typeCode = TypeCode::float32; } additional = (uint64_t(data[1])<<24)|(uint64_t(data[2])<<16)|(uint64_t(data[3])<<8)|uint64_t(data[4]); dataNext = data + 5; break; case 27: if (typeCode == TypeCode::simple) { typeCode = TypeCode::float64; } additional = (uint64_t(data[1])<<56)|(uint64_t(data[2])<<48)|(uint64_t(data[3])<<40)|(uint64_t(data[4])<<32)|(uint64_t(data[5])<<24)|(uint64_t(data[6])<<16)|(uint64_t(data[7])<<8)|uint64_t(data[8]); dataNext = data + 9; break; case 28: case 29: case 30: typeCode = TypeCode::error; additional = ERROR_INVALID_ADDITIONAL; dataNext = data; case 31: additional = 0; // returns 0 length for indefinite values switch (typeCode) { case TypeCode::integerP: case TypeCode::integerN: case TypeCode::tag: typeCode = TypeCode::error; additional = ERROR_INVALID_ADDITIONAL; break; case TypeCode::bytes: typeCode = TypeCode::indefiniteBytes; break; case TypeCode::utf8: typeCode = TypeCode::indefiniteUtf8; break; case TypeCode::array: typeCode = TypeCode::indefiniteArray; break; case TypeCode::map: typeCode = TypeCode::indefiniteMap; break; case TypeCode::simple: typeCode = TypeCode::indefiniteBreak; break; default: typeCode = TypeCode::error; additional = ERROR_SHOULD_BE_IMPOSSIBLE; break; } default: additional = remainder; dataNext = data + 1; break; } } // All error codes are non-zero, so can be checked with `.error()` static constexpr uint64_t ERROR_END_OF_DATA = 1; static constexpr uint64_t ERROR_INVALID_ADDITIONAL = 2; static constexpr uint64_t ERROR_INVALID_VALUE = 3; static constexpr uint64_t ERROR_INCONSISTENT_INDEFINITE = 4; static constexpr uint64_t ERROR_NOT_INITIALISED = 5; static constexpr uint64_t ERROR_METHOD_TYPE_MISMATCH = 6; static constexpr uint64_t ERROR_SHOULD_BE_IMPOSSIBLE = 7; CborWalker next(size_t count) const { CborWalker result = *this; for (size_t i = 0; i < count; ++i) { ++result; } return result; } CborWalker next() const { switch (typeCode) { case TypeCode::integerP: case TypeCode::integerN: case TypeCode::simple: case TypeCode::float32: case TypeCode::float64: case TypeCode::indefiniteBreak: return nextBasic(); case TypeCode::bytes: case TypeCode::utf8: return {dataNext + additional, dataEnd}; return {dataNext + additional, dataEnd}; case TypeCode::array: { auto result = nextBasic(); auto length = additional; for (uint64_t i = 0; i < length; ++i) { ++result; } return result; } case TypeCode::map: { auto result = nextBasic(); auto length = additional; for (uint64_t i = 0; i < length; ++i) { ++result; ++result; } return result; } case TypeCode::indefiniteBytes: { auto result = nextBasic(); while (!result.error() && result.typeCode != TypeCode::indefiniteBreak) { if (result.typeCode != TypeCode::bytes) { return {data, dataEnd, ERROR_INCONSISTENT_INDEFINITE}; } ++result; } return result.nextBasic(); } case TypeCode::indefiniteUtf8: { auto result = nextBasic(); while (!result.error() && result.typeCode != TypeCode::indefiniteBreak) { if (result.typeCode != TypeCode::utf8) { return {data, dataEnd, ERROR_INCONSISTENT_INDEFINITE}; } ++result; } return result.nextBasic(); } case TypeCode::indefiniteArray: { auto result = nextBasic(); while (!result.error() && result.typeCode != TypeCode::indefiniteBreak) { result = result.next(); } return result.nextBasic(); } case TypeCode::indefiniteMap: { auto result = nextBasic(); while (!result.error() && result.typeCode != TypeCode::indefiniteBreak) { ++result; ++result; } return result.nextBasic(); } case TypeCode::tag: { // Skip all the tags first auto result = nextBasic(); while (result.isTagged()) result = nextBasic(); return result.next(); } case TypeCode::error: return *this; } } // ++Prefix increments the position, and returns itself CborWalker & operator++() { *this = next(); return *this; } // Postfix++ increments the position, but returns the old position CborWalker operator++(int) { CborWalker result = *this; *this = next(); return result; } CborWalker enter() const { switch (typeCode) { case TypeCode::integerP: case TypeCode::integerN: case TypeCode::simple: case TypeCode::float32: case TypeCode::float64: case TypeCode::indefiniteBreak: case TypeCode::bytes: case TypeCode::utf8: return next(); case TypeCode::tag: case TypeCode::array: case TypeCode::map: case TypeCode::indefiniteBytes: case TypeCode::indefiniteUtf8: case TypeCode::indefiniteArray: case TypeCode::indefiniteMap: return nextBasic(); case TypeCode::error: return *this; } } CborWalker nextExit() const { CborWalker result = *this; while (!result.error() && !result.isExit()) { ++result; } return result.nextBasic(); } uint64_t error() const { return typeCode == TypeCode::error ? additional : 0; } bool isSimple() const { return typeCode == TypeCode::simple; } bool isBool() const { if (typeCode != TypeCode::simple) return false; return (additional == 20 || additional == 21); } explicit operator bool() const { return (additional == 21); } bool isNull() const { return typeCode == TypeCode::simple && additional == 22; } bool isUndefined() const { return typeCode == TypeCode::simple && additional == 23; } bool isExit() const { return typeCode == TypeCode::indefiniteBreak; } bool atEnd() const { return typeCode == TypeCode::error && additional == ERROR_END_OF_DATA; } bool isNumber() const { return isFloat() || isInt(); } bool isInt() const { return typeCode == TypeCode::integerP || typeCode == TypeCode::integerN; } operator uint64_t() const { switch (typeCode) { case TypeCode::integerP: case TypeCode::bytes: case TypeCode::utf8: case TypeCode::array: case TypeCode::map: case TypeCode::tag: case TypeCode::simple: case TypeCode::error: return (int64_t)additional; case TypeCode::integerN: return (uint64_t)-1 - (uint64_t)additional; return additional; case TypeCode::float32: return (uint64_t)float32; case TypeCode::float64: return (uint64_t)float64; default: return 0; } } operator int64_t() const { switch (typeCode) { case TypeCode::integerP: case TypeCode::bytes: case TypeCode::utf8: case TypeCode::array: case TypeCode::map: case TypeCode::tag: case TypeCode::simple: case TypeCode::error: return (int64_t)additional; case TypeCode::integerN: return -1 - (int64_t)additional; case TypeCode::float32: return (uint64_t)float32; case TypeCode::float64: return (uint64_t)float64; default: return 0; } } operator uint32_t() const { return (uint32_t)(uint64_t)(*this); } operator uint16_t() const { return (uint16_t)(uint64_t)(*this); } operator uint8_t() const { return (uint32_t)(uint64_t)(*this); } operator size_t() const { return (size_t)(uint64_t)(*this); } // For the signed ones, we cast from the signed 64-bit operator int32_t() const { return (int32_t)(int64_t)(*this); } operator int16_t() const { return (int16_t)(int64_t)(*this); } operator int8_t() const { return (int8_t)(int64_t)(*this); } bool isFloat() const { return typeCode == TypeCode::float32 || typeCode == TypeCode::float64; } operator double() const { switch (typeCode) { case TypeCode::float32: return float32; case TypeCode::float64: return float64; case TypeCode::integerP: return (uint64_t)(*this); case TypeCode::integerN: return (int64_t)(*this); default: return 0; } } operator float() const { return (float)(double)(*this); } bool isBytes() const { return typeCode == TypeCode::bytes || typeCode == TypeCode::indefiniteBytes; } bool isUtf8() const { return typeCode == TypeCode::utf8 || typeCode == TypeCode::indefiniteUtf8; } bool hasLength() const { return typeCode != TypeCode::indefiniteBytes && typeCode != TypeCode::indefiniteUtf8 && typeCode != TypeCode::indefiniteArray && typeCode != TypeCode::indefiniteMap; } size_t length() const { return (size_t)(*this); } const unsigned char * bytes() const { return dataNext; } std::string utf8() const { if (typeCode != TypeCode::utf8) return ""; return {(const char *)dataNext, length()}; } #ifdef CBOR_WALKER_USE_STRING_VIEW std::string_view utf8View() const { if (typeCode != TypeCode::utf8) return {nullptr, 0}; return {(const char *)dataNext, length()}; } #endif bool isArray() const { return typeCode == TypeCode::array || typeCode == TypeCode::indefiniteArray; } template CborWalker forEach(Fn &&fn, bool mapValues=true) const { if (typeCode == TypeCode::array) { size_t count = length(); CborWalker item = enter(); for (size_t i = 0; i < count; ++i) { if (item.error()) return item; CborWalker value = item++; fn(value, i); } return item; } else if (typeCode == TypeCode::indefiniteArray) { CborWalker item = enter(); size_t i = 0; while (!item.error() && !item.isExit()) { fn(item++, i++); } return item.next(); // move past the exit } else if (typeCode == TypeCode::indefiniteBytes) { CborWalker item = enter(); size_t i = 0; while (!item.error() && !item.isExit()) { if (item.typeCode != TypeCode::bytes) return {data, dataEnd, ERROR_INVALID_VALUE}; fn(item++, i++); } return item.next(); // move past the exit } else if (typeCode == TypeCode::indefiniteUtf8) { CborWalker item = enter(); size_t i = 0; while (!item.error() && !item.isExit()) { if (item.typeCode != TypeCode::utf8) return {data, dataEnd, ERROR_INVALID_VALUE}; fn(item++, i++); } return item.next(); // move past the exit } else if (typeCode == TypeCode::map) { size_t count = length(); CborWalker item = enter(); for (size_t i = 0; i < count; ++i) { if (item.error()) return item; CborWalker key = item++; if (item.error()) return item; CborWalker value = item++; fn(mapValues ? value : key, i); } return item; } else if (typeCode == TypeCode::indefiniteMap) { CborWalker item = enter(); size_t i = 0; while (!item.error() && !item.isExit()) { CborWalker key = item++; if (item.error()) return item; if (item.isExit()) return {item.data, item.dataEnd, ERROR_INVALID_VALUE}; CborWalker value = item++; fn(mapValues ? value : key, i); ++i; } return item.next(); // move past the exit } return {data, dataEnd, ERROR_METHOD_TYPE_MISMATCH}; } bool isMap() const { return typeCode == TypeCode::map || typeCode == TypeCode::indefiniteMap; } template CborWalker forEachPair(Fn &&fn) const { if (typeCode == TypeCode::map) { size_t count = length(); CborWalker item = enter(); for (size_t i = 0; i < count; ++i) { auto key = item++; if (key.error() || item.error()) return item; auto value = item++; fn(key, value); } return item; } else if (typeCode == TypeCode::indefiniteMap) { CborWalker item = enter(); while (!item.error() && !item.isExit()) { auto key = item++; if (key.error() || item.error()) return item; if (item.isExit()) return {item.data, item.dataEnd, ERROR_INVALID_VALUE}; auto value = item++; fn(CborWalker(key), CborWalker(value)); } return item.next(); // move past the exit } return {data, dataEnd, ERROR_METHOD_TYPE_MISMATCH}; } bool isEnd() const { return typeCode == TypeCode::array || typeCode == TypeCode::indefiniteArray; } bool isTagged() const { return typeCode == TypeCode::tag; } protected: CborWalker(const unsigned char *data, const unsigned char *dataEnd, uint64_t errorCode) : data(data), dataEnd(dataEnd), dataNext(nullptr), typeCode(TypeCode::error), additional(errorCode) {} // The next *core* value - but doesn't check whether the current value is the header for a string/array/etc. CborWalker nextBasic() const { return {dataNext, dataEnd}; } const unsigned char *data, *dataEnd, *dataNext; enum class TypeCode { integerP, integerN, bytes, utf8, array, map, tag, simple, float32, float64, error, indefiniteBreak, indefiniteBytes, indefiniteUtf8, indefiniteArray, indefiniteMap }; TypeCode typeCode; union { uint64_t additional; float float32; double float64; unsigned char additionalBytes[8]; }; }; // Automatically skips over tags, but still lets you query them struct TaggedCborWalker : public CborWalker { TaggedCborWalker() {} TaggedCborWalker(const CborWalker& basic) : CborWalker(basic), tagStart(data) { consumeTags(); } TaggedCborWalker(const unsigned char *dataStart, const unsigned char *dataEnd) : CborWalker(dataStart, dataEnd), tagStart(dataStart) { consumeTags(); } TaggedCborWalker next(size_t i=1) const { return CborWalker::next(i); } TaggedCborWalker & operator++() { CborWalker::operator++(); return *this; } TaggedCborWalker operator++(int _) { return CborWalker::operator++(_); } TaggedCborWalker enter() const { return CborWalker::enter(); } TaggedCborWalker nextExit() const { return CborWalker::nextExit(); } template TaggedCborWalker forEach(Fn &&fn) const { return CborWalker::forEach([&](const CborWalker &item, size_t i){ fn(TaggedCborWalker{item}, i); }); } template TaggedCborWalker forEachPair(Fn &&fn) const { return CborWalker::forEachPair([&](const CborWalker &key, const CborWalker &value){ fn(TaggedCborWalker{key}, TaggedCborWalker{value}); }); } size_t tagCount() const { return nTags; } uint64_t tag(size_t tagIndex) const { CborWalker tagWalker(tagStart, dataEnd); for (size_t i = 0; i < tagIndex; ++i) { tagWalker = tagWalker.enter(); } return tagWalker; } bool isTypedArray() const { return isBytes() && typedArrayTag; } size_t typedArrayLength() const { uint8_t widthLog2 = typedArrayTag&0x03; uint8_t elementType = (typedArrayTag&0x18)>>3; // unsigned, signed, float widthLog2 += (elementType == 2); // int sizes are 8-64 bits, float sizes are 16-128 size_t stride = 1< size_t readTypedArray(Array &&array) const { return readTypedArray(array, 0, typedArrayLength()); } template size_t readTypedArray(Array &&array, size_t offset, size_t maxCount) const { size_t byteLength = length(); bool bigEndian = !(typedArrayTag&0x04); switch (typedArrayTag&0xFB) { // without endian flag // unsigned int case 64: { size_t count = std::min(maxCount, byteLength); const uint8_t *bytes = dataNext + offset; for (size_t i = 0; i < count; ++i) { array[i] = bytes[i]; } return count; } case 65: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 66: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 67: return typedArrayReadInner(array, offset, maxCount, bigEndian); // signed int case 72: { size_t count = std::min(maxCount, byteLength); const uint8_t *bytes = dataNext + offset; for (size_t i = 0; i < count; ++i) { array[i] = (int8_t)bytes[i]; // cast to signed here first, to make sure negatives behave correctly } return count; } case 73: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 74: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 75: return typedArrayReadInner(array, offset, maxCount, bigEndian); // floating-point case 80: // TODO: half-precision float support return 0; case 81: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 82: return typedArrayReadInner(array, offset, maxCount, bigEndian); case 83: // TODO: quad-precision float support return 0; default: return 0; } } private: size_t nTags = 0; const unsigned char *tagStart; uint8_t typedArrayTag = 0; void consumeTags() { while (isTagged() && data < dataEnd) { ++nTags; uint64_t tag = (*this); if (tag >= 64 && tag < 87) { // RFC-8746 range typedArrayTag = tag; } // Move "into" the tag CborWalker::operator=(enter()); } } template size_t typedArrayReadInner(Array &&array, size_t offset, size_t maxCount, bool bigEndian) const { constexpr size_t B = sizeof(UIntType); if (offset*B > length()) return 0; const uint8_t *bytes = dataNext + offset*B; size_t count = std::min(maxCount, length()/B - offset); if (bigEndian) { for (size_t i = 0; i < count; ++i) { UIntType v = 0; for (size_t b = 0; b < B; ++b) { UIntType bv = bytes[i*B + b]; v += bv<<((B - 1 - b)*8); } if (bitcast) { #ifdef CBOR_WALKER_USE_BIT_CAST array[i] = std::bit_cast(v); #else ResultT r; std::memcpy(&r, &v, B); array[i] = r; #endif } else { array[i] = (ResultT)v; } } } else { for (size_t i = 0; i < count; ++i) { UIntType v = 0; for (size_t b = 0; b < B; ++b) { UIntType bv = bytes[i*B + b]; v += bv<<(b*8); } if (bitcast) { #ifdef CBOR_WALKER_USE_BIT_CAST array[i] = std::bit_cast(v); #else ResultT r; std::memcpy(&r, &v, B); array[i] = r; #endif } else { array[i] = (ResultT)v; } } } return count; } }; struct CborWriter { CborWriter(std::vector &bytes) : bytes(bytes) {} CborWriter & addUInt(uint64_t u) { writeHead(0, u); return *this; } CborWriter & addInt(int64_t u) { if (u >= 0) { writeHead(0, u); } else { writeHead(1, -1 - u); } return *this; } CborWriter & addTag(uint64_t u) { writeHead(6, u); return *this; } CborWriter & addBool(bool b) { writeHead(7, 20 + b); return *this; } CborWriter & openArray() { bytes.push_back(0x9F); return *this; } CborWriter & openArray(size_t items) { writeHead(4, items); return *this; } CborWriter & openMap() { bytes.push_back(0xBF); return *this; } CborWriter & openMap(size_t pairs) { writeHead(5, pairs); return *this; } CborWriter & close() { bytes.push_back(0xFF); return *this; } CborWriter & addBytes(const void *ptr, size_t length) { addBytes((const unsigned char *)ptr, length); return *this; } CborWriter & addBytes(const unsigned char *ptr, size_t length) { writeHead(2, length); bytes.insert(bytes.end(), ptr, ptr + length); return *this; } CborWriter & openBytes() { bytes.push_back(0x5F); return *this; } CborWriter & addUtf8(const char *ptr, size_t length) { writeHead(3, length); bytes.insert(bytes.end(), ptr, ptr + length); return *this; } CborWriter & addUtf8(const char *str) { addUtf8(str, std::strlen(str)); return *this; } CborWriter & addUtf8(const std::string &str) { addUtf8(str.c_str()); return *this; } #ifdef CBOR_WALKER_USE_STRING_VIEW CborWriter & addUtf8(const std::string_view &str) { addUtf8(str.data(), str.size()); return *this; } #endif CborWriter & openUtf8() { bytes.push_back(0x7F); return *this; } CborWriter & addNull() { bytes.push_back(0xF6); return *this; } CborWriter & addUndefined() { bytes.push_back(0xF7); return *this; } CborWriter & addSimple(unsigned char k) { writeHead(7, k); return *this; } CborWriter & addFloat(float v) { bytes.push_back(0xFA); #ifdef CBOR_WALKER_USE_BIT_CAST uint32_t vi = std::bit_cast(v); #else uint32_t vi; std::memcpy(&vi, &v, 4); #endif for (size_t i = 0; i < 4; ++i) { auto shift = (3 - i)*8; bytes.push_back((vi>>shift)&0xFF); } return *this; } CborWriter & addFloat(double v) { bytes.push_back(0xFB); #ifdef CBOR_WALKER_USE_BIT_CAST uint64_t vi = std::bit_cast(v); #else uint64_t vi; std::memcpy(&vi, &v, 8); #endif for (size_t i = 0; i < 8; ++i) { auto shift = (7 - i)*8; bytes.push_back((vi>>shift)&0xFF); } return *this; } // RFC-8746 tags for typed arrays // bits: [1, 0] = log2(elementBytes), [2] = isLittleEndian, [3, 4] = [unsigned, signed, float] CborWriter & addTypedArray(const uint8_t *arr, size_t length) { addTag(64); addBytes((const void *)arr, length); return *this; } CborWriter & addTypedArray(const int8_t *arr, size_t length) { addTag(72); addBytes((const void *)arr, length); return *this; } CborWriter & addTypedArray(const uint16_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 65 : 69); writeTypedBlock(arr, length, bigEndian); return *this; } CborWriter & addTypedArray(const uint32_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 66 : 70); writeTypedBlock(arr, length, bigEndian); return *this; } CborWriter & addTypedArray(const uint64_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 67 : 71); writeTypedBlock(arr, length, bigEndian); return *this; } // For signed ints, we make a proxy struct which casts them on-the-fly CborWriter & addTypedArray(const int16_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 73 : 77); struct { const int16_t *arr; uint16_t operator[](size_t i) const { return (uint16_t)(arr[i]); } } unsignedArray{arr}; writeTypedBlock(unsignedArray, length, bigEndian); return *this; } CborWriter & addTypedArray(const int32_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 74 : 78); struct { const int32_t *arr; uint32_t operator[](size_t i) const { return (uint32_t)(arr[i]); } } unsignedArray{arr}; writeTypedBlock(unsignedArray, length, bigEndian); return *this; } CborWriter & addTypedArray(const int64_t *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 75 : 79); struct { const int64_t *arr; uint64_t operator[](size_t i) const { return (uint64_t)(arr[i]); } } unsignedArray{arr}; writeTypedBlock(unsignedArray, length, bigEndian); return *this; } CborWriter & addTypedArray(const float *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 81 : 85); struct { const float *arr; uint32_t operator[](size_t i) const { #ifdef CBOR_WALKER_USE_BIT_CAST return std::bit_cast(arr[i]); #else float v = arr[i]; uint32_t vi; std::memcpy(&vi, &v, 4); return vi; #endif } } unsignedArray{arr}; writeTypedBlock(unsignedArray, length, bigEndian); return *this; } CborWriter & addTypedArray(const double *arr, size_t length, bool bigEndian=false) { addTag(bigEndian ? 82 : 86); struct { const double *arr; uint64_t operator[](size_t i) const { #ifdef CBOR_WALKER_USE_BIT_CAST return std::bit_cast(arr[i]); #else double v = arr[i]; uint64_t vi; std::memcpy(&vi, &v, 8); return vi; #endif } } unsignedArray{arr}; writeTypedBlock(unsignedArray, length, bigEndian); return *this; } private: void writeHead(unsigned char type, uint64_t argument) { type <<= 5; if (argument >= 4294967296ul) { bytes.push_back(type|27); for (size_t i = 0; i < 8; ++i) { bytes.push_back(argument>>(56 - i*8)); } } else if (argument >= 65536) { bytes.push_back(type|26); for (size_t i = 0; i < 4; ++i) { bytes.push_back(argument>>(24 - i*8)); } } else if (argument >= 256) { bytes.push_back(type|25); bytes.push_back(argument>>8); bytes.push_back(argument); } else if (argument >= 24) { bytes.push_back(type|24); bytes.push_back(argument); } else { bytes.push_back(type|argument); } } template void writeTypedBlock(Array &&array, size_t length, bool bigEndian) { constexpr size_t B = sizeof(UIntType); writeHead(2, length*B); if (bigEndian) { for (size_t i = 0; i < length; ++i) { UIntType v = array[i]; for (size_t b = 0; b < B; ++b) bytes.push_back((v>>((B-1-b)*8))&0xFF); } } else { for (size_t i = 0; i < length; ++i) { UIntType v = array[i]; for (size_t b = 0; b < B; ++b) bytes.push_back((v>>(b*8))&0xFF); } } } std::vector &bytes; }; }} // namespace #endif // include guard