- /*
-
- UTILS MODULE
-
- Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
-
- */
-
- #include "utils.h"
-
- #include <limits>
-
- // We can only return small values (max 'z' aka 122)
- static constexpr uint8_t InvalidByte { 255u };
-
- static uint8_t bin_char2byte(char c) {
- switch (c) {
- case '0'...'1':
- return (c - '0');
- }
-
- return InvalidByte;
- }
-
- static uint8_t oct_char2byte(char c) {
- switch (c) {
- case '0'...'7':
- return (c - '0');
- }
-
- return InvalidByte;
- }
-
- static uint8_t dec_char2byte(char c) {
- switch (c) {
- case '0'...'9':
- return (c - '0');
- }
-
- return InvalidByte;
- }
-
- static uint8_t hex_char2byte(char c) {
- switch (c) {
- case '0'...'9':
- return (c - '0');
- case 'a'...'f':
- return 10 + (c - 'a');
- case 'A'...'F':
- return 10 + (c - 'A');
- }
-
- return InvalidByte;
- }
-
- static ParseUnsignedResult parseUnsignedImpl(espurna::StringView value, int base) {
- auto out = ParseUnsignedResult{
- .ok = false,
- .value = 0,
- };
-
- using Char2Byte = uint8_t(*)(char);
- Char2Byte char2byte = nullptr;
-
- switch (base) {
- case 2:
- char2byte = bin_char2byte;
- break;
- case 8:
- char2byte = oct_char2byte;
- break;
- case 10:
- char2byte = dec_char2byte;
- break;
- case 16:
- char2byte = hex_char2byte;
- break;
- }
-
- if (!char2byte) {
- return out;
- }
-
- for (auto it = value.begin(); it != value.end(); ++it) {
- const auto digit = char2byte(*it);
- if (digit == InvalidByte) {
- out.ok = false;
- goto err;
- }
-
- const auto value = out.value;
- out.value = out.value * uint32_t(base);
-
- // TODO explicitly set the output bit width?
- if (value > out.value) {
- out.ok = false;
- goto err;
- }
-
- out.value += uint32_t(digit);
- out.ok = true;
- }
-
- err:
- return out;
- }
-
- bool tryParseId(espurna::StringView value, size_t limit, size_t& out) {
- using T = std::remove_cvref<decltype(out)>::type;
- static_assert(std::is_same<T, size_t>::value, "");
-
- if (value.length()) {
- const auto result = parseUnsignedImpl(value, 10);
- if (result.ok && (result.value < limit)) {
- out = result.value;
- return true;
- }
- }
-
- return false;
- }
-
- bool tryParseIdPath(espurna::StringView value, size_t limit, size_t& out) {
- if (value.length()) {
- const auto before_begin = value.begin() - 1;
- for (auto it = value.end() - 1; it != before_begin; --it) {
- if ((*it) == '/') {
- return tryParseId(
- espurna::StringView(it + 1, value.end()),
- limit, out);
- }
- }
- }
-
- return false;
- }
-
- String prettyDuration(espurna::duration::Seconds seconds) {
- time_t timestamp = static_cast<time_t>(seconds.count());
- tm spec;
- gmtime_r(×tamp, &spec);
-
- char buffer[64];
- sprintf_P(buffer, PSTR("%02dy %02dd %02dh %02dm %02ds"),
- (spec.tm_year - 70), spec.tm_yday, spec.tm_hour,
- spec.tm_min, spec.tm_sec);
-
- return String(buffer);
- }
-
- // -----------------------------------------------------------------------------
- // SSL
- // -----------------------------------------------------------------------------
-
- bool sslCheckFingerPrint(const char * fingerprint) {
- return (strlen(fingerprint) == 59);
- }
-
- bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray) {
-
- // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
- if (!sslCheckFingerPrint(fingerprint)) return false;
-
- // walk the fingerprint
- for (unsigned int i=0; i<20; i++) {
- bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
- }
-
- return true;
-
- }
-
- bool sslFingerPrintChar(const char * fingerprint, char * destination) {
-
- // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
- if (!sslCheckFingerPrint(fingerprint)) return false;
-
- // copy it
- strncpy(destination, fingerprint, 59);
-
- // walk the fingerprint replacing ':' for ' '
- for (unsigned char i = 0; i<59; i++) {
- if (destination[i] == ':') destination[i] = ' ';
- }
-
- return true;
-
- }
-
- // -----------------------------------------------------------------------------
- // Helper functions
- // -----------------------------------------------------------------------------
-
- double roundTo(double num, unsigned char positions) {
- double multiplier = 1;
- while (positions-- > 0) multiplier *= 10;
- return round(num * multiplier) / multiplier;
- }
-
- // ref. https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
- // the machine epsilon has to be scaled to the magnitude of the values used
- // and multiplied by the desired precision in ULPs (units in the last place)
- // unless the result is subnormal
- bool almostEqual(double lhs, double rhs, int ulp) {
- return __builtin_fabs(lhs - rhs) <= std::numeric_limits<double>::epsilon() * __builtin_fabs(lhs + rhs) * ulp
- || __builtin_fabs(lhs - rhs) < std::numeric_limits<double>::min();
- }
-
- bool almostEqual(double lhs, double rhs) {
- return almostEqual(lhs, rhs, 3);
- }
-
- espurna::StringView stripNewline(espurna::StringView value) {
- if ((value.length() >= 2)
- && (*(value.end() - 1) == '\n')
- && (*(value.end() - 2) == '\r')) {
- value = espurna::StringView(value.begin(), value.end() - 2);
- } else if ((value.length() >= 1) && (*(value.end() - 1) == '\n')) {
- value = espurna::StringView(value.begin(), value.end() - 1);
- }
-
- return value;
- }
-
- bool isNumber(espurna::StringView view) {
- bool dot { false };
- bool digit { false };
-
- for (auto ptr = view.begin(); ptr != view.end(); ++ptr) {
- switch (*ptr) {
- case '-':
- case '+':
- if (ptr != view.begin()) {
- return false;
- }
- break;
-
- case '.':
- if (dot) {
- return false;
- }
- dot = true;
- break;
-
- case '0' ... '9':
- digit = true;
- break;
-
- case 'a' ... 'z':
- case 'A' ... 'Z':
- return false;
- }
- }
-
- return digit;
- }
-
- // ref: lwip2 lwip_strnstr with strnlen
- char* strnstr(const char* buffer, const char* token, size_t n) {
- const auto token_len = strnlen_P(token, n);
- if (!token_len) {
- return const_cast<char*>(buffer);
- }
-
- const auto first = pgm_read_byte(token);
- for (const char* p = buffer; *p && (p + token_len <= buffer + n); p++) {
- if ((*p == first) && (strncmp_P(p, token, token_len) == 0)) {
- return const_cast<char*>(p);
- }
- }
-
- return nullptr;
- }
-
- ParseUnsignedResult parseUnsigned(espurna::StringView value, int base) {
- return parseUnsignedImpl(value, base);
- }
-
- static constexpr int base_from_char(char c) {
- return (c == 'b') ? 2 :
- (c == 'o') ? 8 :
- (c == 'x') ? 16 : 0;
- }
-
- ParseUnsignedResult parseUnsigned(espurna::StringView value) {
- int base = 10;
-
- if (value.length() && (value.length() > 2)) {
- const auto from_base = base_from_char(value[1]);
- if ((value[0] == '0') && (from_base != 0)) {
- base = from_base;
- value = espurna::StringView(
- value.begin() + 2, value.end());
- }
- }
-
- return parseUnsignedImpl(value, base);
- }
-
- String formatUnsigned(uint32_t value, int base) {
- constexpr size_t BufferSize { 8 * sizeof(decltype(value)) };
-
- String result;
- if (base == 2) {
- result += "0b";
- } else if (base == 8) {
- result += "0o";
- } else if (base == 16) {
- result += "0x";
- }
-
- char buffer[BufferSize + 1] = {0};
- ultoa(value, buffer, base);
- result += buffer;
-
- return result;
- }
-
- namespace {
-
- // From a byte array to an hexa char array ("A220EE...", double the size)
- template <typename T>
- const uint8_t* hexEncodeImpl(const uint8_t* in_begin, const uint8_t* in_end, T&& callback) {
- static const char base16[] = "0123456789ABCDEF";
-
- constexpr uint8_t Left { 0xf0 };
- constexpr uint8_t Right { 0xf };
- constexpr uint8_t Shift { 4 };
-
- auto* in_ptr = in_begin;
- for (; in_ptr != in_end; ++in_ptr) {
- const char buf[2] {
- base16[((*in_ptr) & Left) >> Shift],
- base16[(*in_ptr) & Right]
- };
- if (!callback(buf)) {
- break;
- }
- }
-
- return in_ptr;
- }
-
- } // namespace
-
- char* hexEncode(const uint8_t* in_begin, const uint8_t* in_end, char* out_begin, char* out_end) {
- char* out_ptr { out_begin };
-
- hexEncodeImpl(in_begin, in_end, [&](const char (&byte)[2]) {
- *(out_ptr) = byte[0];
- ++out_ptr;
-
- *(out_ptr) = byte[1];
- ++out_ptr;
-
- return out_ptr != out_end;
- });
-
- return out_ptr;
- }
-
- String hexEncode(const uint8_t* in_begin, const uint8_t* in_end) {
- String out;
- out.reserve(in_end - in_begin);
-
- hexEncodeImpl(in_begin, in_end, [&](const char (&byte)[2]) {
- out.concat(byte, 2);
- return true;
- });
-
- return out;
- }
-
- size_t hexEncode(const uint8_t* in, size_t in_size, char* out, size_t out_size) {
- if (out_size >= ((in_size * 2) + 1)) {
- char* out_ptr = hexEncode(in, in + in_size, out, out + out_size);
- *out_ptr = '\0';
- ++out_ptr;
- return out_ptr - out;
- }
-
- return 0;
- }
-
- // From an hexa char array ("A220EE...") to a byte array (half the size)
- uint8_t* hexDecode(const char* in_begin, const char* in_end, uint8_t* out_begin, uint8_t* out_end) {
- constexpr uint8_t Shift { 4 };
-
- const char* in_ptr { in_begin };
- uint8_t* out_ptr { out_begin };
- while ((in_ptr != in_end) && (out_ptr != out_end)) {
- uint8_t lhs = hex_char2byte(*in_ptr);
- if (lhs == InvalidByte) {
- break;
- }
- ++in_ptr;
-
- uint8_t rhs = hex_char2byte(*in_ptr);
- if (rhs == InvalidByte) {
- break;
- }
- ++in_ptr;
-
- (*out_ptr) = (lhs << Shift) | rhs;
- ++out_ptr;
- }
-
- return out_ptr;
- }
-
- size_t hexDecode(const char* in, size_t in_size, uint8_t* out, size_t out_size) {
- if ((in_size & 1) || (out_size < (in_size / 2))) {
- return 0;
- }
-
- uint8_t* out_ptr { hexDecode(in, in + in_size, out, out + out_size) };
- return out_ptr - out;
- }
-
- size_t consumeAvailable(Stream& stream) {
- const auto result = stream.available();
- if (result <= 0) {
- return 0;
- }
-
- const auto available = static_cast<size_t>(result);
- size_t size = 0;
- uint8_t buf[64];
- do {
- const auto chunk = std::min(available, std::size(buf));
- stream.readBytes(&buf[0], chunk);
- size += chunk;
- } while (size != available);
-
- return size;
- }
|