Implement zstd decompression

Given the size of Redfish schemas these days, it would be nice to be
able to store them on disk in a zstd format.  Unfortunately, not all
clients support zstd at this time.

This commit implements reading of zstd files from disk, as well as
decompressing zstd in the case where the client does not support zstd as
a return type.

Tested:
Implanted an artificial zstd file into the system, and observed correct
decompression both with an allow-encoding header of empty string and
zstd.

Change-Id: I8b631bb943de99002fdd6745340aec010ee591ff
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/http/zstd_decompressor.cpp b/http/zstd_decompressor.cpp
new file mode 100644
index 0000000..aa42392
--- /dev/null
+++ b/http/zstd_decompressor.cpp
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+
+#include "zstd_decompressor.hpp"
+
+#include "logging.hpp"
+
+#ifdef HAVE_ZSTD
+#include <zstd.h>
+#endif
+#include <boost/asio/buffer.hpp>
+
+#include <cstddef>
+#include <optional>
+
+#ifdef HAVE_ZSTD
+ZstdDecompressor::ZstdDecompressor() : dctx(ZSTD_createDStream())
+{
+    ZSTD_initDStream(dctx);
+}
+#else
+ZstdDecompressor::ZstdDecompressor() {};
+#endif
+
+std::optional<boost::asio::const_buffer> ZstdDecompressor::decompress(
+    [[maybe_unused]] boost::asio::const_buffer buffIn)
+{
+#ifdef HAVE_ZSTD
+    compressionBuf.clear();
+    ZSTD_inBuffer input = {buffIn.data(), buffIn.size(), 0};
+
+    // Note, this loop is prone to compression bombs, decompressing chunks that
+    // appear very small, but decompress to be very large, given that they're
+    // highly decompressible. This algorithm assumes that at this time, the
+    // whole file will fit in ram.
+    while (input.pos != input.size)
+    {
+        constexpr size_t frameSize = 4096;
+        auto buffer = compressionBuf.prepare(frameSize);
+        ZSTD_outBuffer output = {buffer.data(), buffer.size(), 0};
+        const size_t ret = ZSTD_decompressStream(dctx, &output, &input);
+        if (ZSTD_isError(ret) != 0)
+        {
+            BMCWEB_LOG_ERROR("Decompression Failed with code {}:{}", ret,
+                             ZSTD_getErrorName(ret));
+            return std::nullopt;
+        }
+        compressionBuf.commit(output.pos);
+    }
+    return compressionBuf.cdata();
+#else
+    BMCWEB_LOG_CRITICAL("Attempt to decompress, but libzstd not enabled");
+
+    return std::nullopt;
+#endif
+}
+
+ZstdDecompressor::~ZstdDecompressor()
+{
+#ifdef HAVE_ZSTD
+    ZSTD_freeDStream(dctx);
+#endif
+}