xref: /openbmc/bmcweb/http/zstd_compressor.cpp (revision f485bd44a22c27a2346a69d740764ea98b333bd1)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 
4 #include "zstd_compressor.hpp"
5 
6 #include "logging.hpp"
7 
8 #include <boost/asio/buffer.hpp>
9 
10 #include <cstdint>
11 
12 #ifdef HAVE_ZSTD
13 #include <zstd.h>
14 #endif
15 
16 #include <cstddef>
17 #include <optional>
18 #include <span>
19 
20 namespace bmcweb
21 {
22 
init(size_t sourceSize)23 bool ZstdCompressor::init([[maybe_unused]] size_t sourceSize)
24 {
25 #ifdef HAVE_ZSTD
26     if (cctx != nullptr)
27     {
28         BMCWEB_LOG_ERROR("ZstdCompressor already initialized");
29         return false;
30     }
31     cctx = ZSTD_createCCtx();
32     if (cctx == nullptr)
33     {
34         BMCWEB_LOG_ERROR("Failed to create ZstdCompressor");
35         return false;
36     }
37 
38     // 3 is the default compression level for zstd, but set it explicitly
39     // so we can tune later if needed]
40     size_t ret = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, 3);
41     if (ZSTD_isError(ret) != 0U)
42     {
43         BMCWEB_LOG_ERROR("Failed to set compression level {}:{}", ret,
44                          ZSTD_getErrorName(ret));
45         return false;
46     }
47     ret = ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1);
48     if (ZSTD_isError(ret) != 0U)
49     {
50         BMCWEB_LOG_ERROR("Failed to set checksum flag {}:{}", ret,
51                          ZSTD_getErrorName(ret));
52         return false;
53     }
54     ret = ZSTD_CCtx_setParameter(cctx, ZSTD_c_contentSizeFlag, 1);
55     if (ZSTD_isError(ret) != 0U)
56     {
57         BMCWEB_LOG_ERROR("Failed to set contentsize flag {}:{}", ret,
58                          ZSTD_getErrorName(ret));
59         return false;
60     }
61 
62     ret = ZSTD_CCtx_setPledgedSrcSize(cctx, sourceSize);
63     if (ZSTD_isError(ret) != 0U)
64     {
65         BMCWEB_LOG_ERROR("Failed to set pledged src size {}:{}", ret,
66                          ZSTD_getErrorName(ret));
67         return false;
68     }
69     return true;
70 #else
71     BMCWEB_LOG_CRITICAL("ZstdCompressor not compiled in");
72     return false;
73 #endif
74 }
75 
compress(std::span<const uint8_t> buffIn,bool more)76 std::optional<std::span<const uint8_t>> ZstdCompressor::compress(
77     [[maybe_unused]] std::span<const uint8_t> buffIn,
78     [[maybe_unused]] bool more)
79 {
80 #ifdef HAVE_ZSTD
81     if (cctx == nullptr)
82     {
83         BMCWEB_LOG_ERROR("ZstdCompressor not initialized");
84         return std::nullopt;
85     }
86     compressionBuf.clear();
87     ZSTD_inBuffer input = {buffIn.data(), buffIn.size(), 0};
88 
89     while (true)
90     {
91         constexpr size_t frameSize = 4096;
92         auto buffer = compressionBuf.prepare(frameSize);
93         ZSTD_outBuffer output = {buffer.data(), buffer.size(), 0};
94         ZSTD_EndDirective dir = ZSTD_e_end;
95         if (more)
96         {
97             dir = ZSTD_e_continue;
98         }
99         size_t remaining = ZSTD_compressStream2(cctx, &output, &input, dir);
100         if (ZSTD_isError(remaining) != 0U)
101         {
102             return std::nullopt;
103         }
104         compressionBuf.commit(output.pos);
105         if (more)
106         {
107             if (input.pos == input.size)
108             {
109                 break;
110             }
111         }
112         else
113         {
114             if (remaining == 0)
115             {
116                 break;
117             }
118         }
119     }
120     boost::asio::const_buffer buf = compressionBuf.cdata();
121     return std::span(static_cast<const uint8_t*>(buf.data()), buf.size());
122 #else
123     BMCWEB_LOG_CRITICAL("Attempt to compress, but libzstd not enabled");
124 
125     return std::nullopt;
126 #endif
127 }
128 
~ZstdCompressor()129 ZstdCompressor::~ZstdCompressor()
130 {
131 #ifdef HAVE_ZSTD
132     ZSTD_freeCCtx(cctx);
133 #endif
134 }
135 } // namespace bmcweb
136