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