replace deprecated codecvt with safe utf16BeToUtf8 helper std::wstring_convert and std::codecvt_utf8_utf16 were deprecated in C++17 and removed in C++26, triggering compiler warnings/errors under C++23 with -Werror. Google-Bug-Id: 482144905 Change-Id: I6393490e2a46318d79b4bb94f82cd15549414073 Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/common/test/pldm_utils_test.cpp b/common/test/pldm_utils_test.cpp index 549e289..76d339f 100644 --- a/common/test/pldm_utils_test.cpp +++ b/common/test/pldm_utils_test.cpp
@@ -1012,3 +1012,151 @@ auto results5 = split(s5, "\\"); EXPECT_EQ(results5[0], "aa"); } + +TEST(utf16BeToUtf8, testAsciiConversion) +{ + // "Temp" in UTF-16BE: 0x0054, 0x0065, 0x006D, 0x0070, 0x0000 + const uint8_t u16Data[] = {0x00, 'T', 0x00, 'e', 0x00, + 'm', 0x00, 'p', 0x00, 0x00}; + size_t bytesConsumed = 0; + std::string result = utf16BeToUtf8(u16Data, sizeof(u16Data), bytesConsumed); + EXPECT_EQ(result, "Temp"); + EXPECT_EQ(bytesConsumed, 10); +} + +TEST(utf16BeToUtf8, testMultibyteAndSurrogates) +{ + // U+00E9 ('é' -> UTF-8 0xC3 0xA9) = 0x00E9 + // U+4E2D ('中' -> UTF-8 0xE4 0xB8 0xAD) = 0x4E2D + // U+1F600 (😀 -> UTF-8 0xF0 0x9F 0x98 0x80) = High surrogate 0xD83D, Low + // surrogate 0xDE00 Null terminator = 0x0000 + const uint8_t u16Data[] = { + 0x00, 0xE9, // é + 0x4E, 0x2D, // 中 + 0xD8, 0x3D, 0xDE, 0x00, // 😀 + 0x00, 0x00 // null + }; + size_t bytesConsumed = 0; + std::string result = utf16BeToUtf8(u16Data, sizeof(u16Data), bytesConsumed); + EXPECT_EQ(result, "é中😀"); + EXPECT_EQ(bytesConsumed, sizeof(u16Data)); +} + +TEST(utf16BeToUtf8, testTruncationAndNullPtr) +{ + size_t bytesConsumed = 0; + std::string resultNull = utf16BeToUtf8(nullptr, 10, bytesConsumed); + EXPECT_EQ(resultNull, ""); + EXPECT_EQ(bytesConsumed, 0); + + // Buffer with maxBytes < 2 + const uint8_t u16Data[] = {0x00, 'A', 0x00, 'B', 0x00, 'C'}; + std::string result0 = utf16BeToUtf8(u16Data, 0, bytesConsumed); + EXPECT_EQ(result0, ""); + EXPECT_EQ(bytesConsumed, 0); + + std::string result1 = utf16BeToUtf8(u16Data, 1, bytesConsumed); + EXPECT_EQ(result1, ""); + EXPECT_EQ(bytesConsumed, 0); + + // Odd byte count (3 bytes: consumes 1 UTF-16 code unit = 2 bytes) + std::string result3 = utf16BeToUtf8(u16Data, 3, bytesConsumed); + EXPECT_EQ(result3, "A"); + EXPECT_EQ(bytesConsumed, 2); + + // Buffer without null terminator, truncated at 4 bytes (2 characters) + std::string result4 = utf16BeToUtf8(u16Data, 4, bytesConsumed); + EXPECT_EQ(result4, "AB"); + EXPECT_EQ(bytesConsumed, 4); +} + +TEST(utf16BeToUtf8, testEmptyUtf16String) +{ + // Empty UTF-16 string containing only null terminator: 0x0000 + const uint8_t u16Data[] = {0x00, 0x00}; + size_t bytesConsumed = 0; + std::string result = utf16BeToUtf8(u16Data, sizeof(u16Data), bytesConsumed); + EXPECT_EQ(result, ""); + EXPECT_EQ(bytesConsumed, 2); +} + +TEST(utf16BeToUtf8, testUnpairedSurrogates) +{ + // Unpaired high surrogate at end of buffer + const uint8_t u16High[] = {0xD8, 0x00}; + size_t bytesConsumed = 0; + std::string resultHigh = utf16BeToUtf8(u16High, sizeof(u16High), + bytesConsumed); + EXPECT_EQ(resultHigh, "\xEF\xBF\xBD"); // U+FFFD Replacement Character + EXPECT_EQ(bytesConsumed, 2); + + // Unpaired high surrogate followed by non-low surrogate + const uint8_t u16HighFollowedByAscii[] = {0xD8, 0x00, 0x00, + 'A', 0x00, 0x00}; + bytesConsumed = 0; + std::string resultHighAscii = utf16BeToUtf8( + u16HighFollowedByAscii, sizeof(u16HighFollowedByAscii), bytesConsumed); + EXPECT_EQ(resultHighAscii, "\xEF\xBF\xBD" + "A"); + EXPECT_EQ(bytesConsumed, 6); + + // Unpaired low surrogate + const uint8_t u16Low[] = {0xDC, 0x00, 0x00, 0x00}; + bytesConsumed = 0; + std::string resultLow = utf16BeToUtf8(u16Low, sizeof(u16Low), + bytesConsumed); + EXPECT_EQ(resultLow, "\xEF\xBF\xBD"); + EXPECT_EQ(bytesConsumed, 4); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#include <codecvt> +#include <locale> + +TEST(utf16BeToUtf8, testMatchesLegacyWstringConvert) +{ + auto legacyConvert = [](const std::u16string& u16) { + return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, + char16_t>{} + .to_bytes(u16); + }; + + auto toUtf16BeBytes = [](const std::u16string& str) { + std::vector<uint8_t> bytes; + for (char16_t c : str) + { + bytes.push_back(static_cast<uint8_t>((c >> 8) & 0xFF)); + bytes.push_back(static_cast<uint8_t>(c & 0xFF)); + } + bytes.push_back(0); + bytes.push_back(0); + return bytes; + }; + + const std::vector<std::u16string> testCases = { + u"", + u"A", + u"Hello World", + u"Sensor_0_Volt", + u"Caf\u00E9 \u00C5ngstr\u00F6m", + u"\u041F\u0440\u0438\u0432\u0435\u0442 \u03A9\u03BC\u03AD\u03B3\u03B1", // Cyrillic & Greek + u"\u6E29\u5EA6\u4F20\u611F\u5668", // Chinese + u"\u96FB\u5727\u30BB\u30F3\u30B5\u30FC", // Japanese + u"\U0001F600 \U0001F680 \u26A1", // Emojis (surrogates + BMP) + u"\u0001 \u007F \u0080 \u07FF \u0800 \uD7FF \uE000 \uFFFF", // Boundaries + u"\U00010000 \U00010348 \U0010FFFF", // SMP / boundary surrogates + }; + + for (const auto& u16Str : testCases) + { + std::string expected = legacyConvert(u16Str); + auto wireBytes = toUtf16BeBytes(u16Str); + size_t bytesConsumed = 0; + std::string actual = utf16BeToUtf8(wireBytes.data(), wireBytes.size(), + bytesConsumed); + EXPECT_EQ(actual, expected); + EXPECT_EQ(bytesConsumed, wireBytes.size()); + } +} +#pragma GCC diagnostic pop
diff --git a/common/transport.cpp b/common/transport.cpp index 7a7e4b5..9dff0d3 100644 --- a/common/transport.cpp +++ b/common/transport.cpp
@@ -3,16 +3,15 @@ #include <libpldm/transport.h> #include <libpldm/transport/af-mctp.h> #include <libpldm/transport/mctp-demux.h> - -#include <sys/ioctl.h> #include <linux/mctp.h> -#include <iostream> +#include <sys/ioctl.h> + #include <algorithm> +#include <iostream> #include <ranges> #include <system_error> -struct pldm_transport* transport_impl_init(TransportImpl& impl, - pollfd& pollfd, +struct pldm_transport* transport_impl_init(TransportImpl& impl, pollfd& pollfd, bool isResponder); void transport_impl_destroy(TransportImpl& impl); @@ -69,8 +68,7 @@ } [[maybe_unused]] static struct pldm_transport* - pldm_transport_impl_af_mctp_init(TransportImpl& impl, - pollfd& pollfd, + pldm_transport_impl_af_mctp_init(TransportImpl& impl, pollfd& pollfd, bool isResponder) { impl.af_mctp = nullptr; @@ -128,9 +126,8 @@ return pldmTransport; } -struct pldm_transport* transport_impl_init(TransportImpl& impl, - pollfd& pollfd, - bool isResponder) +struct pldm_transport* transport_impl_init(TransportImpl& impl, pollfd& pollfd, + [[maybe_unused]] bool isResponder) { #if defined(PLDM_TRANSPORT_WITH_MCTP_DEMUX) return pldm_transport_impl_mctp_demux_init(impl, pollfd);
diff --git a/common/utils.cpp b/common/utils.cpp index c693e8b..7826ca9 100644 --- a/common/utils.cpp +++ b/common/utils.cpp
@@ -1006,5 +1006,88 @@ } } +std::string utf16BeToUtf8(const uint8_t* data, size_t maxBytes, + size_t& bytesRead) +{ + std::string out; + bytesRead = 0; + + if (!data) + { + return out; + } + + auto readU16 = [](const uint8_t* p) -> uint16_t { + return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8) | p[1]); + }; + + while (bytesRead + 2 <= maxBytes) + { + uint16_t u16 = readU16(data + bytesRead); + bytesRead += 2; + + if (u16 == 0) + { + break; // Reached UTF-16 null terminator + } + + uint32_t cp = u16; + if (cp >= 0xD800 && cp <= 0xDBFF) // High surrogate + { + if (bytesRead + 2 <= maxBytes) + { + uint16_t low = readU16(data + bytesRead); + if (low >= 0xDC00 && low <= 0xDFFF) + { + bytesRead += 2; + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + } + else + { + cp = 0xFFFD; // Unpaired high surrogate + } + } + else + { + cp = 0xFFFD; // Truncated surrogate pair + } + } + else if (cp >= 0xDC00 && cp <= 0xDFFF) // Unpaired low surrogate + { + cp = 0xFFFD; + } + + // Encode Unicode Scalar Value to UTF-8 + if (cp <= 0x7F) + { + out.push_back(static_cast<char>(cp)); + } + else if (cp <= 0x7FF) + { + out.push_back(static_cast<char>(0xC0 | ((cp >> 6) & 0x1F))); + out.push_back(static_cast<char>(0x80 | (cp & 0x3F))); + } + else if (cp <= 0xFFFF) + { + out.push_back(static_cast<char>(0xE0 | ((cp >> 12) & 0x0F))); + out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F))); + out.push_back(static_cast<char>(0x80 | (cp & 0x3F))); + } + else if (cp <= 0x10FFFF) + { + out.push_back(static_cast<char>(0xF0 | ((cp >> 18) & 0x07))); + out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F))); + out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F))); + out.push_back(static_cast<char>(0x80 | (cp & 0x3F))); + } + else + { + out.append("\xEF\xBF\xBD"); // U+FFFD Replacement Character + } + } + + return out; +} + } // namespace utils } // namespace pldm
diff --git a/common/utils.hpp b/common/utils.hpp index 6345349..8e102c8 100644 --- a/common/utils.hpp +++ b/common/utils.hpp
@@ -530,5 +530,16 @@ void addOEMSelLog(std::string &msg, std::vector<uint8_t> &evtData, uint8_t recordType); +/** @brief Converts a null-terminated or bounded UTF-16BE byte buffer to UTF-8. + * @param[in] data - Pointer to raw UTF-16BE bytes (safe with unaligned + * pointers) + * @param[in] maxBytes - Maximum bytes to inspect from buffer + * @param[out] bytesRead - Total bytes consumed including the null terminator + * (if present) + * @return Decoded UTF-8 string + */ +std::string utf16BeToUtf8(const uint8_t* data, size_t maxBytes, + size_t& bytesRead); + } // namespace utils } // namespace pldm
diff --git a/pldmtool/pldm_platform_cmd.cpp b/pldmtool/pldm_platform_cmd.cpp index f2f3704..8193da6 100644 --- a/pldmtool/pldm_platform_cmd.cpp +++ b/pldmtool/pldm_platform_cmd.cpp
@@ -1017,7 +1017,6 @@ */ void printAuxNamePDR(uint8_t* data, ordered_json& output) { - constexpr uint8_t nullTerminator = 0; struct pldm_effecter_aux_name_pdr* auxNamePdr = (struct pldm_effecter_aux_name_pdr*)data; @@ -1036,6 +1035,7 @@ output[sPrefix + "Id"] = int(auxNamePdr->effecter_id); output[sPrefix + "Count"] = int(auxNamePdr->effecter_count); + constexpr uint8_t nullTerminator = 0; const uint8_t* ptr = auxNamePdr->effecter_names; for (auto i : std::views::iota(0, (int)auxNamePdr->effecter_count)) { @@ -1052,18 +1052,17 @@ std::string nameLanguageTag(reinterpret_cast<const char*>(ptr), 0, PLDM_STR_UTF_8_MAX_LEN); ptr += nameLanguageTag.size() + sizeof(nullTerminator); - std::u16string u16NameString( - reinterpret_cast<const char16_t*>(ptr), 0, - PLDM_STR_UTF_16_MAX_LEN); - ptr += (u16NameString.size() + sizeof(nullTerminator)) * - sizeof(uint16_t); - std::transform(u16NameString.cbegin(), u16NameString.cend(), - u16NameString.begin(), - [](uint16_t utf16) { return be16toh(utf16); }); - std::string nameString = - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, - char16_t>{} - .to_bytes(u16NameString); + size_t u16BytesConsumed = 0; + std::string nameString = utf16BeToUtf8( + ptr, PLDM_STR_UTF_16_MAX_LEN * sizeof(uint16_t), + u16BytesConsumed); + if (u16BytesConsumed == 0 || + u16BytesConsumed > + PLDM_STR_UTF_16_MAX_LEN * sizeof(uint16_t)) + { + break; + } + ptr += u16BytesConsumed; output[nameLanguageTagKey] = nameLanguageTag; output[entityAuxNameKey] = nameString; }
diff --git a/requester/terminus_handler.cpp b/requester/terminus_handler.cpp index 1c4e446..cb3a62d 100644 --- a/requester/terminus_handler.cpp +++ b/requester/terminus_handler.cpp
@@ -1830,18 +1830,17 @@ std::string nameLanguageTag(reinterpret_cast<const char*>(ptr), 0, PLDM_STR_UTF_8_MAX_LEN); ptr += nameLanguageTag.size() + sizeof(nullTerminator); - std::u16string u16NameString( - reinterpret_cast<const char16_t*>(ptr), 0, - PLDM_STR_UTF_16_MAX_LEN); - ptr += (u16NameString.size() + sizeof(nullTerminator)) * - sizeof(uint16_t); - std::transform(u16NameString.cbegin(), u16NameString.cend(), - u16NameString.begin(), - [](uint16_t utf16) { return be16toh(utf16); }); - std::string nameString = - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, - char16_t>{} - .to_bytes(u16NameString); + size_t u16BytesConsumed = 0; + std::string nameString = pldm::utils::utf16BeToUtf8( + ptr, PLDM_STR_UTF_16_MAX_LEN * sizeof(uint16_t), + u16BytesConsumed); + if (u16BytesConsumed == 0 || + u16BytesConsumed > + PLDM_STR_UTF_16_MAX_LEN * sizeof(uint16_t)) + { + break; + } + ptr += u16BytesConsumed; nameLists.emplace_back( std::make_tuple(nameLanguageTag, nameString)); }