991 lines
26 KiB
C++
991 lines
26 KiB
C++
#ifndef SIGNALSMITH_CBOR_WALKER_H
|
|
#define SIGNALSMITH_CBOR_WALKER_H
|
|
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#ifndef UINT64_MAX
|
|
# define UINT64_MAX 0xFFFFFFFFFFFFFFFFull;
|
|
#endif
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#if __cplusplus >= 201703L
|
|
# define CBOR_WALKER_USE_STRING_VIEW
|
|
# include <string_view>
|
|
#endif
|
|
#if __cplusplus >= 202002L
|
|
# define CBOR_WALKER_USE_BIT_CAST
|
|
# include <bit>
|
|
#endif
|
|
|
|
namespace signalsmith { namespace cbor {
|
|
|
|
struct CborWalker {
|
|
CborWalker(uint64_t errorCode=ERROR_NOT_INITIALISED) : CborWalker(nullptr, nullptr, errorCode) {}
|
|
CborWalker(const std::vector<unsigned char> &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<class Fn>
|
|
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<class Fn>
|
|
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<class Fn>
|
|
TaggedCborWalker forEach(Fn &&fn) const {
|
|
return CborWalker::forEach([&](const CborWalker &item, size_t i){
|
|
fn(TaggedCborWalker{item}, i);
|
|
});
|
|
}
|
|
template<class Fn>
|
|
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<<widthLog2;
|
|
return length()/stride;
|
|
}
|
|
|
|
template<class Array>
|
|
size_t readTypedArray(Array &&array) const {
|
|
return readTypedArray(array, 0, typedArrayLength());
|
|
}
|
|
|
|
template<class Array>
|
|
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, uint16_t, uint16_t>(array, offset, maxCount, bigEndian);
|
|
case 66:
|
|
return typedArrayReadInner<Array, uint32_t, uint32_t>(array, offset, maxCount, bigEndian);
|
|
case 67:
|
|
return typedArrayReadInner<Array, uint64_t, uint64_t>(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, uint16_t, int16_t>(array, offset, maxCount, bigEndian);
|
|
case 74:
|
|
return typedArrayReadInner<Array, uint32_t, int32_t>(array, offset, maxCount, bigEndian);
|
|
case 75:
|
|
return typedArrayReadInner<Array, uint64_t, int64_t>(array, offset, maxCount, bigEndian);
|
|
// floating-point
|
|
case 80:
|
|
// TODO: half-precision float support
|
|
return 0;
|
|
case 81:
|
|
return typedArrayReadInner<Array, uint32_t, float, true>(array, offset, maxCount, bigEndian);
|
|
case 82:
|
|
return typedArrayReadInner<Array, uint64_t, double, true>(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<class Array, typename UIntType, typename ResultT, bool bitcast=false>
|
|
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<ResultT>(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<ResultT>(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<unsigned char> &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<uint32_t>(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<uint64_t>(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<uint16_t>(arr, length, bigEndian);
|
|
return *this;
|
|
}
|
|
CborWriter & addTypedArray(const uint32_t *arr, size_t length, bool bigEndian=false) {
|
|
addTag(bigEndian ? 66 : 70);
|
|
writeTypedBlock<uint32_t>(arr, length, bigEndian);
|
|
return *this;
|
|
}
|
|
CborWriter & addTypedArray(const uint64_t *arr, size_t length, bool bigEndian=false) {
|
|
addTag(bigEndian ? 67 : 71);
|
|
writeTypedBlock<uint64_t>(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<uint16_t>(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<uint32_t>(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<uint64_t>(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<uint32_t>(arr[i]);
|
|
#else
|
|
float v = arr[i];
|
|
uint32_t vi;
|
|
std::memcpy(&vi, &v, 4);
|
|
return vi;
|
|
#endif
|
|
}
|
|
} unsignedArray{arr};
|
|
writeTypedBlock<uint32_t>(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<uint64_t>(arr[i]);
|
|
#else
|
|
double v = arr[i];
|
|
uint64_t vi;
|
|
std::memcpy(&vi, &v, 8);
|
|
return vi;
|
|
#endif
|
|
}
|
|
} unsignedArray{arr};
|
|
writeTypedBlock<uint64_t>(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<typename UIntType, class Array>
|
|
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<unsigned char> &bytes;
|
|
};
|
|
|
|
}} // namespace
|
|
|
|
#endif // include guard
|