xref: /6.0.3/platform/cbcompress/compress.cc (revision c888d660)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2015 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#include "config.h"
19
20#include <platform/cb_malloc.h>
21#include <platform/compress.h>
22#include <snappy.h>
23#include <cctype>
24#include <gsl/gsl>
25#include <stdexcept>
26
27#ifdef CB_LZ4_SUPPORT
28#include <lz4.h>
29#endif
30
31
32static bool doSnappyUncompress(cb::const_char_buffer input,
33                               cb::compression::Buffer& output,
34                               size_t max_inflated_size) {
35    size_t inflated_length;
36
37    if (!snappy::GetUncompressedLength(
38                input.data(), input.size(), &inflated_length)) {
39        return false;
40    }
41
42    if (inflated_length > max_inflated_size) {
43        return false;
44    }
45
46    output.resize(inflated_length);
47    if (!snappy::RawUncompress(input.data(), input.size(), output.data())) {
48        return false;
49    }
50    return true;
51}
52
53static bool doSnappyCompress(cb::const_char_buffer input,
54                             cb::compression::Buffer& output) {
55    size_t compressed_length = snappy::MaxCompressedLength(input.size());
56    output.resize(compressed_length);
57    snappy::RawCompress(
58            input.data(), input.size(), output.data(), &compressed_length);
59    output.resize(compressed_length);
60    return true;
61}
62
63static bool doSnappyValidate(cb::const_char_buffer buffer) {
64    return snappy::IsValidCompressedBuffer(buffer.data(), buffer.size());
65}
66
67static size_t doSnappyUncompressedLength(cb::const_char_buffer buffer) {
68    size_t uncompressed_length = 0;
69    snappy::GetUncompressedLength(
70            buffer.data(), buffer.size(), &uncompressed_length);
71    return uncompressed_length;
72}
73
74static bool doLZ4Uncompress(cb::const_char_buffer input,
75                            cb::compression::Buffer& output,
76                            size_t max_inflated_size) {
77#ifdef CB_LZ4_SUPPORT
78    if (input.size() < 4) {
79        // The length of the compressed data is stored in the first 4 bytes
80        // in network byte order
81        return false;
82    }
83
84    size_t size = ntohl(*reinterpret_cast<const uint32_t*>(input.data()));
85    if (size > max_inflated_size) {
86        return false;
87    }
88
89    output.resize(size);
90    auto nb = LZ4_decompress_safe(input.data() + 4,
91                                  output.data(),
92                                  gsl::narrow_cast<int>(input.size() - 4),
93                                  gsl::narrow_cast<int>(size));
94
95    return nb == gsl::narrow_cast<int>(size);
96
97#else
98    throw std::runtime_error("doLZ4Uncompress: LZ4 not supported");
99#endif
100}
101
102static size_t doLZ4UncompressedLength(cb::const_char_buffer buffer) {
103#ifdef CB_LZ4_SUPPORT
104    return ntohl(*reinterpret_cast<const uint32_t*>(buffer.data()));
105#else
106    throw std::runtime_error("doLZ4UncompressedLength: LZ4 not supported");
107#endif
108}
109
110static bool doLZ4Compress(cb::const_char_buffer input,
111                          cb::compression::Buffer& output) {
112#ifdef CB_LZ4_SUPPORT
113    const auto buffersize =
114            size_t(LZ4_compressBound(gsl::narrow_cast<int>(input.size())));
115    output.resize(buffersize + 4);
116    // The length of the compressed data is stored in the first 4 bytes
117    // in network byte order
118    auto* size_ptr = reinterpret_cast<uint32_t*>(output.data());
119    *size_ptr = htonl(gsl::narrow_cast<uint32_t>(input.size()));
120
121    auto compressed_length =
122            LZ4_compress_default(input.data(),
123                                 output.data() + 4,
124                                 gsl::narrow_cast<int>(input.size()),
125                                 gsl::narrow_cast<int>(buffersize));
126
127    if (compressed_length <= 0) {
128        return false;
129    }
130
131    // Include the length bytes..
132    output.resize(size_t(compressed_length + 4));
133    return true;
134#else
135    throw std::runtime_error("doLZ4Compress: LZ4 not supported");
136#endif
137}
138
139bool cb::compression::inflate(Algorithm algorithm,
140                              cb::const_char_buffer input_buffer,
141                              Buffer& output,
142                              size_t max_inflated_size) {
143    switch (algorithm) {
144    case Algorithm::Snappy:
145        if (!doSnappyUncompress(input_buffer, output, max_inflated_size)) {
146            output.reset();
147            return false;
148        }
149        return true;
150    case Algorithm::LZ4:
151        if (!doLZ4Uncompress(input_buffer, output, max_inflated_size)) {
152            output.reset();
153            return false;
154        }
155        return true;
156    }
157    throw std::invalid_argument(
158        "cb::compression::inflate: Unknown compression algorithm");
159}
160
161bool cb::compression::deflate(Algorithm algorithm,
162                              cb::const_char_buffer input_buffer,
163                              Buffer& output) {
164    switch (algorithm) {
165    case Algorithm::Snappy:
166        if (!doSnappyCompress(input_buffer, output)) {
167            output.reset();
168            return false;
169        }
170        return true;
171    case Algorithm::LZ4:
172        if (!doLZ4Compress(input_buffer, output)) {
173            output.reset();
174            return false;
175        }
176        return true;
177    }
178    throw std::invalid_argument(
179        "cb::compression::deflate: Unknown compression algorithm");
180}
181
182cb::compression::Algorithm cb::compression::to_algorithm(
183        const std::string& string) {
184    std::string input;
185    std::transform(
186            string.begin(), string.end(), std::back_inserter(input), ::toupper);
187
188    if (input == "SNAPPY") {
189        return Algorithm::Snappy;
190    }
191
192    if (input == "LZ4") {
193        return Algorithm::LZ4;
194    }
195
196    throw std::invalid_argument(
197            "cb::compression::to_algorithm: Unknown algorithm: " + string);
198}
199
200std::string to_string(cb::compression::Algorithm algorithm) {
201    switch (algorithm) {
202    case cb::compression::Algorithm::Snappy:
203        return "Snappy";
204    case cb::compression::Algorithm::LZ4:
205        return "LZ4";
206    }
207
208    throw std::invalid_argument(
209            "to_string(cb::compression::Algorithm): Unknown compression "
210            "algorithm");
211}
212
213bool cb::compression::validate(cb::compression::Algorithm algorithm,
214                               cb::const_char_buffer input_buffer,
215                               size_t max_inflated_size) {
216    switch (algorithm) {
217    case Algorithm::Snappy:
218        return doSnappyValidate(input_buffer);
219    case Algorithm::LZ4:
220        cb::compression::Buffer output;
221        return doLZ4Uncompress(input_buffer, output, max_inflated_size);
222    }
223    throw std::invalid_argument(
224        "cb::compression::validate: Unknown compression algorithm");
225}
226
227size_t cb::compression::get_uncompressed_length(
228        cb::compression::Algorithm algorithm,
229        cb::const_char_buffer input_buffer) {
230    switch (algorithm) {
231    case Algorithm::Snappy:
232        return doSnappyUncompressedLength(input_buffer);
233    case Algorithm::LZ4:
234        return doLZ4UncompressedLength(input_buffer);
235    }
236    throw std::invalid_argument(
237            "cb::compression::get_uncompressed_length: Unknown compression "
238            "algorithm");
239}
240