Allow decoding MAC addresses in some FRUs
Some motherboards store their mac address in a zlib compressed chunk at
a known location in the FRU.  Decode that section, and pull the mac
address into the appropriate field.
This requires some refactoring so that the indexing can now have the
indexes passed through the various parse functions.
To use this functionality requires the use of libxml and zlib, which are
added as new dependencies.
Change-Id: Icb5c2e46e2a08ca83b3559892169ee2b3f319b2e
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/src/fru_device/gzip_utils.cpp b/src/fru_device/gzip_utils.cpp
new file mode 100644
index 0000000..430fb91
--- /dev/null
+++ b/src/fru_device/gzip_utils.cpp
@@ -0,0 +1,111 @@
+#include "gzip_utils.hpp"
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include <optional>
+#include <span>
+#include <string>
+#include <vector>
+
+std::optional<std::string> gzipInflate(std::span<uint8_t> compressedBytes)
+{
+    std::string uncompressedBytes;
+    if (compressedBytes.empty())
+    {
+        return std::nullopt;
+    }
+
+    z_stream strm{
+
+    };
+    strm.next_in = (Bytef*)compressedBytes.data();
+    strm.avail_in = static_cast<uInt>(compressedBytes.size());
+    strm.total_out = 0;
+
+    if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
+    {
+        return std::nullopt;
+    }
+
+    while (strm.avail_in > 0)
+    {
+        constexpr size_t chunkSize = 1024;
+        uncompressedBytes.resize(uncompressedBytes.size() + chunkSize);
+        strm.next_out =
+            std::bit_cast<Bytef*>(uncompressedBytes.end() - chunkSize);
+        strm.avail_out = chunkSize;
+
+        // Inflate another chunk.
+        int err = inflate(&strm, Z_SYNC_FLUSH);
+        if (err == Z_STREAM_END)
+        {
+            break;
+        }
+        if (err != Z_OK)
+        {
+            return std::nullopt;
+        }
+    }
+
+    if (inflateEnd(&strm) != Z_OK)
+    {
+        return std::nullopt;
+    }
+    uncompressedBytes.resize(strm.total_out);
+
+    return {uncompressedBytes};
+}
+
+static std::vector<std::string> xpathText(xmlDocPtr doc, const char* xp)
+{
+    std::vector<std::string> val;
+    xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
+    if (ctx == nullptr)
+    {
+        return val;
+    }
+    const unsigned char* xpptr = std::bit_cast<const unsigned char*>(xp);
+    xmlXPathObjectPtr obj = xmlXPathEvalExpression(xpptr, ctx);
+    if (obj != nullptr)
+    {
+        if (obj->type == XPATH_NODESET && obj->nodesetval != nullptr)
+        {
+            xmlNodeSetPtr nodeTab = obj->nodesetval;
+            size_t nodeNr = static_cast<size_t>(nodeTab->nodeNr);
+            std::span<xmlNodePtr> nodes{nodeTab->nodeTab, nodeNr};
+            for (xmlNodePtr node : nodes)
+            {
+                unsigned char* keyword = xmlNodeGetContent(node);
+                val.emplace_back(std::bit_cast<const char*>(keyword));
+                xmlFree(keyword);
+            }
+        }
+    }
+
+    xmlXPathFreeObject(obj);
+    xmlXPathFreeContext(ctx);
+    return val;
+}
+
+std::vector<std::string> getNodeFromXml(std::string_view xml,
+                                        const char* nodeName)
+{
+    std::vector<std::string> node;
+    if (xml.empty())
+    {
+        return node;
+    }
+    xmlDocPtr doc = xmlReadMemory(
+        xml.data(), xml.size(), nullptr, nullptr,
+        XML_PARSE_RECOVER | XML_PARSE_NONET | XML_PARSE_NOWARNING);
+    if (doc == nullptr)
+    {
+        return {};
+    }
+    node = xpathText(doc, nodeName);
+    xmlFreeDoc(doc);
+    return node;
+}