1 //
2 //
3 // Copyright 2015 gRPC authors.
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
19 #include "src/core/lib/compression/compression_internal.h"
20
21 #include <grpc/compression.h>
22 #include <grpc/support/port_platform.h>
23 #include <stdlib.h>
24
25 #include <string>
26
27 #include "absl/container/inlined_vector.h"
28 #include "absl/log/check.h"
29 #include "absl/strings/ascii.h"
30 #include "absl/strings/str_format.h"
31 #include "absl/strings/str_split.h"
32 #include "src/core/lib/channel/channel_args.h"
33 #include "src/core/lib/debug/trace.h"
34 #include "src/core/util/crash.h"
35 #include "src/core/util/ref_counted_ptr.h"
36 #include "src/core/util/ref_counted_string.h"
37
38 namespace grpc_core {
39
CompressionAlgorithmAsString(grpc_compression_algorithm algorithm)40 const char* CompressionAlgorithmAsString(grpc_compression_algorithm algorithm) {
41 switch (algorithm) {
42 case GRPC_COMPRESS_NONE:
43 return "identity";
44 case GRPC_COMPRESS_DEFLATE:
45 return "deflate";
46 case GRPC_COMPRESS_GZIP:
47 return "gzip";
48 case GRPC_COMPRESS_ALGORITHMS_COUNT:
49 default:
50 return nullptr;
51 }
52 }
53
54 namespace {
55 class CommaSeparatedLists {
56 public:
CommaSeparatedLists()57 CommaSeparatedLists() : lists_{}, text_buffer_{} {
58 char* text_buffer = text_buffer_;
__anon402618970202(char c) 59 auto add_char = [&text_buffer, this](char c) {
60 if (text_buffer - text_buffer_ == kTextBufferSize) abort();
61 *text_buffer++ = c;
62 };
63 for (size_t list = 0; list < kNumLists; ++list) {
64 char* start = text_buffer;
65 for (size_t algorithm = 0; algorithm < GRPC_COMPRESS_ALGORITHMS_COUNT;
66 ++algorithm) {
67 if ((list & (1 << algorithm)) == 0) continue;
68 if (start != text_buffer) {
69 add_char(',');
70 add_char(' ');
71 }
72 const char* name = CompressionAlgorithmAsString(
73 static_cast<grpc_compression_algorithm>(algorithm));
74 for (const char* p = name; *p != '\0'; ++p) {
75 add_char(*p);
76 }
77 }
78 lists_[list] = absl::string_view(start, text_buffer - start);
79 }
80 if (text_buffer - text_buffer_ != kTextBufferSize) abort();
81 }
82
operator [](size_t list) const83 absl::string_view operator[](size_t list) const { return lists_[list]; }
84
85 private:
86 static constexpr size_t kNumLists = 1 << GRPC_COMPRESS_ALGORITHMS_COUNT;
87 // Experimentally determined (tweak things until it runs).
88 static constexpr size_t kTextBufferSize = 86;
89 absl::string_view lists_[kNumLists];
90 char text_buffer_[kTextBufferSize];
91 };
92
93 const CommaSeparatedLists kCommaSeparatedLists;
94 } // namespace
95
ParseCompressionAlgorithm(absl::string_view algorithm)96 absl::optional<grpc_compression_algorithm> ParseCompressionAlgorithm(
97 absl::string_view algorithm) {
98 if (algorithm == "identity") {
99 return GRPC_COMPRESS_NONE;
100 } else if (algorithm == "deflate") {
101 return GRPC_COMPRESS_DEFLATE;
102 } else if (algorithm == "gzip") {
103 return GRPC_COMPRESS_GZIP;
104 } else {
105 return absl::nullopt;
106 }
107 }
108
109 grpc_compression_algorithm
CompressionAlgorithmForLevel(grpc_compression_level level) const110 CompressionAlgorithmSet::CompressionAlgorithmForLevel(
111 grpc_compression_level level) const {
112 if (level > GRPC_COMPRESS_LEVEL_HIGH) {
113 Crash(absl::StrFormat("Unknown message compression level %d.",
114 static_cast<int>(level)));
115 }
116
117 if (level == GRPC_COMPRESS_LEVEL_NONE) {
118 return GRPC_COMPRESS_NONE;
119 }
120
121 CHECK_GT(level, 0);
122
123 // Establish a "ranking" or compression algorithms in increasing order of
124 // compression.
125 // This is simplistic and we will probably want to introduce other dimensions
126 // in the future (cpu/memory cost, etc).
127 absl::InlinedVector<grpc_compression_algorithm,
128 GRPC_COMPRESS_ALGORITHMS_COUNT>
129 algos;
130 for (auto algo : {GRPC_COMPRESS_GZIP, GRPC_COMPRESS_DEFLATE}) {
131 if (set_.is_set(algo)) {
132 algos.push_back(algo);
133 }
134 }
135
136 if (algos.empty()) {
137 return GRPC_COMPRESS_NONE;
138 }
139
140 switch (level) {
141 case GRPC_COMPRESS_LEVEL_NONE:
142 abort(); // should have been handled already
143 case GRPC_COMPRESS_LEVEL_LOW:
144 return algos[0];
145 case GRPC_COMPRESS_LEVEL_MED:
146 return algos[algos.size() / 2];
147 case GRPC_COMPRESS_LEVEL_HIGH:
148 return algos.back();
149 default:
150 abort();
151 };
152 }
153
FromUint32(uint32_t value)154 CompressionAlgorithmSet CompressionAlgorithmSet::FromUint32(uint32_t value) {
155 CompressionAlgorithmSet set;
156 for (size_t i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
157 if (value & (1u << i)) {
158 set.set_.set(i);
159 }
160 }
161 return set;
162 }
163
FromChannelArgs(const ChannelArgs & args)164 CompressionAlgorithmSet CompressionAlgorithmSet::FromChannelArgs(
165 const ChannelArgs& args) {
166 CompressionAlgorithmSet set;
167 static const uint32_t kEverything =
168 (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1;
169 return CompressionAlgorithmSet::FromUint32(
170 args.GetInt(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET)
171 .value_or(kEverything));
172 }
173
174 CompressionAlgorithmSet::CompressionAlgorithmSet() = default;
175
CompressionAlgorithmSet(std::initializer_list<grpc_compression_algorithm> algorithms)176 CompressionAlgorithmSet::CompressionAlgorithmSet(
177 std::initializer_list<grpc_compression_algorithm> algorithms) {
178 for (auto algorithm : algorithms) {
179 Set(algorithm);
180 }
181 }
182
IsSet(grpc_compression_algorithm algorithm) const183 bool CompressionAlgorithmSet::IsSet(
184 grpc_compression_algorithm algorithm) const {
185 size_t i = static_cast<size_t>(algorithm);
186 if (i < GRPC_COMPRESS_ALGORITHMS_COUNT) {
187 return set_.is_set(i);
188 } else {
189 return false;
190 }
191 }
192
Set(grpc_compression_algorithm algorithm)193 void CompressionAlgorithmSet::Set(grpc_compression_algorithm algorithm) {
194 size_t i = static_cast<size_t>(algorithm);
195 if (i < GRPC_COMPRESS_ALGORITHMS_COUNT) {
196 set_.set(i);
197 }
198 }
199
ToString() const200 absl::string_view CompressionAlgorithmSet::ToString() const {
201 return kCommaSeparatedLists[ToLegacyBitmask()];
202 }
203
ToSlice() const204 Slice CompressionAlgorithmSet::ToSlice() const {
205 return Slice::FromStaticString(ToString());
206 }
207
FromString(absl::string_view str)208 CompressionAlgorithmSet CompressionAlgorithmSet::FromString(
209 absl::string_view str) {
210 CompressionAlgorithmSet set{GRPC_COMPRESS_NONE};
211 for (auto algorithm : absl::StrSplit(str, ',')) {
212 auto parsed =
213 ParseCompressionAlgorithm(absl::StripAsciiWhitespace(algorithm));
214 if (parsed.has_value()) {
215 set.Set(*parsed);
216 }
217 }
218 return set;
219 }
220
ToLegacyBitmask() const221 uint32_t CompressionAlgorithmSet::ToLegacyBitmask() const {
222 return set_.ToInt<uint32_t>();
223 }
224
225 absl::optional<grpc_compression_algorithm>
DefaultCompressionAlgorithmFromChannelArgs(const ChannelArgs & args)226 DefaultCompressionAlgorithmFromChannelArgs(const ChannelArgs& args) {
227 auto* value = args.Get(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM);
228 if (value == nullptr) return absl::nullopt;
229 auto ival = value->GetIfInt();
230 if (ival.has_value()) {
231 return static_cast<grpc_compression_algorithm>(*ival);
232 }
233 auto sval = value->GetIfString();
234 if (sval != nullptr) {
235 return ParseCompressionAlgorithm(sval->as_string_view());
236 }
237 return absl::nullopt;
238 }
239
CompressionOptionsFromChannelArgs(const ChannelArgs & args)240 grpc_compression_options CompressionOptionsFromChannelArgs(
241 const ChannelArgs& args) {
242 // Set compression options.
243 grpc_compression_options compression_options;
244 grpc_compression_options_init(&compression_options);
245 auto default_level = args.GetInt(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL);
246 if (default_level.has_value()) {
247 compression_options.default_level.is_set = true;
248 compression_options.default_level.level = Clamp(
249 static_cast<grpc_compression_level>(*default_level),
250 GRPC_COMPRESS_LEVEL_NONE,
251 static_cast<grpc_compression_level>(GRPC_COMPRESS_LEVEL_COUNT - 1));
252 }
253 auto default_algorithm =
254 args.GetInt(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM);
255 if (default_algorithm.has_value()) {
256 compression_options.default_algorithm.is_set = true;
257 compression_options.default_algorithm.algorithm =
258 Clamp(static_cast<grpc_compression_algorithm>(*default_algorithm),
259 GRPC_COMPRESS_NONE,
260 static_cast<grpc_compression_algorithm>(
261 GRPC_COMPRESS_ALGORITHMS_COUNT - 1));
262 }
263 auto enabled_algorithms_bitset =
264 args.GetInt(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET);
265 if (enabled_algorithms_bitset.has_value()) {
266 compression_options.enabled_algorithms_bitset =
267 *enabled_algorithms_bitset | 1 /* always support no compression */;
268 }
269 return compression_options;
270 }
271
272 } // namespace grpc_core
273