diff --git a/src/util/util.cpp b/src/util/util.cpp index 95ee262b0..dc445419a 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -43,6 +43,65 @@ extern "C" char* strptime(const char* s, } #endif +/** + * @brief Trims left-side/leading characters (whitespace by default) from the passed string; this modifies the string, in place. + * Locates the first characters which are not within the set of ws, and removes all characters from the start upto that first + * non-matching character. + * + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return a reference to the input string. + */ +std::string& utils::ltrim(std::string& s, const char* t) { s.erase(0, s.find_first_not_of(t)); return s; }; + +/** + * @brief Trims right-side/trailing characters (whitespace by default) from the passed string; this modifies the string, in place. + * Locates the last characters of the string which are not within the set of ws, and removes all characters from that position + * until the end of the string non-matching character. + * + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return a reference to the input string. + */ +std::string& utils::rtrim(std::string& s, const char* t) { s.erase(s.find_last_not_of(t) + 1); return s; }; + +/** + * @brief Trims left-size/leading and right-side/trailing characters (whitespace by default) from the passed string; this modifies + * the string, in place. Call both rtrim() and ltrim() in a single call. + * + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return a reference to the input string. + */ +std::string& utils::trim(std::string& s, const char* t) { return ltrim(rtrim(s, t), t); }; + + +/** + * ltrim() equivalent which makes a new copy, and does not modify the original + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return the modified/trimmed copy of the original input string. + */ +std::string utils::ltrim_copy(std::string s, const char* t) { return ltrim(s, t); }; + +/** + * rtrim() equivalent which makes a new copy, and does not modify the original + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return the modified/trimmed copy of the original input string. + */ +std::string utils::rtrim_copy(std::string s, const char* t) { return rtrim(s, t); }; + +/** + * trim() equivalent which makes a new copy, and does not modify the original + * @param s the string to be trimmed + * @param t a set of characters, which will be removed if they exists + * @return the modified/trimmed copy of the original input string. + */ +std::string utils::trim_copy(std::string s, const char* t) { return trim(s, t); }; + + + /** * Splits the passed string into a vector of strings, delimited by delimiter. * @param str the string to be split @@ -156,24 +215,7 @@ std::string utils::did_hexdump(const char *raw_data, const p_data_hdr_t& hdr, in return std::string(buf); } -/** - * A convenience function to format and return a string representation - * of (in a very specific format, that matching devInfoFromString()) - * the contents of dev_info_t devInfo. Note that this does not format/ - * print all dev_info_t fields, but it does handle the majority of them. - * - * SN[serialNo]: [hdwType]-[hdwVer], fw[fwVersion] b[buildNum][buildType] [buildDate] [buildTime] (addlInfo) - * SN102934: IMX-5.0, fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp) - * - * @param devInfo the dev_info_t struct that provides the values to print - * @return a std::string of the resulting devInfo; - */ -std::string utils::devInfoToString(const dev_info_t& devInfo) { - std::string out; - - // device serial no - out += utils::string_format("SN%06d: ", devInfo.serialNumber); - +std::string utils::getHardwareAsString(const dev_info_t& devInfo) { // hardware type & version const char *typeName = "\?\?\?"; switch (devInfo.hardwareType) { @@ -182,190 +224,236 @@ std::string utils::devInfoToString(const dev_info_t& devInfo) { case IS_HARDWARE_TYPE_GPX: typeName = "GPX"; break; default: typeName = "\?\?\?"; break; } - out += utils::string_format("%s-%u.%u", typeName, devInfo.hardwareVer[0], devInfo.hardwareVer[1]); + std::string out = utils::string_format("%s-%u.%u", typeName, devInfo.hardwareVer[0], devInfo.hardwareVer[1]); if ((devInfo.hardwareVer[2] != 0) || (devInfo.hardwareVer[3] != 0)) { out += utils::string_format(".%u", devInfo.hardwareVer[2]); if (devInfo.hardwareVer[3] != 0) out += utils::string_format(".%u", devInfo.hardwareVer[3]); } + return out; +} - // firmware version - out += utils::string_format(" fw%u.%u.%u", devInfo.firmwareVer[0], devInfo.firmwareVer[1], devInfo.firmwareVer[2]); +std::string utils::getFirmwareAsString(const dev_info_t& devInfo) { + std::string out = utils::string_format("fw%u.%u.%u", devInfo.firmwareVer[0], devInfo.firmwareVer[1], devInfo.firmwareVer[2]); + switch(devInfo.buildType) { + case 'a': out +="-alpha"; break; + case 'b': out +="-beta"; break; + case 'c': out +="-rc"; break; + case 'd': out +="-devel"; break; + case 's': out +="-snap"; break; + case '*': out +="-snap"; break; + default : out +=""; break; + } if (devInfo.firmwareVer[3] != 0) out += utils::string_format(".%u", devInfo.firmwareVer[3]); - // build number/type - out += utils::string_format(" b%u", devInfo.buildNumber); - if ((devInfo.buildType != 0) && (devInfo.buildType != 'r')) - out += utils::string_format("%c", devInfo.buildType); - - // build date/time - out += utils::string_format(" %04u-%02u-%02u", devInfo.buildYear + 2000, devInfo.buildMonth, devInfo.buildDay); - out += utils::string_format(" %02u:%02u:%02u", devInfo.buildHour, devInfo.buildMinute, devInfo.buildSecond); - if (devInfo.buildMillisecond) - out += utils::string_format(".%03u", devInfo.buildMillisecond); - - // additional info - out += utils::string_format(" (%s)", devInfo.addInfo); return out; } -/** - * A convenience function to parse and populate a dev_info_t struct - * from an input string of a specific format (the format matching - * devInfoToString()). Note that this does not parse all dev_info_t - * fields, but it does handle the majority of them. This function - * used a regex pattern to match/parse the string contents. - * @param str the string to parse - * @param devInfo the dev_info_t struct to parse into - * @return true if the string was successfully parsed - */ -bool utils::devInfoFromString(const std::string& str, dev_info_t& devInfo) { - // first, see what we can derive from the filename - // REGEX Patterns: - // SN[serialNo]: [hdwType]-[hdwVer], fw[fwVersion] b[buildNum][buildType] [buildDate] [buildTime] (addlInfo) - // SN102934: IMX-5.0, fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp) - std::regex fwPattern("SN(\\d+): ([\\w]+)-([\\d\\.]+), fw([\\d.]+) b([\\d.]+)([a-z]{0,1}) ([\\d-]+ [\\d:]{8}) \\([^\\)]+\\)$"); - std::smatch match; - - if (!std::regex_match(str, match, fwPattern)) - return false; - - // match components are: (0) serial-number, (1) device-type, (2) hdw-version, (3) image-type, (4) firmware version, (5) build-number, (6) build-type, (7) build-date, (8) build-time, (9) addl info +std::string utils::getBuildAsString(const dev_info_t &devInfo, uint16_t flags) { + std::string out; - // hardware type - std::string hdwType = match[1]; - for (int i = 0; i < IS_HARDWARE_TYPE_COUNT; i++) { - if (hdwType == g_isHardwareTypeNames[i]) { - devInfo.hardwareType = i; - break; + if ((flags & DV_BIT_BUILD_COMMIT) && devInfo.repoRevision) { + out += utils::string_format("%08x", devInfo.repoRevision); + if (devInfo.buildType == '*') { + out += "*"; } } - // hardware version - either 1 or 2 digits, seperated by decimal - parseStringVersion(match[2].str(), devInfo.hardwareVer); - - // image type (we don't really care) - std::string imgType = match[3]; - - // firmware version - typically 3 digits, but could be 4, seperated by decimal - parseStringVersion(match[4].str(), devInfo.firmwareVer); - - // build number - devInfo.buildNumber = std::stol(match[5].str()); + if (flags & DV_BIT_BUILD_KEY) { // include build key/number + // build number/type + if (!out.empty()) out += " "; + if (((devInfo.buildNumber >> 12) & 0xFFFFF) > 0) { + out += utils::string_format("%05x.%d", ((devInfo.buildNumber >> 12) & 0xFFFFF), (devInfo.buildNumber & 0xFFF)); + } else { + if (devInfo.buildNumber & 0xFFF) { + out += utils::string_format("b%d", (devInfo.buildNumber & 0xFFF)); + } + } + } - // build/release type - devInfo.buildType = match[6].str()[0]; + if (flags & DV_BIT_BUILD_DATE) { + out += (out.empty() ? "" : " ") + utils::string_format("%04u-%02u-%02u", devInfo.buildYear + 2000, devInfo.buildMonth, devInfo.buildDay); + } - std::tm tm = {}; - std::string fwBuildDateTime = match[7].str() + " " + match[8].str(); - strptime(fwBuildDateTime.c_str(), "%Y-%m-%d %H:%M:%S", &tm); - devInfo.buildYear = tm.tm_year; - devInfo.buildMonth = tm.tm_mon + 1; - devInfo.buildDay = tm.tm_mday; - devInfo.buildHour = tm.tm_hour; - devInfo.buildMinute = tm.tm_min; - devInfo.buildSecond = tm.tm_sec; - devInfo.buildMillisecond = 0; + if (flags & DV_BIT_BUILD_TIME) { + out += (out.empty() ? "" : " ") + utils::string_format("%02u:%02u:%02u", devInfo.buildHour, devInfo.buildMinute, devInfo.buildSecond); + if (devInfo.buildMillisecond) + out += utils::string_format(".%03u", devInfo.buildMillisecond); + } - return true; + return out; } /** - * simple utility to parse a version string of x.x.x.x (upto 4) and return descrete - * values into an array of 4 bytes. If the string contains fewer than 4 numbers, - * the remaining values are not assigned (you should initialize vOut before calling - * this function). - * @param vIn the string "1.2.3.4" to parse - * @param vOut an array of 4 bytes, each bytes containing the parsed version number. - * @return returns the number of elements parsed. + * A convenience function to format and return a string representation + * of (in a very specific format, that matching devInfoFromString()) + * the contents of dev_info_t devInfo. Note that this does not format/ + * print all dev_info_t fields, but it does handle the majority of them. + * + * SN[serialNo]: [hdwType]-[hdwVer], fw[fwVersion] b[buildNum][buildType] [buildDate] [buildTime] (addlInfo) + * SN102934: IMX-5.0, fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp) + * + * @param devInfo the dev_info_t struct that provides the values to print + * @return a std::string of the resulting devInfo; */ -int utils::parseStringVersion(const std::string& vIn, uint8_t vOut[4]) { - long unsigned int start = 0, n = 0, end = 0; - while ((n < 3) && ((end = vIn.find_first_of('.', start)) != std::string::npos)){ - vOut[n++] = stoi(vIn.substr(start, end - start)); - start = end+1; - } - vOut[n++] = stoi(vIn.substr(start)); - return n; +std::string utils::devInfoToString(const dev_info_t& devInfo, uint16_t flags) { + std::string out; + + // device serial no + + if (flags & DV_BIT_SERIALNO) + out += utils::string_format("SN%06d:", devInfo.serialNumber); + if (flags & DV_BIT_HARDWARE_INFO) + out += (out.empty() ? "" : " ") + utils::getHardwareAsString(devInfo); + if (flags & DV_BIT_FIRMWARE_VER) + out += (out.empty() ? "" : " ") + utils::getFirmwareAsString(devInfo); + if (flags & (DV_BIT_BUILD_DATE | DV_BIT_BUILD_TIME | DV_BIT_BUILD_KEY | DV_BIT_BUILD_COMMIT)) + out += (out.empty() ? "" : " ") + utils::getBuildAsString(devInfo, flags); + if ((flags & DV_BIT_ADDITIONAL_INFO) && devInfo.addInfo[0]) + out += (out.empty() ? "" : " ") + utils::string_format("(%s)", devInfo.addInfo); + + return out; } /** - * Attempts to populate a dev_info_t struct with the firmware version and build info parsed - * from either the filename or the file contents. - * @param imageFile - * @param devInfo - * @return true if dev_info_t was populated, otherwise false + * @brief A convenience function to parse and populate a dev_info_t struct from an input string. + * This function works by attempting to parse a series of "components" from the input string, + * removing each successfully parsed component from the string, and then trying again until + * the string is fully consumed, or the string fails to match any component patterns. + * + * @param str the string to parse + * @param devInfo the dev_info_t struct to parse into + * @return a bitmask of which components were parsed from the input string */ -bool utils::fillDevInfoFromFirmwareImage(std::string imgFilename, dev_info_t& devInfo) { +uint16_t utils::devInfoFromString(const std::string& str, dev_info_t& devInfo) { // first, see what we can derive from the filename // REGEX Patterns: - // -- IMX BL Firmware: IS_(\w+)-([\d\.]+)_(\w+)_(v[\d.]+)_(b[\d.]+)_([\d-]+)_([\d]+).*(\.hex) - // -- IMX/GPX App Firmware: IS_(\w+)-([\d\.]+)_(\w+_|)(v[\d.]+)_(b[\d.]+)_([\d-]+)_([\d]+).*(\.[\w]+)$ - // -- Firmware Package: IS-(\w+)_(r[\d.]+)_([\d-]+)_([\d]+).*(\.[\w]+)$ - std::regex fwPattern("IS_(\\w+)-([\\d\\.]+)_(\\w+_|)v([\\d.]+)_b([\\d.]+)([a-z]{0,1})_([\\d-]+)_([\\d]{6}).*(\\.[\\w]+)$"); - std::smatch match; - - if (!std::regex_match(imgFilename, match, fwPattern)) - return false; - - // match components are: (0) device-type, (1) hdw-version, (2) image-type, (3) firmware version, (4) build-number, (5) build-type, (6) build-date, (7) build-time, (8) file-type - - // hardware type - const std::string hdwType = match[1]; - for (int i = 0; i < IS_HARDWARE_TYPE_COUNT; i++) { - if (hdwType == g_isHardwareTypeNames[i]) { - devInfo.hardwareType = i; - break; + // SN[serialNo]: [hdwType]-[hdwVer], fw[fwVersion] b[buildNum][buildType] [buildDate] [buildTime] (addlInfo) + // SN102934: IMX-5.0 fw2.1.7-rc.83 1739c.5 2024-09-18 15:35:43 (p12 cmp) + // SN102934: IMX-5.0 fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp) + // SN23914: GPX-1.0 fw0.1.12 b0b 2023-11-03 9:09:13 (Some Info) + + // These are a list of Regex patterns that match different components of a firmware string; + // these MUST be in order of the most restrictive first, and least restrictive last, otherwise the least restrictive will consume everything. + static std::vector componentPatterns = { + std::regex(R"(SN(\d+)[:]?)"), //!< 0, serial number + std::regex(R"((fw|v)([\d.]+)(-([\w]+)\.([\d]+))?)"), //!< 1, firmware version w/ optional release type + std::regex(R"((\d){4}-(\d){1,2}-(\d){1,2})"), //!< 2, build date + std::regex(R"((\d){1,2}:(\d){2}:(\d){2}|_(\d{6}))"), //!< 3, build time + std::regex(R"((IS_)?([\w]+)-([\d\.]+)[,]?)"), //!< 4, hdw type & version + std::regex(R"(([0-9A-F]{5})(\.(\d+)))"), //!< 5, build key and build number + std::regex(R"(b([\d.]+)([a-z]?))"), //!< 6, legacy build number and type -- note that this MUST be tested before 5, because 5 CAN catch it... + std::regex(R"(\(([^\)]*)\))"), //!< 7, additional info + std::regex(R"(([0-9a-f]{6,8})(\*)?)"), //!< 8, repo hash & build status (dirty) + }; + + int ii = 0; + std::tm tm = {}; + uint16_t componentsParsed = 0; + std::string local_str = str; // make a copy that we can destroy + bool making_progress = true; // we'll keep trying, as long as we keep making progress.. + devInfo = {}; // reinitialize dev_info_t + + while (!local_str.empty() && making_progress) { + making_progress = false; + + for ( int i = 0, nc = componentPatterns.size(); i < nc; i++ ) { + std::smatch match; + if (std::regex_search(local_str, match, componentPatterns[i])) { + making_progress = true; // we've matched and consumed something, let's make note of it. + switch (i) { + case 0: // parse SN + // serial number + devInfo.serialNumber = stoi(match[1].str()); + componentsParsed |= DV_BIT_SERIALNO; + break; + case 1: // firmware version w/ optional release type + // firmware version - typically 3 digits, but could be 4, seperated by decimal + devInfo.buildType = 0; + for (auto& e : devInfo.firmwareVer) e = 0; + split_from_string(match[2].str(), devInfo.firmwareVer); + if (match[3].matched) { + if (match[4].str() == "alpha") devInfo.buildType = 'a'; + else if (match[4].str() == "beta") devInfo.buildType = 'b'; + else if (match[4].str() == "rc") devInfo.buildType = 'c'; + else if (match[4].str() == "devel") devInfo.buildType = 'd'; + else if (match[4].str() == "snap") devInfo.buildType = 's'; + + if (match[5].matched) { + devInfo.firmwareVer[3] = std::stoi(match[5].str()); + } + } + componentsParsed |= DV_BIT_FIRMWARE_VER; + break; + case 2: // build date + strptime(match[0].str().c_str(), "%Y-%m-%d", &tm); + devInfo.buildYear = tm.tm_year - 100; + devInfo.buildMonth = tm.tm_mon + 1; + devInfo.buildDay = tm.tm_mday; + componentsParsed |= DV_BIT_BUILD_DATE; + break; + case 3: // build time + if (match[4].matched) strptime(match[4].str().c_str(), "%H%M%S", &tm); + else strptime(match[0].str().c_str(), "%H:%M:%S", &tm); + devInfo.buildHour = tm.tm_hour; + devInfo.buildMinute = tm.tm_min; + devInfo.buildSecond = tm.tm_sec; + devInfo.buildMillisecond = 0; + componentsParsed |= DV_BIT_BUILD_TIME; + break; + case 4: // parse HDW type & version + // hardware type + for (ii = 0; ii < IS_HARDWARE_TYPE_COUNT; ii++) { + if (match[2].str() == g_isHardwareTypeNames[ii]) { + devInfo.hardwareType = ii; + break; + } + } + // hardware version + for (auto& e : devInfo.hardwareVer) e = 0; + split_from_string(match[3].str(), devInfo.hardwareVer); + componentsParsed |= DV_BIT_HARDWARE_INFO; + break; + case 5: // build key and build number + { + int build_key = std::stoi(match[1].str(), NULL, 16); + int build_num = std::stoi(match[3].str()); + devInfo.buildNumber = ((build_key << 12) & 0xFFFFF000) | (build_num & 0xFFF); + } + componentsParsed |= DV_BIT_BUILD_KEY; + break; + case 6: // legacy build number and type + devInfo.buildNumber = std::stol(match[1].str()); + devInfo.buildType = match[2].str()[0]; + componentsParsed |= DV_BIT_BUILD_KEY; + break; + case 7: // additional info + strncpy(devInfo.addInfo, trim_copy(match[1].str(), "() ").c_str(), DEVINFO_ADDINFO_STRLEN); + componentsParsed |= DV_BIT_ADDITIONAL_INFO; + break; + case 8: // repo hash & build status + devInfo.repoRevision = std::stol(match[1].str(), NULL, 16); + if (match[2].matched && (match[2].str()[0] == '*')) { + devInfo.buildType = '*'; + } + componentsParsed |= DV_BIT_BUILD_COMMIT; + break; + } + + // don't remove the match string from the local_str until AFTER we parse it-- match[]es are an iterator into the searched string. + int pos = match.position(0); + int len = match.length(0); + local_str.erase(pos, len); + utils::trim(local_str); + } } } - // hardware version - either 1 or 2 digits, seperated by decimal - const std::string hdwStr = (match[2]); - parseStringVersion(hdwStr, devInfo.hardwareVer); - - // image type (we don't really care) - const std::string imgType = match[3]; - - // firmware version - typically 3 digits, but could be 4, seperated by decimal - const std::string fwVerStr = match[4]; - parseStringVersion(match[4], devInfo.firmwareVer); - - // build number - const std::string buildNumStr = match[5]; - devInfo.buildNumber = std::stol(buildNumStr); - - // build/release type - const std::string buildTypeStr = match[6]; - if (buildTypeStr[0] == 0) devInfo.buildType = 'r'; - else devInfo.buildType = buildTypeStr[0]; - - const std::string buildDateStr = match[7]; - const std::string buildTimeStr = match[8]; - - std::tm tm = {}; - std::string fwBuildDateTime = buildDateStr + " " + buildTimeStr; - strptime(fwBuildDateTime.c_str(), "%Y-%m-%d %H%M%S", &tm); - devInfo.buildYear = tm.tm_year - 100; // we use 2-digit years starting 2000, not 1900. - devInfo.buildMonth = tm.tm_mon + 1; - devInfo.buildDay = tm.tm_mday; - devInfo.buildHour = tm.tm_hour; - devInfo.buildMinute = tm.tm_min; - devInfo.buildSecond = tm.tm_sec; - devInfo.buildMillisecond = 0; - -/* - int fileType = -1; - const std::string filenameExtStr = match[9]; - if (filenameExtStr == ".hex") fileType = 0; - else if (filenameExtStr == ".bin") fileType = 1; - else if (filenameExtStr == ".fpk") fileType = 2; -*/ - - // second, parse the file, and see if we can find any additional details -- we do this second because A) its expensive, and B) is more reliable, so it will overwrite values parsed from the filename - return true; + return componentsParsed; } + /** * simple check if two dev_info_t structs indicate equivelent/compatible hardware * Used primarily to determine if a firmware is compatible with a target device. diff --git a/src/util/util.h b/src/util/util.h index 4cb879736..f62529658 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -6,14 +6,15 @@ * @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved. */ -#ifndef INERTIALSENSE_SDK__UTIL_H -#define INERTIALSENSE_SDK__UTIL_H +#ifndef IS_SDK__UTIL_H +#define IS_SDK__UTIL_H #include #include #include #include #include +#include #include "ISComm.h" @@ -27,7 +28,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return a reference to the input string. */ - std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; }; + std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v"); /** * @brief Trims right-side/trailing characters (whitespace by default) from the passed string; this modifies the string, in place. @@ -38,7 +39,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return a reference to the input string. */ - std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; }; + std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v"); /** * @brief Trims left-size/leading and right-side/trailing characters (whitespace by default) from the passed string; this modifies @@ -48,7 +49,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return a reference to the input string. */ - std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); }; + std::string& trim(std::string& s, const char* t = " \t\n\r\f\v"); /** @@ -57,7 +58,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return the modified/trimmed copy of the original input string. */ - std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v") { return ltrim(s, t); }; + std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v"); /** * rtrim() equivalent which makes a new copy, and does not modify the original @@ -65,7 +66,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return the modified/trimmed copy of the original input string. */ - std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v") { return rtrim(s, t); }; + std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v"); /** * trim() equivalent which makes a new copy, and does not modify the original @@ -73,7 +74,7 @@ namespace utils { * @param t a set of characters, which will be removed if they exists * @return the modified/trimmed copy of the original input string. */ - std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v") { return trim(s, t); }; + std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v"); /** * Performs sprintf-type formatting, using a std:string for the format string, and outputting a std::string @@ -132,6 +133,37 @@ namespace utils { return s.str(); } + /** + * Parses a string of delimited values (ie, x.x.x.x) and populate the values into a passed + * std::array of type T and size N. If the string contains fewer than N numbers elements, + * the remaining elements are not assigned (you should initialize vOut before calling this + * function). At most N elements will be parsed. The lamba is used to convert the parsed + * substring into the value of type T. + * + * This is primarily used to parse versions and ip address, and the template provides + * default values that support this usage (T = uint8_t, N = 4). If you wish to use it for + * other types, remember to set the template parameters. + * + * @param s the string to split + * @param vOut a std::array each element containing a parsed value. + * @param d the delimiters to use when splitting (if multiple, will be separated on ANY) + * (defaults to '.') + * @param lambda a lambda used to convert the parsed substring to a value of type T (defaults + * to stoi(), returning a decimal + * @return returns the number of elements parsed. + */ + template + int split_from_string(const std::string& s, T vOut[N], const char* d = ".", std::function lambda = [](const std::string& ss) -> T { return stoi(ss); } ) { + long unsigned int start = 0, n = 0, end = 0; + while ( (end = s.find_first_of(d, start)) != std::string::npos ) { + vOut[n++] = lambda(s.substr(start, end - start)); + start = end + 1; + } + vOut[n++] = lambda(s.substr(start)); + return n; + } + + /** * Splits the passed string into a vector of strings, delimited by delimiter. * @param str the string to be split @@ -143,15 +175,30 @@ namespace utils { std::string raw_hexdump(const char* raw_data, int bytesLen, int bytesPerLine); std::string did_hexdump(const char *raw_data, const p_data_hdr_t& hdr, int bytesPerLine); + enum dev_info_fmt_e : uint16_t { + DV_BIT_SERIALNO = 0x001, //!< serial number + DV_BIT_FIRMWARE_VER = 0x002, //!< firmware version w/ optional release type + DV_BIT_HARDWARE_INFO = 0x004, //!< hdw type & version + DV_BIT_BUILD_KEY = 0x008, //!< build key and build number + DV_BIT_BUILD_DATE = 0x010, //!< build date + DV_BIT_BUILD_TIME = 0x020, //!< build time + DV_BIT_BUILD_COMMIT = 0x040, //!< repo hash & build status (dirty) + DV_BIT_ADDITIONAL_INFO = 0x100, //!< additional info + }; + + std::string getHardwareAsString(const dev_info_t& devInfo); + std::string getFirmwareAsString(const dev_info_t& devInfo); + std::string getBuildAsString(const dev_info_t& devInfo, uint16_t flags = -1); + std::string getCurrentTimestamp(); - std::string devInfoToString(const dev_info_t& devInfo); - bool devInfoFromString(const std::string& str, dev_info_t& devInfo); + std::string devInfoToString(const dev_info_t& devInfo, uint16_t flags = -1); + uint16_t devInfoFromString(const std::string& str, dev_info_t& devInfo); int parseStringVersion(const std::string& vIn, uint8_t vOut[4]); uint64_t intDateTimeFromDevInfo(const dev_info_t& a, bool useMillis = false); - bool fillDevInfoFromFirmwareImage(std::string imgFilename, dev_info_t& devInfo); + bool devInfoFromFirmwareImage(std::string imgFilename, dev_info_t& devInfo); bool isDevInfoCompatible(const dev_info_t& a, const dev_info_t& b); bool compareFirmwareVersions(const dev_info_t& a, const dev_info_t& b); }; -#endif //INERTIALSENSE_SDK__UTIL_H +#endif //IS_SDK__UTIL_H diff --git a/tests/test_ISFirmwarePackage.cpp b/tests/test_ISFirmwarePackage.cpp index dda90f104..2b3a3d81b 100644 --- a/tests/test_ISFirmwarePackage.cpp +++ b/tests/test_ISFirmwarePackage.cpp @@ -11,7 +11,7 @@ #include #include "gtest_helpers.h" -#include "test_utils.h" +#include "test_data_utils.h" #include "ISFileManager.h" #include "ISFirmwareUpdater.h" diff --git a/tests/test_ISFirmwareUpdate.cpp b/tests/test_ISFirmwareUpdate.cpp index 58c05d218..39c3194c2 100644 --- a/tests/test_ISFirmwareUpdate.cpp +++ b/tests/test_ISFirmwareUpdate.cpp @@ -5,7 +5,7 @@ #include #include "gtest_helpers.h" -#include +#include #include "../protocol/FirmwareUpdate.h" #include "miniz.h" #include "md5.h" diff --git a/tests/test_data_utils.cpp b/tests/test_data_utils.cpp index 44c3a9f9a..e751c3036 100644 --- a/tests/test_data_utils.cpp +++ b/tests/test_data_utils.cpp @@ -622,7 +622,6 @@ uint32_t GenerateRawLogData(std::list*>& msgs, float logSiz return runningSize; } - bool AddDataToStream(uint8_t *buffer, int bufferSize, int &streamSize, uint8_t *data, int dataSize) { if (streamSize + dataSize < bufferSize) @@ -715,6 +714,33 @@ int GenerateDataStream(uint8_t *buffer, int bufferSize, eTestGenDataOptions opti return streamSize; } +std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines) +{ + std::vector words= {"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat"}; + int numSentences = RAND_RANGE(minSentences, maxSentences); + int numWords = RAND_RANGE(minWords, maxWords); + + std::string sb; + for (int p = 0; p < numLines; p++) + { + for (int s = 0; s < numSentences; s++) + { + for (int w = 0; w < numWords; w++) + { + if (w > 0) { sb.append(" "); } + int word_idx = RAND_RANGE(0, words.size() - 1); + std::string word = words[ word_idx ]; + if (w == 0) { word[0] = toupper(word[0]); } + sb.append(word); + } + sb.append(". "); + } + if (p < numLines-1) sb.append("\n"); + } + return sb; +} + + static int dummyIsbProtocolHandler(p_data_t* data, port_handle_t port) { return 0; } static int dummyGenericProtocolHandler(const unsigned char* msg, int msgSize, port_handle_t port) { return 0; } diff --git a/tests/test_data_utils.h b/tests/test_data_utils.h index dbe10ef8d..7d7682324 100644 --- a/tests/test_data_utils.h +++ b/tests/test_data_utils.h @@ -6,8 +6,8 @@ * @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved. */ -#ifndef IS_SDK_UNIT_TESTS_TEST_UTILS_H -#define IS_SDK_UNIT_TESTS_TEST_UTILS_H +#ifndef IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H +#define IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H #include @@ -16,6 +16,11 @@ #include "ISLogger.h" #include "time_conversion.h" +/** + * A macro to generate a random integer number between two values. + */ +#define RAND_RANGE(MIN, MAX) (MIN + rand() / (RAND_MAX / (MAX - MIN + 1) + 1)) + typedef struct { protocol_type_t ptype; // Data start byte @@ -45,8 +50,17 @@ int GenerateDataStream(uint8_t *buffer, int bufferSize, eTestGenDataOptions opti uint32_t GenerateRawLogData(std::list*>& msgs, float logSizeMB=20, eTestGenDataOptions options=GEN_LOG_OPTIONS_NONE); +/** + * Generates a string of some arbitrary length, containing some number of random sentences/paragraphs of "Lorem Ipsum" text. + * This is primarily used to generate files of random content. + * @param minWords the minimum number of words per sentence to generate + * @param maxWords the maximum number of words per sentence to generate + * @param minSentences the minimum number of sentences per paragraph to generate + * @param maxSentences the maximum number of sentences per paragraph to generate + * @param numLines the actual number of lines (paragraphs?) to generate (this is NOT random). + * @return a std::string containing the generated content + */ +std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines); - - -#endif //IS_SDK_UNIT_TESTS_TEST_UTILS_H +#endif //IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index d6928e3cf..686689093 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -2,6 +2,8 @@ * @file test_utils.cpp * @brief a collection of functions and classes that might be useful when writing/running unit tests * + * Also includes a few unit tests for testing utils::* + * * @author Kyle Mallory on 1/18/24. * @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved. */ @@ -9,30 +11,114 @@ #include #include +#include + #include "test_utils.h" +#include "util.h" + +TEST(test_utils, Format_devInfo) { + dev_info_t devInfo = { }; + + std::string origStr = "SN102934: IMX-5.0 fw2.1.7-devel.128 E753C.83 deadbeaf 2024-09-18 15:35:43 (p12 cmp)"; // with a comma after the hardware info + uint16_t dvBits = utils::devInfoFromString(origStr, devInfo); + + // Test the output of various alternative formatting + std::string devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_SERIALNO); + EXPECT_EQ(devInfoStr, "SN102934:"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_HARDWARE_INFO); + EXPECT_EQ(devInfoStr, "IMX-5.0"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_FIRMWARE_VER); + EXPECT_EQ(devInfoStr, "fw2.1.7-devel.128"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_KEY); + EXPECT_EQ(devInfoStr, "e753c.83"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_COMMIT); + EXPECT_EQ(devInfoStr, "deadbeaf"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_DATE); + EXPECT_EQ(devInfoStr, "2024-09-18"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_TIME); + EXPECT_EQ(devInfoStr, "15:35:43"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_ADDITIONAL_INFO); + EXPECT_EQ(devInfoStr, "(p12 cmp)"); + + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_SERIALNO | utils::DV_BIT_HARDWARE_INFO | utils::DV_BIT_FIRMWARE_VER); + EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-devel.128"); + + devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_DATE | utils::DV_BIT_BUILD_TIME); + EXPECT_EQ(devInfoStr, "2024-09-18 15:35:43"); + + devInfoStr = utils::devInfoToString(devInfo); + EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-devel.128 deadbeaf e753c.83 2024-09-18 15:35:43 (p12 cmp)"); +} + + +TEST(test_utils, FormatAndParse_devInfo) { + dev_info_t devInfo = { }; + + std::string origStr; + uint16_t dvBits = 0; + + // test legacy formats + origStr = "SN102934: IMX-5.0, fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp)"; // with a comma after the hardware info + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl; + std::string devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-rc b83 2024-09-18 15:35:43 (p12 cmp)"); + + origStr = "SN032014: uINS-3.2 fw1.0.9 b12 2022-11-03 00:31:18 (p0)"; // with-out the comma after the hardware info + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN032014: uINS-3.2 fw1.0.9 b12 2022-11-03 00:31:18 (p0)"); + + origStr = "SN23914: GPX-1.0 fw0.1.12 b0 2023-11-03 9:09:13 ()"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN023914: GPX-1.0 fw0.1.12 2023-11-03 09:09:13"); + + origStr = "SN505192: IMX-5.1.0 fw0.1.12.8 b123 2025-07-31 13:38:22"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 63) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN505192: IMX-5.1 fw0.1.12.8 b123 2025-07-31 13:38:22"); + + origStr = "SN98712178: GPX-1.0.4 fw2.18.5.3 b123c 2025-08-22 18:56:03"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 63) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN98712178: GPX-1.0.4 fw2.18.5-rc.3 b123 2025-08-22 18:56:03"); + + + // newer 2.1.0 format + origStr = "SN102934: IMX-5.0 fw2.1.7-rc.83 9d849cba 1739C.5 2024-09-18 15:35:43 (p12 cmp)"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 383) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-rc.83 9d849cba 1739c.5 2024-09-18 15:35:43 (p12 cmp)"); + + origStr = "SN102934: IMX-5.0 fw2.1.7 b83 2024-09-18 15:35:43 (p12 cmp)"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7 b83 2024-09-18 15:35:43 (p12 cmp)"); +} + +TEST(test_utils, parse_devInfo_from_filename) { + dev_info_t devInfo = { }; + + std::string origStr; + uint16_t dvBits = 0; + + // test legacy formats + origStr = "IS_IMX-5_v2.0.6_b80_2024-05-30_145347.hex"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 62) << "Unable to parse '" << origStr << "'" << std::endl; + std::string devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "IMX-5.0 fw2.0.6 b80 2024-05-30 14:53:47"); + -std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines) -{ - std::vector words= {"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat"}; - int numSentences = RAND_RANGE(minSentences, maxSentences); - int numWords = RAND_RANGE(minWords, maxWords); - - std::string sb; - for (int p = 0; p < numLines; p++) - { - for (int s = 0; s < numSentences; s++) - { - for (int w = 0; w < numWords; w++) - { - if (w > 0) { sb.append(" "); } - int word_idx = RAND_RANGE(0, words.size() - 1); - std::string word = words[ word_idx ]; - if (w == 0) { word[0] = toupper(word[0]); } - sb.append(word); - } - sb.append(". "); - } - if (p < numLines-1) sb.append("\n"); - } - return sb; + origStr = "IS_GPX-1_v2.3.0-snap.153+2024-12-17_002837.encrypted.bin"; + EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 54) << "Unable to parse '" << origStr << "'" << std::endl; + devInfoStr = utils::devInfoToString(devInfo, dvBits); + EXPECT_EQ(devInfoStr, "GPX-1.0 fw2.3.0-snap.153 2024-12-17 00:28:37"); } diff --git a/tests/test_utils.h b/tests/test_utils.h index 4c0c788dd..39ca9efd7 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -9,21 +9,4 @@ #ifndef IS_SDK_UNIT_TESTS_TEST_UTILS_H #define IS_SDK_UNIT_TESTS_TEST_UTILS_H -/** - * A macro to generate a random integer number between two values. - */ -#define RAND_RANGE(MIN, MAX) (MIN + rand() / (RAND_MAX / (MAX - MIN + 1) + 1)) - -/** - * Generates a string of some arbitrary length, containing some number of random sentences/paragraphs of "Lorem Ipsum" text. - * This is primarily used to generate files of random content. - * @param minWords the minimum number of words per sentence to generate - * @param maxWords the maximum number of words per sentence to generate - * @param minSentences the minimum number of sentences per paragraph to generate - * @param maxSentences the maximum number of sentences per paragraph to generate - * @param numLines the actual number of lines (paragraphs?) to generate (this is NOT random). - * @return a std::string containing the generated content - */ -std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines); - #endif //IS_SDK_UNIT_TESTS_TEST_UTILS_H