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/http_body.hpp b/http/http_body.hpp
index d76ed49..288c1fe 100644
--- a/http/http_body.hpp
+++ b/http/http_body.hpp
@@ -5,6 +5,7 @@
 #include "duplicatable_file_handle.hpp"
 #include "logging.hpp"
 #include "utility.hpp"
+#include "zstd_decompressor.hpp"
 
 #include <fcntl.h>
 
@@ -49,6 +50,13 @@
     Base64,
 };
 
+enum class CompressionType
+{
+    Raw,
+    Gzip,
+    Zstd,
+};
+
 class HttpBody::value_type
 {
     DuplicatableFileHandle fileHandle;
@@ -60,6 +68,19 @@
     explicit value_type(std::string_view s) : strBody(s) {}
     explicit value_type(EncodingType e) : encodingType(e) {}
     EncodingType encodingType = EncodingType::Raw;
+    CompressionType compressionType = CompressionType::Raw;
+    CompressionType clientCompressionType = CompressionType::Raw;
+
+    ~value_type() = default;
+
+    explicit value_type(EncodingType enc, CompressionType comp) :
+        encodingType(enc), compressionType(comp)
+    {}
+
+    value_type(const value_type& other) noexcept = default;
+    value_type& operator=(const value_type& other) noexcept = default;
+    value_type(value_type&& other) noexcept = default;
+    value_type& operator=(value_type&& other) noexcept = default;
 
     const boost::beast::file_posix& file() const
     {
@@ -156,6 +177,8 @@
     std::string buf;
     crow::utility::Base64Encoder encoder;
 
+    std::optional<ZstdDecompressor> zstd;
+
     value_type& body;
     size_t sent = 0;
     // 64KB This number is arbitrary, and selected to try to optimize for larger
@@ -169,7 +192,13 @@
     template <bool IsRequest, class Fields>
     writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
            value_type& bodyIn) : body(bodyIn)
-    {}
+    {
+        if (body.compressionType == CompressionType::Zstd &&
+            body.clientCompressionType != CompressionType::Zstd)
+        {
+            zstd.emplace();
+        }
+    }
 
     static void init(boost::beast::error_code& ec)
     {
@@ -235,6 +264,18 @@
         {
             ret.first = const_buffers_type(chunkView.data(), chunkView.size());
         }
+
+        if (zstd)
+        {
+            std::optional<const_buffers_type> decompressed =
+                zstd->decompress(ret.first);
+            if (!decompressed)
+            {
+                return boost::none;
+            }
+            ret.first = *decompressed;
+        }
+
         return ret;
     }
 };