diff --git a/include/Common.h b/include/Common.h index 0b5e07e5..e4c67de0 100644 --- a/include/Common.h +++ b/include/Common.h @@ -10,7 +10,6 @@ TypeName(TypeName&&) = delete; \ void operator=(TypeName&&) = delete - #ifndef OPENSHOCK_API_DOMAIN #error "OPENSHOCK_API_DOMAIN must be defined" #endif @@ -59,6 +58,5 @@ #endif namespace OpenShock::Constants { - const char* const FW_USERAGENT = OPENSHOCK_FW_USERAGENT; - const std::string_view FW_USERAGENT_sv = OPENSHOCK_FW_USERAGENT ""_sv; + const char* const FW_USERAGENT = OPENSHOCK_FW_USERAGENT; } // namespace OpenShock::Constants diff --git a/include/SemVer.h b/include/SemVer.h index f58bb3e1..24805232 100644 --- a/include/SemVer.h +++ b/include/SemVer.h @@ -16,8 +16,8 @@ namespace OpenShock { SemVer(uint16_t major, uint16_t minor, uint16_t patch) : major(major), minor(minor), patch(patch), prerelease(), build() {} - SemVer(uint16_t major, uint16_t minor, uint16_t patch, StringView prerelease, StringView build) - : major(major), minor(minor), patch(patch), prerelease(prerelease.toString()), build(build.toString()) + SemVer(uint16_t major, uint16_t minor, uint16_t patch, std::string_view prerelease, std::string_view build) + : major(major), minor(minor), patch(patch), prerelease(std::string(prerelease)), build(std::string(build)) {} bool operator==(const SemVer& other) const { diff --git a/include/util/StringUtils.h b/include/util/StringUtils.h index 1b2cc8c0..1f6db043 100644 --- a/include/util/StringUtils.h +++ b/include/util/StringUtils.h @@ -1,8 +1,108 @@ #pragma once -#include +#include + #include +#include +#include +#include +#include namespace OpenShock { bool FormatToString(std::string& out, const char* format, ...); -} // namespace OpenShock + + inline std::vector StringSplit(const std::string_view view, char delimiter, std::size_t maxSplits = std::numeric_limits::max()) { + if (view.empty()) { + return {}; + } + + std::vector result = {}; + + std::size_t pos = 0; + std::size_t splits = 0; + while (pos < view.size() && splits < maxSplits) { + std::size_t nextPos = view.find(delimiter, pos); + if (nextPos == std::string_view::npos) { + nextPos = view.size(); + } + + result.push_back(view.substr(pos, nextPos - pos)); + pos = nextPos + 1; + ++splits; + } + + if (pos < view.size()) { + result.push_back(view.substr(pos)); + } + + return result; + } + inline std::vector StringSplit(const std::string_view view, bool (*predicate)(char delimiter), std::size_t maxSplits = std::numeric_limits::max()) { + if (view.empty()) { + return {}; + } + + std::vector result = {}; + + const char* start = nullptr; + for (const char* ptr = view.begin(); ptr < view.end(); ++ptr) { + if (predicate(*ptr)) { + if (start != nullptr) { + result.emplace_back(std::string_view(start, ptr - start)); + start = nullptr; + } + } else if (start == nullptr) { + start = ptr; + } + } + + if (start != nullptr) { + result.emplace_back(std::string_view(start, view.end() - start)); + } + + return result; + } + inline std::vector StringSplitNewLines(const std::string_view view, std::size_t maxSplits = std::numeric_limits::max()) { + return StringSplit( + view, [](char c) { return c == '\r' || c == '\n'; }, maxSplits + ); + } + inline std::vector StringSplitWhiteSpace(const std::string_view view, std::size_t maxSplits = std::numeric_limits::max()) { + return StringSplit( + view, [](char c) { return isspace(c) != 0; }, maxSplits + ); + } + constexpr std::string_view StringTrimLeft(std::string_view view) { + if (view.empty()) { + return view; + } + + std::size_t pos = 0; + while (pos < view.size() && isspace(view[pos])) { + ++pos; + } + + return view.substr(pos); + } + constexpr std::string_view StringTrimRight(std::string_view view) { + if (view.empty()) { + return view; + } + + std::size_t pos = view.size() - 1; + while (pos > 0 && isspace(view[pos])) { + --pos; + } + + return view.substr(0, pos + 1); + } + constexpr std::string_view StringTrim(std::string_view view) { + return StringTrimLeft(StringTrimRight(view)); + } + constexpr bool StringStartsWith(std::string_view view, std::string_view prefix) { + return view.size() >= prefix.size() && view.substr(0, prefix.size()) == prefix; + } + inline String StringToArduinoString(std::string_view view) { + return String(view.data(), view.size()); + } +} // namespace OpenShock diff --git a/src/EStopManager.cpp b/src/EStopManager.cpp index 7eb8130f..f5f48b08 100644 --- a/src/EStopManager.cpp +++ b/src/EStopManager.cpp @@ -11,6 +11,7 @@ const char* const TAG = "EStopManager"; #include "VisualStateManager.h" #include +#include #include using namespace OpenShock; diff --git a/src/OtaUpdateManager.cpp b/src/OtaUpdateManager.cpp index 759fa271..65a26391 100644 --- a/src/OtaUpdateManager.cpp +++ b/src/OtaUpdateManager.cpp @@ -27,6 +27,8 @@ const char* const TAG = "OtaUpdateManager"; #include #include +using namespace std::string_view_literals; + #define OPENSHOCK_FW_CDN_CHANNEL_URL(ch) OPENSHOCK_FW_CDN_URL("/version-" ch ".txt") #define OPENSHOCK_FW_CDN_STABLE_URL OPENSHOCK_FW_CDN_CHANNEL_URL("stable") @@ -148,7 +150,7 @@ bool _flashAppPartition(const esp_partition_t* partition, std::string_view remot if (!OpenShock::FlashPartitionFromUrl(partition, remoteUrl, remoteHash, onProgress)) { ESP_LOGE(TAG, "Failed to flash app partition"); - _sendFailureMessage("Failed to flash app partition"_sv); + _sendFailureMessage("Failed to flash app partition"sv); return false; } @@ -159,7 +161,7 @@ bool _flashAppPartition(const esp_partition_t* partition, std::string_view remot // Set app partition bootable. if (esp_ota_set_boot_partition(partition) != ESP_OK) { ESP_LOGE(TAG, "Failed to set app partition bootable"); - _sendFailureMessage("Failed to set app partition bootable"_sv); + _sendFailureMessage("Failed to set app partition bootable"sv); return false; } @@ -174,7 +176,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view // Make sure captive portal is stopped, timeout after 5 seconds. if (!CaptivePortal::ForceClose(5000U)) { ESP_LOGE(TAG, "Failed to force close captive portal (timed out)"); - _sendFailureMessage("Failed to force close captive portal (timed out)"_sv); + _sendFailureMessage("Failed to force close captive portal (timed out)"sv); return false; } @@ -194,7 +196,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view if (!OpenShock::FlashPartitionFromUrl(parition, remoteUrl, remoteHash, onProgress)) { ESP_LOGE(TAG, "Failed to flash filesystem partition"); - _sendFailureMessage("Failed to flash filesystem partition"_sv); + _sendFailureMessage("Failed to flash filesystem partition"sv); return false; } @@ -206,7 +208,7 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view fs::LittleFSFS test; if (!test.begin(false, "/static", 10, "static0")) { ESP_LOGE(TAG, "Failed to mount filesystem"); - _sendFailureMessage("Failed to mount filesystem"_sv); + _sendFailureMessage("Failed to mount filesystem"sv); return false; } test.end(); @@ -334,7 +336,7 @@ void _otaUpdateTask(void* arg) { OtaUpdateManager::FirmwareRelease release; if (!OtaUpdateManager::TryGetFirmwareRelease(version, release)) { ESP_LOGE(TAG, "Failed to fetch firmware release"); // TODO: Send error message to server - _sendFailureMessage("Failed to fetch firmware release"_sv); + _sendFailureMessage("Failed to fetch firmware release"sv); continue; } @@ -350,7 +352,7 @@ void _otaUpdateTask(void* arg) { const esp_partition_t* appPartition = esp_ota_get_next_update_partition(nullptr); if (appPartition == nullptr) { ESP_LOGE(TAG, "Failed to get app update partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to get app update partition"_sv); + _sendFailureMessage("Failed to get app update partition"sv); continue; } @@ -358,7 +360,7 @@ void _otaUpdateTask(void* arg) { const esp_partition_t* filesystemPartition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "static0"); if (filesystemPartition == nullptr) { ESP_LOGE(TAG, "Failed to find filesystem partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to find filesystem partition"_sv); + _sendFailureMessage("Failed to find filesystem partition"sv); continue; } @@ -373,7 +375,7 @@ void _otaUpdateTask(void* arg) { // Set OTA boot type in config. if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updated)) { ESP_LOGE(TAG, "Failed to set OTA update step"); - _sendFailureMessage("Failed to set OTA update step"_sv); + _sendFailureMessage("Failed to set OTA update step"sv); continue; } @@ -410,17 +412,17 @@ bool _tryGetStringList(std::string_view url, std::vector& list) { std::string_view data = response.data; - auto lines = data.splitLines(); + auto lines = OpenShock::StringSplitNewLines(data); list.reserve(lines.size()); for (auto line : lines) { - line = line.trim(); + line = OpenShock::StringTrim(line); if (line.empty()) { continue; } - list.push_back(line.toString()); + list.push_back(std::string(line)); } return true; @@ -580,21 +582,21 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F return false; } - auto hashesLines = std::string_view(sha256HashesResponse.data).splitLines(); + auto hashesLines = OpenShock::StringSplitNewLines(sha256HashesResponse.data); // Parse hashes. bool foundAppHash = false, foundFilesystemHash = false; for (std::string_view line : hashesLines) { - auto parts = line.splitWhitespace(); + auto parts = OpenShock::StringSplitWhiteSpace(line); if (parts.size() != 2) { ESP_LOGE(TAG, "Invalid hashes entry: %.*s", line.size(), line.data()); return false; } - auto hash = parts[0].trim(); - auto file = parts[1].trim(); + auto hash = OpenShock::StringTrim(parts[0]); + auto file = OpenShock::StringTrim(parts[1]); - if (file.startsWith("./"_sv)) { + if (OpenShock::StringStartsWith(file, "./"sv)) { file = file.substr(2); } diff --git a/src/SemVer.cpp b/src/SemVer.cpp index 162a90b8..048696a4 100644 --- a/src/SemVer.cpp +++ b/src/SemVer.cpp @@ -3,6 +3,7 @@ const char* const TAG = "SemVer"; #include "Logging.h" +#include "util/StringUtils.h" using namespace OpenShock; @@ -145,7 +146,7 @@ bool _semverIsVersionCore(std::string_view str) { return false; } - auto parts = str.split('.'); + auto parts = OpenShock::StringSplit(str, '.'); if (parts.size() != 3) { return false; } @@ -253,7 +254,7 @@ std::string SemVer::toString() const { } bool OpenShock::TryParseSemVer(std::string_view semverStr, SemVer& semver) { - auto parts = semverStr.split('.'); + auto parts = OpenShock::StringSplit(semverStr, '.'); if (parts.size() < 3) { ESP_LOGE(TAG, "Must have at least 3 parts: %.*s", semverStr.length(), semverStr.data()); return false; diff --git a/src/config/WiFiConfig.cpp b/src/config/WiFiConfig.cpp index d069b188..f0f8ec16 100644 --- a/src/config/WiFiConfig.cpp +++ b/src/config/WiFiConfig.cpp @@ -9,7 +9,7 @@ using namespace OpenShock::Config; WiFiConfig::WiFiConfig() : accessPointSSID(OPENSHOCK_FW_AP_PREFIX), hostname(OPENSHOCK_FW_HOSTNAME), credentialsList() { } -WiFiConfig::WiFiConfig(std::string_view accessPointSSID, std::string_view hostname, const std::vector& credentialsList) : accessPointSSID(accessPointSSID.toString()), hostname(hostname.toString()), credentialsList(credentialsList) { } +WiFiConfig::WiFiConfig(std::string_view accessPointSSID, std::string_view hostname, const std::vector& credentialsList) : accessPointSSID(std::string(accessPointSSID)), hostname(std::string(hostname)), credentialsList(credentialsList) { } void WiFiConfig::ToDefault() { accessPointSSID = OPENSHOCK_FW_AP_PREFIX; diff --git a/src/config/WiFiCredentials.cpp b/src/config/WiFiCredentials.cpp index f152fe8d..c6d9f4d1 100644 --- a/src/config/WiFiCredentials.cpp +++ b/src/config/WiFiCredentials.cpp @@ -10,7 +10,7 @@ using namespace OpenShock::Config; WiFiCredentials::WiFiCredentials() : id(0), ssid(), password() { } -WiFiCredentials::WiFiCredentials(uint8_t id, std::string_view ssid, std::string_view password) : id(id), ssid(ssid.toString()), password(password.toString()) { } +WiFiCredentials::WiFiCredentials(uint8_t id, std::string_view ssid, std::string_view password) : id(id), ssid(std::string(ssid)), password(std::string(password)) { } void WiFiCredentials::ToDefault() { id = 0; diff --git a/src/http/HTTPRequestManager.cpp b/src/http/HTTPRequestManager.cpp index c8906d75..2793d349 100644 --- a/src/http/HTTPRequestManager.cpp +++ b/src/http/HTTPRequestManager.cpp @@ -5,15 +5,19 @@ const char* const TAG = "HTTPRequestManager"; #include "Common.h" #include "Logging.h" #include "Time.h" +#include "util/StringUtils.h" #include #include #include #include +#include #include #include +using namespace std::string_view_literals; + const std::size_t HTTP_BUFFER_SIZE = 4096LLU; const int HTTP_DOWNLOAD_SIZE_LIMIT = 200 * 1024 * 1024; // 200 MB @@ -104,20 +108,35 @@ using namespace OpenShock; std::string_view _getDomain(std::string_view url) { if (url.empty()) { - return std::string_view::Null(); + return {}; + } + + // Remove the protocol eg. "https://api.example.com:443/path" -> "api.example.com:443/path" + auto seperator = url.find("://"); + if (seperator != std::string_view::npos) { + url.substr(seperator + 3); } - // Remove the protocol, port, and path eg. "https://api.example.com:443/path" -> "api.example.com" - url = url.afterDelimiter("://"_sv).beforeDelimiter('/').beforeDelimiter(':'); + // Remove the path eg. "api.example.com:443/path" -> "api.example.com:443" + seperator = url.find('/'); + if (seperator != std::string_view::npos) { + url = url.substr(0, seperator); + } + + // Remove the port eg. "api.example.com:443" -> "api.example.com" + seperator = url.rfind(':'); + if (seperator != std::string_view::npos) { + url = url.substr(0, seperator); + } // Remove all subdomains eg. "api.example.com" -> "example.com" - auto domainSep = url.rfind('.'); - if (domainSep == std::string_view::npos) { + seperator = url.rfind('.'); + if (seperator == std::string_view::npos) { return url; // E.g. "localhost" } - domainSep = url.rfind('.', domainSep - 1); - if (domainSep != std::string_view::npos) { - url = url.substr(domainSep + 1); + seperator = url.rfind('.', seperator - 1); + if (seperator != std::string_view::npos) { + url = url.substr(seperator + 1); } return url; @@ -140,7 +159,7 @@ std::shared_ptr _rateLimitFactory(std::string_view domain) { } std::shared_ptr _getRateLimiter(std::string_view url) { - auto domain = _getDomain(url).toString(); + auto domain = std::string(_getDomain(url)); if (domain.empty()) { return nullptr; } @@ -159,7 +178,7 @@ std::shared_ptr _getRateLimiter(std::string_view url) { } void _setupClient(HTTPClient& client) { - client.setUserAgent(OpenShock::Constants::FW_USERAGENT_sv.toArduinoString()); + client.setUserAgent(OpenShock::Constants::FW_USERAGENT); } struct StreamReaderResult { @@ -431,7 +450,7 @@ HTTP::Response _doGetStream( uint32_t timeoutMs ) { int64_t begin = OpenShock::millis(); - if (!client.begin(url.toArduinoString())) { + if (!client.begin(OpenShock::StringToArduinoString(url))) { ESP_LOGE(TAG, "Failed to begin HTTP request"); return {HTTP::RequestResult::RequestFailed, 0}; } diff --git a/src/http/JsonAPI.cpp b/src/http/JsonAPI.cpp index de77dde3..f5d4af18 100644 --- a/src/http/JsonAPI.cpp +++ b/src/http/JsonAPI.cpp @@ -2,6 +2,7 @@ #include "Common.h" #include "config/Config.h" +#include "util/StringUtils.h" using namespace OpenShock; @@ -37,7 +38,7 @@ HTTP::Response HTTP::JsonAPI::GetDev uri, { { "Accept", "application/json"}, - {"DeviceToken", deviceToken.toArduinoString()} + {"DeviceToken", OpenShock::StringToArduinoString(deviceToken)} }, Serialization::JsonAPI::ParseDeviceInfoJsonResponse, {200, 401} @@ -57,7 +58,7 @@ HTTP::Response HTTP::JsonAPI::AssignL uri, { { "Accept", "application/json"}, - {"DeviceToken", deviceToken.toArduinoString()} + {"DeviceToken", OpenShock::StringToArduinoString(deviceToken)} }, Serialization::JsonAPI::ParseAssignLcgJsonResponse, {200, 401} diff --git a/src/intconv.cpp b/src/intconv.cpp index b6856e56..caa64410 100644 --- a/src/intconv.cpp +++ b/src/intconv.cpp @@ -2,8 +2,11 @@ #include #include +#include #include +using namespace std::string_view_literals; + template constexpr unsigned int NumDigits() { static_assert(std::is_integral::value); @@ -136,189 +139,189 @@ static_assert(NumDigits() == 20, "NumDigits test for int64_t failed"); constexpr bool test_spanToUT8() { uint8_t u8 = 0; - return spanToUT("255"_sv, u8) && u8 == 255; + return spanToUT("255"sv, u8) && u8 == 255; } static_assert(test_spanToUT8(), "test_spanToUT8 failed"); constexpr bool test_spanToUT16() { uint16_t u16 = 0; - return spanToUT("65535"_sv, u16) && u16 == 65'535; + return spanToUT("65535"sv, u16) && u16 == 65'535; } static_assert(test_spanToUT16(), "test_spanToUT16 failed"); constexpr bool test_spanToUT32() { uint32_t u32 = 0; - return spanToUT("4294967295"_sv, u32) && u32 == 4'294'967'295U; + return spanToUT("4294967295"sv, u32) && u32 == 4'294'967'295U; } static_assert(test_spanToUT32(), "test_spanToUT32 failed"); constexpr bool test_spanToUT64() { uint64_t u64 = 0; - return spanToUT("18446744073709551615"_sv, u64) && u64 == 18'446'744'073'709'551'615ULL; + return spanToUT("18446744073709551615"sv, u64) && u64 == 18'446'744'073'709'551'615ULL; } static_assert(test_spanToUT64(), "test_spanToUT64 failed"); constexpr bool test_spanToUT8Overflow() { uint8_t u8 = 0; - return !spanToUT("256"_sv, u8); // Overflow + return !spanToUT("256"sv, u8); // Overflow } static_assert(test_spanToUT8Overflow(), "test_spanToUT8Overflow failed"); constexpr bool test_spanToUT16Overflow() { uint16_t u16 = 0; - return !spanToUT("70000"_sv, u16); // Overflow + return !spanToUT("70000"sv, u16); // Overflow } static_assert(test_spanToUT16Overflow(), "test_spanToUT16Overflow failed"); constexpr bool test_spanToUT32Overflow() { uint32_t u32 = 0; - return !spanToUT("4294967296"_sv, u32); // Overflow + return !spanToUT("4294967296"sv, u32); // Overflow } static_assert(test_spanToUT32Overflow(), "test_spanToUT32Overflow failed"); constexpr bool test_spanToUT64Overflow() { uint64_t u64 = 0; - return !spanToUT("18446744073709551616"_sv, u64); // Overflow + return !spanToUT("18446744073709551616"sv, u64); // Overflow } static_assert(test_spanToUT64Overflow(), "test_spanToUT64Overflow failed"); constexpr bool test_spanToST8() { int8_t i8 = 0; - return spanToST("-127"_sv, i8) && i8 == -127; + return spanToST("-127"sv, i8) && i8 == -127; } static_assert(test_spanToST8(), "test_spanToST8 failed"); constexpr bool test_spanToST16() { int16_t i16 = 0; - return spanToST("32767"_sv, i16) && i16 == 32'767; + return spanToST("32767"sv, i16) && i16 == 32'767; } static_assert(test_spanToST16(), "test_spanToST16 failed"); constexpr bool test_spanToST32() { int32_t i32 = 0; - return spanToST("-2147483647"_sv, i32) && i32 == -2'147'483'647; + return spanToST("-2147483647"sv, i32) && i32 == -2'147'483'647; } static_assert(test_spanToST32(), "test_spanToST32 failed"); constexpr bool test_spanToST64() { int64_t i64 = 0; - return spanToST("9223372036854775807"_sv, i64) && i64 == 9'223'372'036'854'775'807LL; + return spanToST("9223372036854775807"sv, i64) && i64 == 9'223'372'036'854'775'807LL; } static_assert(test_spanToST64(), "test_spanToST64 failed"); constexpr bool test_spanToST8Underflow() { int8_t i8 = 0; - return !spanToST("-128"_sv, i8); // Underflow + return !spanToST("-128"sv, i8); // Underflow } static_assert(test_spanToST8Underflow(), "test_spanToST8Underflow failed"); constexpr bool test_spanToST8Overflow() { int8_t i8 = 0; - return !spanToST("128"_sv, i8); // Overflow + return !spanToST("128"sv, i8); // Overflow } static_assert(test_spanToST8Overflow(), "test_spanToST8Overflow failed"); constexpr bool test_spanToST16Underflow() { int16_t i16 = 0; - return !spanToST("-32769"_sv, i16); // Underflow + return !spanToST("-32769"sv, i16); // Underflow } static_assert(test_spanToST16Underflow(), "test_spanToST16Underflow failed"); constexpr bool test_spanToST16Overflow() { int16_t i16 = 0; - return !spanToST("32768"_sv, i16); // Overflow + return !spanToST("32768"sv, i16); // Overflow } static_assert(test_spanToST16Overflow(), "test_spanToST16Overflow failed"); constexpr bool test_spanToST32Underflow() { int32_t i32 = 0; - return !spanToST("-2147483649"_sv, i32); // Underflow + return !spanToST("-2147483649"sv, i32); // Underflow } static_assert(test_spanToST32Underflow(), "test_spanToST32Underflow failed"); constexpr bool test_spanToST32Overflow() { int32_t i32 = 0; - return !spanToST("2147483648"_sv, i32); // Overflow + return !spanToST("2147483648"sv, i32); // Overflow } static_assert(test_spanToST32Overflow(), "test_spanToST32Overflow failed"); constexpr bool test_spanToST64Underflow() { int64_t i64 = 0; - return !spanToST("-9223372036854775809"_sv, i64); // Underflow + return !spanToST("-9223372036854775809"sv, i64); // Underflow } static_assert(test_spanToST64Underflow(), "test_spanToST64Underflow failed"); constexpr bool test_spanToST64Overflow() { int64_t i64 = 0; - return !spanToST("9223372036854775808"_sv, i64); // Overflow + return !spanToST("9223372036854775808"sv, i64); // Overflow } static_assert(test_spanToST64Overflow(), "test_spanToST64Overflow failed"); constexpr bool test_spanToSTEmptyString() { int8_t i8 = 0; - return !spanToST(""_sv, i8); // Empty string + return !spanToST(""sv, i8); // Empty string } static_assert(test_spanToSTEmptyString(), "test_spanToSTEmptyString failed"); constexpr bool test_spanToSTJustNegativeSign() { int16_t i16 = 0; - return !spanToST("-"_sv, i16); // Just a negative sign + return !spanToST("-"sv, i16); // Just a negative sign } static_assert(test_spanToSTJustNegativeSign(), "test_spanToSTJustNegativeSign failed"); constexpr bool test_spanToSTNegativeZero() { int32_t i32 = 0; - return !spanToST("-0"_sv, i32); // Negative zero + return !spanToST("-0"sv, i32); // Negative zero } static_assert(test_spanToSTNegativeZero(), "test_spanToSTNegativeZero failed"); constexpr bool test_spanToSTInvalidCharacter() { int32_t i32 = 0; - return !spanToST("+123"_sv, i32); // Invalid character + return !spanToST("+123"sv, i32); // Invalid character } static_assert(test_spanToSTInvalidCharacter(), "test_spanToSTInvalidCharacter failed"); constexpr bool test_spanToSTLeadingSpace() { int64_t i64 = 0; - return !spanToST(" 123"_sv, i64); // Leading space + return !spanToST(" 123"sv, i64); // Leading space } static_assert(test_spanToSTLeadingSpace(), "test_spanToSTLeadingSpace failed"); constexpr bool test_spanToSTTrailingSpace() { int64_t i64 = 0; - return !spanToST("123 "_sv, i64); // Trailing space + return !spanToST("123 "sv, i64); // Trailing space } static_assert(test_spanToSTTrailingSpace(), "test_spanToSTTrailingSpace failed"); constexpr bool test_spanToSTLeadingZero() { int64_t i64 = 0; - return !spanToST("0123"_sv, i64); // Leading zero + return !spanToST("0123"sv, i64); // Leading zero } static_assert(test_spanToSTLeadingZero(), "test_spanToSTLeadingZero failed"); diff --git a/src/serial/SerialInputHandler.cpp b/src/serial/SerialInputHandler.cpp index 9bbe01a2..22eed9a3 100644 --- a/src/serial/SerialInputHandler.cpp +++ b/src/serial/SerialInputHandler.cpp @@ -14,6 +14,7 @@ const char* const TAG = "SerialInputHandler"; #include "serialization/JsonSerial.h" #include "Time.h" #include "util/Base64Utils.h" +#include "util/StringUtils.h" #include "wifi/WiFiManager.h" #include @@ -23,6 +24,31 @@ const char* const TAG = "SerialInputHandler"; #include #include +namespace std { + struct hash_ci { + std::size_t operator()(std::string_view str) const { + std::size_t hash = 7; + + for (int i = 0; i < str.size(); ++i) { + hash = hash * 31 + tolower(str[i]); + } + + return hash; + } + }; + + template<> + struct less { + bool operator()(std::string_view a, std::string_view b) const { return a < b; } + }; + + struct equals_ci { + bool operator()(std::string_view a, std::string_view b) const { return strncasecmp(a.data(), b.data(), std::max(a.size(), b.size())) == 0; } + }; +} // namespace std + +using namespace std::string_view_literals; + #define SERPR_SYS(format, ...) Serial.printf("$SYS$|" format "\n", ##__VA_ARGS__) #define SERPR_RESPONSE(format, ...) SERPR_SYS("Response|" format, ##__VA_ARGS__) #define SERPR_SUCCESS(format, ...) SERPR_SYS("Success|" format, ##__VA_ARGS__) @@ -30,7 +56,7 @@ const char* const TAG = "SerialInputHandler"; using namespace OpenShock; -const int64_t PASTE_INTERVAL_THRESHOLD_MS = 20; +const int64_t PASTE_INTERVAL_THRESHOLD_MS = 20; const std::size_t SERIAL_BUFFER_CLEAR_THRESHOLD = 512; struct SerialCmdHandler { @@ -52,7 +78,7 @@ bool _tryParseBool(std::string_view str, bool& out) { return false; } - str = str.trim(); + str = OpenShock::StringTrim(str); if (str.length() > 5) { return false; @@ -170,15 +196,7 @@ void _handleDomainCommand(std::string_view arg) { return; } - ESP_LOGI( - TAG, - "Successfully connected to \"%.*s\", version: %s, commit: %s, current time: %s", - arg.length(), - arg.data(), - resp.data.version.c_str(), - resp.data.commit.c_str(), - resp.data.currentTime.c_str() - ); + ESP_LOGI(TAG, "Successfully connected to \"%.*s\", version: %s, commit: %s, current time: %s", arg.length(), arg.data(), resp.data.version.c_str(), resp.data.commit.c_str(), resp.data.currentTime.c_str()); bool result = OpenShock::Config::SetBackendDomain(arg); @@ -228,7 +246,7 @@ void _handleLcgOverrideCommand(std::string_view arg) { return; } - if (arg.startsWith("clear")) { + if (OpenShock::StringStartsWith(arg, "clear"sv)) { if (arg.size() != 5) { SERPR_ERROR("Invalid command (clear command should not have any arguments)"); return; @@ -243,7 +261,7 @@ void _handleLcgOverrideCommand(std::string_view arg) { return; } - if (arg.startsWith("set ")) { + if (OpenShock::StringStartsWith(arg, "set "sv)) { if (arg.size() <= 4) { SERPR_ERROR("Invalid command (set command should have an argument)"); return; @@ -338,8 +356,8 @@ void _handleNetworksCommand(std::string_view arg) { std::vector creds; - uint8_t id = 1; - cJSON* network = nullptr; + uint8_t id = 1; + cJSON* network = nullptr; cJSON_ArrayForEach(network, root) { Config::WiFiCredentials cred; @@ -558,7 +576,7 @@ void _handleRFTransmitCommand(std::string_view arg) { } void _handleHelpCommand(std::string_view arg) { - arg = arg.trim(); + arg = OpenShock::StringTrim(arg); if (arg.empty()) { SerialInputHandler::PrintWelcomeHeader(); @@ -603,7 +621,7 @@ factoryreset reset device to factory defaults and restart } static const SerialCmdHandler kVersionCmdHandler = { - "version"_sv, + "version"sv, R"(version Print version information Example: @@ -612,7 +630,7 @@ static const SerialCmdHandler kVersionCmdHandler = { _handleVersionCommand, }; static const SerialCmdHandler kRestartCmdHandler = { - "restart"_sv, + "restart"sv, R"(restart Restart the board Example: @@ -621,7 +639,7 @@ static const SerialCmdHandler kRestartCmdHandler = { _handleRestartCommand, }; static const SerialCmdHandler kSystemInfoCmdHandler = { - "sysinfo"_sv, + "sysinfo"sv, R"(sysinfo Get system information from RTOS, WiFi, etc. Example: @@ -630,7 +648,7 @@ static const SerialCmdHandler kSystemInfoCmdHandler = { _handleDebugInfoCommand, }; static const SerialCmdHandler kSerialEchoCmdHandler = { - "echo"_sv, + "echo"sv, R"(echo Get the serial echo status. If enabled, typed characters are echoed back to the serial port. @@ -645,7 +663,7 @@ echo [] _handleSerialEchoCommand, }; static const SerialCmdHandler kValidGpiosCmdHandler = { - "validgpios"_sv, + "validgpios"sv, R"(validgpios List all valid GPIO pins Example: @@ -654,7 +672,7 @@ static const SerialCmdHandler kValidGpiosCmdHandler = { _handleValidGpiosCommand, }; static const SerialCmdHandler kRfTxPinCmdHandler = { - "rftxpin"_sv, + "rftxpin"sv, R"(rftxpin Get the GPIO pin used for the radio transmitter. @@ -668,7 +686,7 @@ rftxpin [] _handleRfTxPinCommand, }; static const SerialCmdHandler kDomainCmdHandler = { - "domain"_sv, + "domain"sv, R"(domain Get the backend domain. @@ -682,7 +700,7 @@ domain [] _handleDomainCommand, }; static const SerialCmdHandler kAuthTokenCmdHandler = { - "authtoken"_sv, + "authtoken"sv, R"(authtoken Get the backend auth token. @@ -715,7 +733,7 @@ lcgoverride clear _handleLcgOverrideCommand, }; static const SerialCmdHandler kNetworksCmdHandler = { - "networks"_sv, + "networks"sv, R"(networks Get all saved networks. @@ -732,7 +750,7 @@ networks [] _handleNetworksCommand, }; static const SerialCmdHandler kKeepAliveCmdHandler = { - "keepalive"_sv, + "keepalive"sv, R"(keepalive Get the shocker keep-alive status. @@ -746,7 +764,7 @@ keepalive [] _handleKeepAliveCommand, }; static const SerialCmdHandler kJsonConfigCmdHandler = { - "jsonconfig"_sv, + "jsonconfig"sv, R"(jsonconfig Get the configuration as JSON Example: @@ -762,7 +780,7 @@ jsonconfig _handleJsonConfigCommand, }; static const SerialCmdHandler kRawConfigCmdHandler = { - "rawconfig"_sv, + "rawconfig"sv, R"(rawconfig Get the raw binary config Example: @@ -778,7 +796,7 @@ rawconfig _handleRawConfigCommand, }; static const SerialCmdHandler kRfTransmitCmdHandler = { - "rftransmit"_sv, + "rftransmit"sv, R"(rftransmit Transmit a RF command Arguments: @@ -794,7 +812,7 @@ static const SerialCmdHandler kRfTransmitCmdHandler = { _handleRFTransmitCommand, }; static const SerialCmdHandler kFactoryResetCmdHandler = { - "factoryreset"_sv, + "factoryreset"sv, R"(factoryreset Reset the device to factory defaults and restart Example: @@ -803,7 +821,7 @@ static const SerialCmdHandler kFactoryResetCmdHandler = { _handleFactoryResetCommand, }; static const SerialCmdHandler khelpCmdHandler = { - "help"_sv, + "help"sv, R"(help [] Print help information Arguments: @@ -853,13 +871,13 @@ int findLineStart(const char* buffer, int bufferSize, int lineEnd) { } void processSerialLine(std::string_view line) { - line = line.trim(); + line = OpenShock::StringTrim(line); if (line.empty()) { SERPR_ERROR("No command"); return; } - auto parts = line.split(' ', 1); + auto parts = OpenShock::StringSplit(line, ' ', 1); std::string_view command = parts[0]; std::string_view arguments = parts.size() > 1 ? parts[1] : std::string_view(); @@ -914,7 +932,7 @@ void SerialInputHandler::Update() { static char* buffer = nullptr; // TODO: Clean up this buffer every once in a while static std::size_t bufferSize = 0; static std::size_t bufferIndex = 0; - static int64_t lastEcho = 0; + static int64_t lastEcho = 0; static bool suppressingPaste = false; while (true) { @@ -980,7 +998,7 @@ void SerialInputHandler::Update() { break; } - std::string_view line = std::string_view(buffer, lineEnd).trim(); + std::string_view line = OpenShock::StringTrim(std::string_view(buffer, lineEnd)); Serial.printf("\r> %.*s\n", line.size(), line.data()); diff --git a/src/wifi/WiFiManager.cpp b/src/wifi/WiFiManager.cpp index 1d217a33..ce401b4c 100644 --- a/src/wifi/WiFiManager.cpp +++ b/src/wifi/WiFiManager.cpp @@ -180,7 +180,7 @@ bool _authenticate(const WiFiNetwork& net, std::string_view password) { Serialization::Local::SerializeWiFiNetworkEvent(Serialization::Types::WifiNetworkEventType::Saved, net, CaptivePortal::BroadcastMessageBIN); - return _connect(net.ssid, password.toString()); + return _connect(net.ssid, std::string(password)); } void _evWiFiConnected(arduino_event_t* event) {