/* UTILS MODULE Copyright (C) 2017-2019 by Xose PĂ©rez */ #include "espurna.h" #include "board.h" #include "ntp.h" #include #include bool tryParseId(const char* p, TryParseIdFunc limit, size_t& out) { static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), ""); char* endp { nullptr }; out = strtoul(p, &endp, 10); if ((endp == p) || (*endp != '\0') || (out >= limit())) { return false; } return true; } String getDescription() { return getSetting("desc"); } String getHostname() { if (strlen(HOSTNAME) > 0) { return getSetting("hostname", F(HOSTNAME)); } return getSetting("hostname", getIdentifier()); } void setDefaultHostname() { if (!getSetting("hostname").length()) { if (strlen(HOSTNAME) > 0) { setSetting("hostname", F(HOSTNAME)); } else { setSetting("hostname", getIdentifier()); } } } String getBoardName() { return getSetting("boardName", F(DEVICE_NAME)); } void setBoardName() { if (!isEspurnaCore()) { setSetting("boardName", F(DEVICE_NAME)); } } String getAdminPass() { static const String defaultValue(F(ADMIN_PASS)); return getSetting("adminPass", defaultValue); } const String& getCoreVersion() { static String version; if (!version.length()) { #ifdef ARDUINO_ESP8266_RELEASE version = ESP.getCoreVersion(); if (version.equals("00000000")) { version = String(ARDUINO_ESP8266_RELEASE); } version.replace("_", "."); #else #define _GET_COREVERSION_STR(X) #X #define GET_COREVERSION_STR(X) _GET_COREVERSION_STR(X) version = GET_COREVERSION_STR(ARDUINO_ESP8266_GIT_DESC); #undef _GET_COREVERSION_STR #undef GET_COREVERSION_STR #endif } return version; } const String& getCoreRevision() { static String revision; if (!revision.length()) { #ifdef ARDUINO_ESP8266_GIT_VER revision = String(ARDUINO_ESP8266_GIT_VER, 16); #else revision = "(unspecified)"; #endif } return revision; } const char* getVersion() { static const char version[] = APP_VERSION; return version; } const char* getAppName() { static const char app[] = APP_NAME; return app; } const char* getAppAuthor() { static const char author[] = APP_AUTHOR; return author; } const char* getAppWebsite() { static const char website[] = APP_WEBSITE; return website; } const char* getDevice() { static const char device[] = DEVICE; return device; } const char* getManufacturer() { static const char manufacturer[] = MANUFACTURER; return manufacturer; } String prettyDuration(espurna::duration::Seconds seconds) { time_t timestamp = static_cast(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); } String getUptime() { #if NTP_SUPPORT return prettyDuration(systemUptime()); #else return String(systemUptime().count(), 10); #endif } String buildTime() { #if NTP_SUPPORT constexpr const time_t ts = __UNIX_TIMESTAMP__; tm timestruct; gmtime_r(&ts, ×truct); return ntpDateTime(×truct); #else char buffer[32]; snprintf_P( buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), __TIME_YEAR__, __TIME_MONTH__, __TIME_DAY__, __TIME_HOUR__, __TIME_MINUTE__, __TIME_SECOND__ ); return String(buffer); #endif } // ----------------------------------------------------------------------------- // 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 // ----------------------------------------------------------------------------- // using 'random device' as-is, while most common implementations // would've used it as a seed for some generator func // TODO notice that stdlib std::mt19937 struct needs ~2KiB for it's internal // `result_type state[std::mt19937::state_size]` (ref. sizeof()) uint32_t randomNumber(uint32_t minimum, uint32_t maximum) { using Device = espurna::system::RandomDevice; using Type = Device::result_type; static Device random; auto distribution = std::uniform_int_distribution(minimum, maximum); return distribution(random); } uint32_t randomNumber() { return (espurna::system::RandomDevice{})(); } 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::epsilon() * __builtin_fabs(lhs + rhs) * ulp || __builtin_fabs(lhs - rhs) < std::numeric_limits::min(); } bool almostEqual(double lhs, double rhs) { return almostEqual(lhs, rhs, 3); } bool isNumber(const char* begin, const char* end) { bool dot { false }; bool digit { false }; for (auto ptr = begin; ptr != end; ++ptr) { switch (*ptr) { case '\0': break; case '-': case '+': if (ptr != 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; } bool isNumber(const String& value) { if (value.length()) { return isNumber(value.begin(), value.end()); } return false; } // 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(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(p); } } return nullptr; } namespace { uint32_t parseUnsignedImpl(const String& value, int base) { const char* ptr { value.c_str() }; char* endp { nullptr }; // invalidate the whole string when invalid chars are detected // while this does not return a 'result' type, we can't know // whether 0 was the actual decoded number or not const auto result = strtoul(ptr, &endp, base); if (endp == ptr || endp[0] != '\0') { return 0; } return result; } } // namespace uint32_t parseUnsigned(const String& value, int base) { return parseUnsignedImpl(value, base); } uint32_t parseUnsigned(const String& value) { if (!value.length()) { return 0; } int base = 10; if (value.length() > 2) { auto* ptr = value.c_str(); if (*ptr == '0') { switch (*(ptr + 1)) { case 'b': base = 2; break; case 'o': base = 8; break; case 'x': base = 16; break; } } } return parseUnsignedImpl((base == 10) ? value : value.substring(2), 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 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) { 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) { // We can only return small values (max 'z' aka 122) constexpr uint8_t InvalidByte { 255u }; auto char2byte = [](char ch) -> uint8_t { switch (ch) { case '0'...'9': return (ch - '0'); case 'a'...'f': return 10 + (ch - 'a'); case 'A'...'F': return 10 + (ch - 'A'); } return InvalidByte; }; 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 = char2byte(*in_ptr); if (lhs == InvalidByte) { break; } ++in_ptr; uint8_t rhs = 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; } const char* getFlashChipMode() { static const char* mode { nullptr }; if (!mode) { switch (ESP.getFlashChipMode()) { case FM_QIO: mode = "QIO"; break; case FM_QOUT: mode = "QOUT"; break; case FM_DIO: mode = "DIO"; break; case FM_DOUT: mode = "DOUT"; break; case FM_UNKNOWN: default: mode = "UNKNOWN"; break; } } return mode; }