Compare commits
2 Commits
b4412624a9
...
5bfcf6f151
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bfcf6f151 | ||
|
|
cb21797593 |
21
crunch.h
21
crunch.h
@ -31,6 +31,7 @@ struct CrunchSTFX : public BaseEffect {
|
||||
ParamRange drive{4};
|
||||
ParamRange fuzz{0};
|
||||
ParamRange toneHz{2000};
|
||||
ParamRange cutHz{50};
|
||||
ParamRange outGain{1};
|
||||
|
||||
const bool autoGain;
|
||||
@ -52,6 +53,9 @@ struct CrunchSTFX : public BaseEffect {
|
||||
stfx::units::rangeHz(storage.range("toneHz", toneHz)
|
||||
.info("tone", "limits the brightness of the distortion")
|
||||
.range(100, 4000, 20000));
|
||||
stfx::units::rangeHz(storage.range("cutHz", cutHz)
|
||||
.info("cut", "prevents low frequencies from driving the distortion")
|
||||
.range(20, 100, 5000));
|
||||
|
||||
stfx::units::rangeGain(storage.range("outGain", outGain)
|
||||
.info("out", "output gain")
|
||||
@ -66,6 +70,7 @@ struct CrunchSTFX : public BaseEffect {
|
||||
|
||||
oversampler.resize(channels, config.maxBlockSize, oversampleHalfLatency, std::min(0.45, 21000/config.sampleRate));
|
||||
gainshapers.resize(channels);
|
||||
cutFilters.resize(channels);
|
||||
toneFilters.resize(channels);
|
||||
outputFilters.resize(channels);
|
||||
}
|
||||
@ -73,6 +78,7 @@ struct CrunchSTFX : public BaseEffect {
|
||||
void reset() {
|
||||
oversampler.reset();
|
||||
for (auto &g : gainshapers) g.reset();
|
||||
for (auto &f : cutFilters) f.reset();
|
||||
for (auto &f : toneFilters) f.reset();
|
||||
for (auto &f : outputFilters) f.reset();
|
||||
}
|
||||
@ -88,13 +94,18 @@ struct CrunchSTFX : public BaseEffect {
|
||||
double outputGainFrom = outGain.from();
|
||||
double outputGainTo = outGain.to();
|
||||
if (autoGain) {
|
||||
Sample averageGain = gainshapers[0].averageGain(autoGainLevel*drive.from());
|
||||
outputGainFrom /= drive.from()*averageGain;
|
||||
outputGainTo /= drive.to()*gainshapers[0].averageGain(autoGainLevel*drive.to());
|
||||
Sample cutRatioFrom = 1 - cutHz.from()/(cutHz.from() + 200);
|
||||
Sample averageGainFrom = gainshapers[0].averageGain(autoGainLevel*cutRatioFrom*drive.from());
|
||||
outputGainFrom /= drive.from()*averageGainFrom;
|
||||
Sample cutRatioTo = 1 - cutHz.to()/(cutHz.to() + 200);
|
||||
Sample averageGainTo = gainshapers[0].averageGain(autoGainLevel*cutRatioTo*drive.to());
|
||||
outputGainTo /= drive.to()*averageGainTo;
|
||||
}
|
||||
auto outputGain = block.smooth(outputGainFrom, outputGainTo);
|
||||
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
auto &cutFilter = cutFilters[c];
|
||||
cutFilter.highpass(cutHz/(config.sampleRate*2));
|
||||
auto &gainshaper = gainshapers[c];
|
||||
gainshaper.setFuzzFactor(fuzz);
|
||||
auto &toneFilter = toneFilters[c];
|
||||
@ -107,7 +118,7 @@ struct CrunchSTFX : public BaseEffect {
|
||||
for (int i = 0; i < block.length*2; ++i) {
|
||||
double hi = i*0.5;
|
||||
Sample x = samples[i]*inputGain.at(hi);
|
||||
Sample gain = gainshaper(x)*outputGain.at(hi);
|
||||
Sample gain = gainshaper(cutFilter(x))*outputGain.at(hi);
|
||||
Sample y = x*toneFilter(gain);
|
||||
samples[i] = outputFilter(y);
|
||||
}
|
||||
@ -162,7 +173,7 @@ private:
|
||||
};
|
||||
std::vector<GainshapeADAA> gainshapers;
|
||||
using Filter = signalsmith::filters::BiquadStatic<Sample>;
|
||||
std::vector<Filter> toneFilters, outputFilters;
|
||||
std::vector<Filter> cutFilters, toneFilters, outputFilters;
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include "./param-info.h"
|
||||
#include "../storage/storage.h"
|
||||
|
||||
namespace stfx { namespace clap {
|
||||
|
||||
@ -233,6 +234,12 @@ struct Plugin : public clap_plugin {
|
||||
audio_ports_get
|
||||
};
|
||||
return &ext;
|
||||
} else if (!std::strcmp(extId, CLAP_EXT_STATE)) {
|
||||
static struct clap_plugin_state ext{
|
||||
state_save,
|
||||
state_load
|
||||
};
|
||||
return &ext;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -472,6 +479,98 @@ struct Plugin : public clap_plugin {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool writeToStream(const unsigned char *bytes, size_t length, const clap_ostream *stream) {
|
||||
size_t index = 0;
|
||||
while (index < length) {
|
||||
size_t remaining = length - index;
|
||||
auto written = stream->write(stream, bytes + index, remaining);
|
||||
if (written <= 0) return false;
|
||||
index += written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool fillFromStream(const clap_istream *stream, std::vector<unsigned char> &buffer) {
|
||||
buffer.resize(0);
|
||||
size_t chunkSize = 1024;
|
||||
size_t length = 0;
|
||||
while (1) {
|
||||
buffer.resize(length + chunkSize);
|
||||
auto read = stream->read(stream, buffer.data() + length, chunkSize);
|
||||
length += read;
|
||||
if (read < 0) {
|
||||
buffer.resize(0);
|
||||
return false;
|
||||
} else if (read == 0) {
|
||||
buffer.resize(length);
|
||||
return true;
|
||||
}
|
||||
chunkSize = buffer.size();
|
||||
}
|
||||
}
|
||||
|
||||
struct StateWriter : public StorageCborWriter<true, StateWriter> {
|
||||
using StorageCborWriter<true, StateWriter>::StorageCborWriter;
|
||||
|
||||
void info(const char *, const char *) {}
|
||||
int version(int v) {
|
||||
LOG_EXPR(v);
|
||||
(*this)("version", v);
|
||||
return v;
|
||||
}
|
||||
template<class RP>
|
||||
PInfoPlaceholder range(const char *key, RP ¶m) {
|
||||
double v = param;
|
||||
(*this)(key, v);
|
||||
return {};
|
||||
}
|
||||
template<class SP>
|
||||
PInfoPlaceholder stepped(const char *key, SP ¶m) {
|
||||
int v = param;
|
||||
(*this)(key, v);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
struct StateReader : public StorageCborReader<true, StateReader> {
|
||||
using StorageCborReader<true, StateReader>::StorageCborReader;
|
||||
|
||||
void info(const char *, const char *) {}
|
||||
int version(int v) {
|
||||
(*this)("version", v);
|
||||
return v;
|
||||
}
|
||||
template<class RP>
|
||||
PInfoPlaceholder range(const char *key, RP ¶m) {
|
||||
double v = param;
|
||||
(*this)(key, v);
|
||||
param = v;
|
||||
return {};
|
||||
}
|
||||
template<class SP>
|
||||
PInfoPlaceholder stepped(const char *key, SP ¶m) {
|
||||
int v = param;
|
||||
(*this)(key, v);
|
||||
param = v;
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<unsigned char> stateBuffer;
|
||||
static bool state_save(const clap_plugin *obj, const clap_ostream *stream) {
|
||||
auto &plugin = *(Plugin *)obj;
|
||||
auto &buffer = plugin.stateBuffer;
|
||||
StateWriter storage{buffer};
|
||||
plugin.effect.state(storage);
|
||||
return writeToStream(buffer.data(), buffer.size(), stream);
|
||||
}
|
||||
static bool state_load(const clap_plugin *obj, const clap_istream *stream) {
|
||||
auto &plugin = *(Plugin *)obj;
|
||||
auto &buffer = plugin.stateBuffer;
|
||||
if (!fillFromStream(stream, buffer)) return false;
|
||||
StateReader storage{buffer};
|
||||
plugin.effect.state(storage);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
@ -94,6 +94,34 @@ namespace stfx {
|
||||
}
|
||||
}
|
||||
|
||||
// When we want to ignore parameter info (covers both range and stepped)
|
||||
struct PInfoPlaceholder {
|
||||
PInfoPlaceholder & info(const char*, const char*) {
|
||||
return *this;
|
||||
}
|
||||
PInfoPlaceholder & info(std::string, std::string) {
|
||||
return *this;
|
||||
}
|
||||
template<typename V1, typename V2>
|
||||
PInfoPlaceholder & range(V1, V2) {
|
||||
return *this;
|
||||
}
|
||||
template<typename V1, typename V2, typename V3>
|
||||
PInfoPlaceholder & range(V1, V2, V3) {
|
||||
return *this;
|
||||
}
|
||||
template<class ...Args>
|
||||
PInfoPlaceholder & unit(Args...) {
|
||||
return *this;
|
||||
}
|
||||
PInfoPlaceholder & exact(double, const char *) {
|
||||
return *this;
|
||||
}
|
||||
template<typename ...Args>
|
||||
PInfoPlaceholder & label(Args ...) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
namespace {
|
||||
// A value which changes linearly within a given index range (e.g. a block)
|
||||
class LinearSegment {
|
||||
@ -286,36 +314,6 @@ namespace stfx {
|
||||
return prevBlock;
|
||||
}
|
||||
};
|
||||
|
||||
// When we want to ignore parameter info
|
||||
struct PInfoPlaceholder {
|
||||
PInfoPlaceholder & info(const char*, const char*) {
|
||||
return *this;
|
||||
}
|
||||
PInfoPlaceholder & info(std::string, std::string) {
|
||||
return *this;
|
||||
}
|
||||
template<typename V1, typename V2>
|
||||
PInfoPlaceholder & range(V1, V2) {
|
||||
return *this;
|
||||
}
|
||||
template<typename V1, typename V2, typename V3>
|
||||
PInfoPlaceholder & range(V1, V2, V3) {
|
||||
return *this;
|
||||
}
|
||||
template<class ...Args>
|
||||
PInfoPlaceholder & unit(Args...) {
|
||||
return *this;
|
||||
}
|
||||
PInfoPlaceholder & exact(double, const char *) {
|
||||
return *this;
|
||||
}
|
||||
template<typename ...Args>
|
||||
PInfoPlaceholder & label(Args ...) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// Base class for our effect to inherit from. Provides parameter classes and some default config.
|
||||
|
||||
959
stfx/storage/cbor-walker.h
Normal file
959
stfx/storage/cbor-walker.h
Normal file
@ -0,0 +1,959 @@
|
||||
#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) {}
|
||||
|
||||
void addUInt(uint64_t u) {
|
||||
writeHead(0, u);
|
||||
}
|
||||
void addInt(int64_t u) {
|
||||
if (u >= 0) {
|
||||
writeHead(0, u);
|
||||
} else {
|
||||
writeHead(1, -1 - u);
|
||||
}
|
||||
}
|
||||
void addTag(uint64_t u) {
|
||||
writeHead(6, u);
|
||||
}
|
||||
void addBool(bool b) {
|
||||
writeHead(7, 20 + b);
|
||||
}
|
||||
void openArray() {
|
||||
bytes.push_back(0x9F);
|
||||
}
|
||||
void openArray(size_t items) {
|
||||
writeHead(4, items);
|
||||
}
|
||||
void openMap() {
|
||||
bytes.push_back(0xBF);
|
||||
}
|
||||
void openMap(size_t pairs) {
|
||||
writeHead(5, pairs);
|
||||
}
|
||||
void close() {
|
||||
bytes.push_back(0xFF);
|
||||
}
|
||||
void addBytes(const void *ptr, size_t length) {
|
||||
addBytes((const unsigned char *)ptr, length);
|
||||
}
|
||||
void addBytes(const unsigned char *ptr, size_t length) {
|
||||
writeHead(2, length);
|
||||
bytes.insert(bytes.end(), ptr, ptr + length);
|
||||
}
|
||||
void openBytes() {
|
||||
bytes.push_back(0x5F);
|
||||
}
|
||||
void addUtf8(const char *ptr, size_t length) {
|
||||
writeHead(3, length);
|
||||
bytes.insert(bytes.end(), ptr, ptr + length);
|
||||
}
|
||||
void addUtf8(const char *str) {
|
||||
addUtf8(str, std::strlen(str));
|
||||
}
|
||||
void addUtf8(const std::string &str) {
|
||||
addUtf8(str.c_str());
|
||||
}
|
||||
#ifdef CBOR_WALKER_USE_STRING_VIEW
|
||||
void addUtf8(const std::string_view &str) {
|
||||
addUtf8(str.data(), str.size());
|
||||
}
|
||||
#endif
|
||||
void openUtf8() {
|
||||
bytes.push_back(0x7F);
|
||||
}
|
||||
void addNull() {
|
||||
bytes.push_back(0xF6);
|
||||
}
|
||||
void addUndefined() {
|
||||
bytes.push_back(0xF7);
|
||||
}
|
||||
void addSimple(unsigned char k) {
|
||||
writeHead(7, k);
|
||||
}
|
||||
|
||||
void 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);
|
||||
}
|
||||
}
|
||||
void 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);
|
||||
}
|
||||
}
|
||||
|
||||
// RFC-8746 tags for typed arrays
|
||||
// bits: [1, 0] = log2(elementBytes), [2] = isLittleEndian, [3, 4] = [unsigned, signed, float]
|
||||
void addTypedArray(const uint8_t *arr, size_t length) {
|
||||
addTag(64);
|
||||
addBytes((const void *)arr, length);
|
||||
}
|
||||
void addTypedArray(const int8_t *arr, size_t length) {
|
||||
addTag(72);
|
||||
addBytes((const void *)arr, length);
|
||||
}
|
||||
void addTypedArray(const uint16_t *arr, size_t length, bool bigEndian=false) {
|
||||
addTag(bigEndian ? 65 : 69);
|
||||
writeTypedBlock<uint16_t>(arr, length, bigEndian);
|
||||
}
|
||||
void addTypedArray(const uint32_t *arr, size_t length, bool bigEndian=false) {
|
||||
addTag(bigEndian ? 66 : 70);
|
||||
writeTypedBlock<uint32_t>(arr, length, bigEndian);
|
||||
}
|
||||
void addTypedArray(const uint64_t *arr, size_t length, bool bigEndian=false) {
|
||||
addTag(bigEndian ? 67 : 71);
|
||||
writeTypedBlock<uint64_t>(arr, length, bigEndian);
|
||||
}
|
||||
// For signed ints, we make a proxy struct which casts them on-the-fly
|
||||
void 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);
|
||||
}
|
||||
void 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);
|
||||
}
|
||||
void 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);
|
||||
}
|
||||
// Look, I'm not any happier about this than you are
|
||||
void 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);
|
||||
}
|
||||
void 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);
|
||||
}
|
||||
|
||||
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
|
||||
242
stfx/storage/storage.h
Normal file
242
stfx/storage/storage.h
Normal file
@ -0,0 +1,242 @@
|
||||
#pragma once
|
||||
|
||||
#include "./cbor-walker.h"
|
||||
|
||||
struct StorageDummy {
|
||||
template<class V>
|
||||
void operator()(const char *, V &) {}
|
||||
};
|
||||
|
||||
struct StorageExample {
|
||||
template<class V>
|
||||
void operator()(const char */*key*/, V &v) {
|
||||
value(v);
|
||||
}
|
||||
|
||||
private:
|
||||
void value(int64_t &v) {};
|
||||
void value(uint64_t &v) {};
|
||||
void value(int32_t &v) {};
|
||||
void value(uint32_t &v) {};
|
||||
void value(int16_t &v) {};
|
||||
void value(uint16_t &v) {};
|
||||
void value(int8_t &v) {};
|
||||
void value(uint8_t &v) {};
|
||||
void value(float &v) {};
|
||||
void value(double &v) {};
|
||||
void value(bool &v) {};
|
||||
|
||||
template<class Item>
|
||||
void value(std::vector<Item> &array) {
|
||||
for (auto &item : array) {
|
||||
value(item);
|
||||
}
|
||||
}
|
||||
|
||||
template<class V>
|
||||
void value(V &v) {
|
||||
v.state(*this);
|
||||
}
|
||||
};
|
||||
|
||||
namespace _impl {
|
||||
template<class Fallback, class MaybeVoid=void>
|
||||
struct VoidFallback {
|
||||
using T = MaybeVoid;
|
||||
};
|
||||
template<class Fallback>
|
||||
struct VoidFallback<Fallback, void> {
|
||||
using T = Fallback;
|
||||
};
|
||||
}
|
||||
|
||||
template<bool compact=false, class SubClassCRTP=void>
|
||||
struct StorageCborWriter {
|
||||
StorageCborWriter(const signalsmith::cbor::CborWriter &writer, std::vector<unsigned char> *buffer=nullptr) : cbor(writer) {
|
||||
if (buffer) buffer->resize(0);
|
||||
|
||||
if (compact) {
|
||||
cbor.addTag(0xCB68ADED); // turns into "2store..." when base64-encoded
|
||||
cbor.openArray();
|
||||
} else {
|
||||
cbor.openMap();
|
||||
}
|
||||
}
|
||||
StorageCborWriter(std::vector<unsigned char> &cborBuffer) : StorageCborWriter(signalsmith::cbor::CborWriter(cborBuffer), &cborBuffer) {}
|
||||
~StorageCborWriter() {
|
||||
cbor.close();
|
||||
}
|
||||
|
||||
template<class V>
|
||||
void operator()(const char *key, V &value) {
|
||||
if (!compact) cbor.addUtf8(key);
|
||||
writeValue(value);
|
||||
}
|
||||
|
||||
protected:
|
||||
signalsmith::cbor::CborWriter cbor;
|
||||
|
||||
#define STORAGE_BASIC_INT(V) \
|
||||
void writeValue(V &value) { \
|
||||
cbor.addInt(value); \
|
||||
}
|
||||
STORAGE_BASIC_INT(int64_t)
|
||||
STORAGE_BASIC_INT(uint64_t)
|
||||
STORAGE_BASIC_INT(int32_t)
|
||||
STORAGE_BASIC_INT(uint32_t)
|
||||
STORAGE_BASIC_INT(int16_t)
|
||||
STORAGE_BASIC_INT(uint16_t)
|
||||
STORAGE_BASIC_INT(int8_t)
|
||||
STORAGE_BASIC_INT(uint8_t)
|
||||
#undef STORAGE_BASIC_INT
|
||||
|
||||
void writeValue(float &value) {
|
||||
cbor.addFloat(value);
|
||||
}
|
||||
void writeValue(double &value) {
|
||||
cbor.addFloat(value);
|
||||
}
|
||||
void writeValue(bool &value) {
|
||||
cbor.addBool(value);
|
||||
}
|
||||
|
||||
void writeValue(const char *cStr) {
|
||||
cbor.addUtf8(cStr);
|
||||
}
|
||||
|
||||
template<class Item>
|
||||
void writeValue(std::vector<Item> &array) {
|
||||
cbor.openArray(array.size());
|
||||
for (auto &item : array) {
|
||||
writeValue(item);
|
||||
}
|
||||
}
|
||||
#define STORAGE_TYPED_ARRAY(T) \
|
||||
void writeValue(std::vector<T> &array) { \
|
||||
cbor.addTypedArray(array.data(), array.size()); \
|
||||
}
|
||||
STORAGE_TYPED_ARRAY(uint8_t)
|
||||
STORAGE_TYPED_ARRAY(int8_t)
|
||||
STORAGE_TYPED_ARRAY(uint16_t)
|
||||
STORAGE_TYPED_ARRAY(int16_t)
|
||||
STORAGE_TYPED_ARRAY(uint32_t)
|
||||
STORAGE_TYPED_ARRAY(int32_t)
|
||||
STORAGE_TYPED_ARRAY(uint64_t)
|
||||
STORAGE_TYPED_ARRAY(int64_t)
|
||||
STORAGE_TYPED_ARRAY(float)
|
||||
STORAGE_TYPED_ARRAY(double)
|
||||
#undef STORAGE_TYPED_ARRAY
|
||||
|
||||
using SubStorage = typename _impl::VoidFallback<StorageCborWriter, SubClassCRTP>::T;
|
||||
|
||||
template<class Obj>
|
||||
void writeValue(Obj &obj) {
|
||||
cbor.openMap();
|
||||
obj.state(*(SubStorage *)this);
|
||||
cbor.close();
|
||||
}
|
||||
};
|
||||
|
||||
template<bool compact=false, class SubClassCRTP=void>
|
||||
struct StorageCborReader {
|
||||
using Cbor = signalsmith::cbor::TaggedCborWalker;
|
||||
|
||||
StorageCborReader(Cbor c) : cbor(c) {
|
||||
if (compact) {
|
||||
if (cbor.isArray()) cbor = cbor.enter();
|
||||
} else {
|
||||
if (cbor.isMap()) cbor = cbor.enter();
|
||||
}
|
||||
}
|
||||
StorageCborReader(const std::vector<unsigned char> &v) : StorageCborReader(Cbor(v)) {}
|
||||
|
||||
template<class V>
|
||||
void operator()(const char *key, V &v) {
|
||||
if (filterKeyBytes != nullptr) {
|
||||
if (!keyMatch(key, filterKeyBytes, filterKeyLength)) return;
|
||||
} else if (!compact) {
|
||||
if (!cbor.isUtf8()) return; // We expect a string key
|
||||
if (!keyMatch(key, (const char *)cbor.bytes(), cbor.length())) {
|
||||
return; // key doesn't match
|
||||
}
|
||||
cbor++;
|
||||
}
|
||||
readValue(v);
|
||||
}
|
||||
|
||||
template<class Obj>
|
||||
void readValue(Obj &obj) {
|
||||
if (compact) {
|
||||
// No keys, no filters
|
||||
return obj.state(*(SubClass *)this);
|
||||
}
|
||||
|
||||
if (!cbor.isMap()) return;
|
||||
|
||||
cbor = cbor.forEachPair([&](Cbor key, Cbor value){
|
||||
if (!key.isUtf8()) return;
|
||||
|
||||
const char *fkb = filterKeyBytes;
|
||||
size_t fkl = filterKeyLength;
|
||||
|
||||
// Temporarily set key, and scan the object for that property
|
||||
filterKeyBytes = (const char *)key.bytes();
|
||||
filterKeyLength = key.length();
|
||||
cbor = value;
|
||||
obj.state(*(SubClass *)this);
|
||||
|
||||
filterKeyBytes = fkb;
|
||||
filterKeyLength = fkl;
|
||||
});
|
||||
}
|
||||
|
||||
#define STORAGE_BASIC_TYPE(V) \
|
||||
void readValue(V &v) { \
|
||||
v = V(cbor++); \
|
||||
}
|
||||
STORAGE_BASIC_TYPE(int64_t)
|
||||
STORAGE_BASIC_TYPE(uint64_t)
|
||||
STORAGE_BASIC_TYPE(int32_t)
|
||||
STORAGE_BASIC_TYPE(uint32_t)
|
||||
STORAGE_BASIC_TYPE(int16_t)
|
||||
STORAGE_BASIC_TYPE(uint16_t)
|
||||
STORAGE_BASIC_TYPE(int8_t)
|
||||
STORAGE_BASIC_TYPE(uint8_t)
|
||||
STORAGE_BASIC_TYPE(float)
|
||||
STORAGE_BASIC_TYPE(double)
|
||||
STORAGE_BASIC_TYPE(bool)
|
||||
#undef STORAGE_BASIC_TYPE
|
||||
|
||||
void readValue(std::string &v) {
|
||||
v = (cbor++).utf8();
|
||||
}
|
||||
|
||||
template<class Item>
|
||||
void readValue(std::vector<Item> &array) {
|
||||
if (!cbor.isArray()) return;
|
||||
size_t length = 0;
|
||||
cbor = cbor.forEach([&](Cbor item, size_t index){
|
||||
length = index + 1;
|
||||
if (array.size() < length) array.resize(length);
|
||||
|
||||
cbor = item;
|
||||
readValue(array[index]);
|
||||
});
|
||||
array.resize(length);
|
||||
}
|
||||
private:
|
||||
protected:
|
||||
Cbor cbor;
|
||||
|
||||
const char *filterKeyBytes = nullptr;
|
||||
size_t filterKeyLength = 0;
|
||||
|
||||
static bool keyMatch(const char *key, const char *filterKeyBytes, size_t filterKeyLength) {
|
||||
for (size_t i = 0; i < filterKeyLength; ++i) {
|
||||
if (key[i] != filterKeyBytes[i]) return false;
|
||||
}
|
||||
return key[filterKeyLength] == 0;
|
||||
}
|
||||
|
||||
using SubClass = typename _impl::VoidFallback<StorageCborReader, SubClassCRTP>::T;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user