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