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));
             }