Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(341)

Side by Side Diff: net/filter/brotli_stream_source.cc

Issue 1662763002: [ON HOLD] Implement pull-based design for content decoding (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address comments Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/filter/brotli_filter.h" 5 #include "net/filter/brotli_stream_source.h"
6 6
7 #include "base/bind.h"
7 #include "base/bit_cast.h" 8 #include "base/bit_cast.h"
9 #include "base/logging.h"
8 #include "base/macros.h" 10 #include "base/macros.h"
11 #include "base/memory/ptr_util.h"
9 #include "base/metrics/histogram_macros.h" 12 #include "base/metrics/histogram_macros.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/numerics/safe_math.h"
12 #include "third_party/brotli/dec/decode.h" 13 #include "third_party/brotli/dec/decode.h"
13 14
14 namespace net { 15 namespace net {
15 16
16 namespace { 17 namespace {
18
19 const char kBrotli[] = "BROTLI";
17 const uint8_t kGzipHeader[] = {0x1f, 0x8b, 0x08}; 20 const uint8_t kGzipHeader[] = {0x1f, 0x8b, 0x08};
18 }
19 21
20 // BrotliFilter applies Brotli content decoding to a data stream. 22 class BrotliStreamSource : public FilterStreamSource {
21 // Brotli format specification: http://www.ietf.org/id/draft-alakuijala-brotli
22 //
23 // BrotliFilter is a subclass of Filter. See the latter's header file filter.h
24 // for sample usage.
25 class BrotliFilter : public Filter {
26 public: 23 public:
27 BrotliFilter(FilterType type) 24 explicit BrotliStreamSource(std::unique_ptr<StreamSource> previous)
28 : Filter(type), 25 : FilterStreamSource(StreamSource::TYPE_BROTLI, std::move(previous)),
29 decoding_status_(DecodingStatus::DECODING_IN_PROGRESS), 26 decoding_status_(DecodingStatus::DECODING_IN_PROGRESS),
30 used_memory_(0), 27 used_memory_(0),
31 used_memory_maximum_(0), 28 used_memory_maximum_(0),
29 produced_bytes_(0),
32 consumed_bytes_(0), 30 consumed_bytes_(0),
33 produced_bytes_(0),
34 gzip_header_detected_(true) { 31 gzip_header_detected_(true) {
35 brotli_state_ = BrotliCreateState(BrotliFilter::AllocateMemory, 32 brotli_state_ = BrotliCreateState(AllocateMemory, FreeMemory, this);
36 BrotliFilter::FreeMemory, this);
37 CHECK(brotli_state_); 33 CHECK(brotli_state_);
38 } 34 }
39 35
40 ~BrotliFilter() override { 36 ~BrotliStreamSource() override {
41 BrotliErrorCode error_code = BrotliGetErrorCode(brotli_state_); 37 BrotliErrorCode error_code = BrotliGetErrorCode(brotli_state_);
42 BrotliDestroyState(brotli_state_); 38 BrotliDestroyState(brotli_state_);
43 brotli_state_ = nullptr; 39 brotli_state_ = nullptr;
44 DCHECK(used_memory_ == 0); 40 DCHECK_EQ(0u, used_memory_);
45 41
46 // Don't report that gzip header was detected in case of lack of input. 42 // Don't report that gzip header was detected in case of lack of input.
47 gzip_header_detected_ &= (consumed_bytes_ >= sizeof(kGzipHeader)); 43 gzip_header_detected_ &= (consumed_bytes_ >= sizeof(kGzipHeader));
48 44
49 UMA_HISTOGRAM_ENUMERATION( 45 UMA_HISTOGRAM_ENUMERATION(
50 "BrotliFilter.Status", static_cast<int>(decoding_status_), 46 "BrotliFilter.Status", static_cast<int>(decoding_status_),
51 static_cast<int>(DecodingStatus::DECODING_STATUS_COUNT)); 47 static_cast<int>(DecodingStatus::DECODING_STATUS_COUNT));
52 UMA_HISTOGRAM_BOOLEAN("BrotliFilter.GzipHeaderDetected", 48 UMA_HISTOGRAM_BOOLEAN("BrotliFilter.GzipHeaderDetected",
53 gzip_header_detected_); 49 gzip_header_detected_);
54 if (decoding_status_ == DecodingStatus::DECODING_DONE) { 50 if (decoding_status_ == DecodingStatus::DECODING_DONE) {
55 // CompressionPercent is undefined when there is no output produced. 51 // CompressionPercent is undefined when there is no output produced.
56 if (produced_bytes_ != 0) { 52 if (produced_bytes_ != 0) {
57 UMA_HISTOGRAM_PERCENTAGE( 53 UMA_HISTOGRAM_PERCENTAGE(
58 "BrotliFilter.CompressionPercent", 54 "BrotliFilter.CompressionPercent",
59 static_cast<int>((consumed_bytes_ * 100) / produced_bytes_)); 55 static_cast<int>((consumed_bytes_ * 100) / produced_bytes_));
60 } 56 }
61 } 57 }
62 if (error_code < 0) { 58 if (error_code < 0) {
63 UMA_HISTOGRAM_ENUMERATION("BrotliFilter.ErrorCode", 59 UMA_HISTOGRAM_ENUMERATION("BrotliFilter.ErrorCode",
64 -static_cast<int>(error_code), 60 -static_cast<int>(error_code),
65 1 - BROTLI_LAST_ERROR_CODE); 61 1 - BROTLI_LAST_ERROR_CODE);
66 } 62 }
67 63
68 // All code here is for gathering stats, and can be removed when 64 // All code here is for gathering stats, and can be removed when
69 // BrotliFilter is considered stable. 65 // BrotliStreamSource is considered stable.
70 static const int kBuckets = 48; 66 const int kBuckets = 48;
71 static const int64_t kMaxKb = 1 << (kBuckets / 3); // 64MiB in KiB 67 const int64_t kMaxKb = 1 << (kBuckets / 3); // 64MiB in KiB
72 UMA_HISTOGRAM_CUSTOM_COUNTS("BrotliFilter.UsedMemoryKB", 68 UMA_HISTOGRAM_CUSTOM_COUNTS("BrotliFilter.UsedMemoryKB",
73 used_memory_maximum_ / 1024, 1, kMaxKb, 69 used_memory_maximum_ / 1024, 1, kMaxKb,
74 kBuckets); 70 kBuckets);
75 } 71 }
76 72
77 // Decodes the pre-filter data and writes the output into the |dest_buffer| 73 private:
78 // passed in. 74 // Reported in UMA and must be kept in sync with the histograms.xml file.
79 // The function returns FilterStatus. See filter.h for its description. 75 enum class DecodingStatus : int {
80 // 76 DECODING_IN_PROGRESS = 0,
81 // Upon entry, |*dest_len| is the total size (in number of chars) of the 77 DECODING_DONE,
82 // destination buffer. Upon exit, |*dest_len| is the actual number of chars 78 DECODING_ERROR,
83 // written into the destination buffer.
84 //
85 // This function will fail if there is no pre-filter data in the
86 // |stream_buffer_|. On the other hand, |*dest_len| can be 0 upon successful
87 // return. For example, decompressor may process some pre-filter data
88 // but not produce output yet.
89 FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len) override {
90 if (!dest_buffer || !dest_len)
91 return Filter::FILTER_ERROR;
92 79
93 if (decoding_status_ == DecodingStatus::DECODING_DONE) { 80 DECODING_STATUS_COUNT
94 *dest_len = 0; 81 // DECODING_STATUS_COUNT must always be the last element in this enum.
95 return Filter::FILTER_DONE; 82 };
96 }
97 83
98 if (decoding_status_ != DecodingStatus::DECODING_IN_PROGRESS) 84 // StreamSource implementation
99 return Filter::FILTER_ERROR; 85 std::string GetTypeAsString() const override { return kBrotli; }
100 86
101 size_t output_buffer_size = base::checked_cast<size_t>(*dest_len); 87 int FilterData(IOBuffer* output_buffer,
102 size_t input_buffer_size = base::checked_cast<size_t>(stream_data_len_); 88 size_t output_buffer_size,
103 89 DrainableIOBuffer* input_buffer) override {
104 size_t available_in = input_buffer_size; 90 const uint8_t* next_in = bit_cast<uint8_t*>(input_buffer->data());
105 const uint8_t* next_in = bit_cast<uint8_t*>(next_stream_data_); 91 size_t available_in = input_buffer->BytesRemaining();
92 uint8_t* next_out = bit_cast<uint8_t*>(output_buffer->data());
106 size_t available_out = output_buffer_size; 93 size_t available_out = output_buffer_size;
107 uint8_t* next_out = bit_cast<uint8_t*>(dest_buffer);
108 size_t total_out = 0; 94 size_t total_out = 0;
109
110 // Check if start of the input stream looks like gzip stream. 95 // Check if start of the input stream looks like gzip stream.
111 for (size_t i = consumed_bytes_; i < sizeof(kGzipHeader); ++i) { 96 for (size_t i = consumed_bytes_; i < sizeof(kGzipHeader); ++i) {
112 if (!gzip_header_detected_) 97 if (!gzip_header_detected_)
113 break; 98 break;
114 size_t j = i - consumed_bytes_; 99 size_t j = i - consumed_bytes_;
115 if (j < available_in && kGzipHeader[i] != next_in[j]) 100 if (j < available_in && kGzipHeader[i] != next_in[j])
116 gzip_header_detected_ = false; 101 gzip_header_detected_ = false;
117 } 102 }
118 103
119 BrotliResult result = 104 BrotliResult result =
120 BrotliDecompressStream(&available_in, &next_in, &available_out, 105 BrotliDecompressStream(&available_in, &next_in, &available_out,
121 &next_out, &total_out, brotli_state_); 106 &next_out, &total_out, brotli_state_);
122 107
123 CHECK(available_in <= input_buffer_size); 108 size_t bytes_used = input_buffer->BytesRemaining() - available_in;
124 CHECK(available_out <= output_buffer_size); 109 size_t bytes_written = output_buffer_size - available_out;
125 consumed_bytes_ += input_buffer_size - available_in; 110 CHECK_GE(bytes_used, 0u);
126 produced_bytes_ += output_buffer_size - available_out; 111 CHECK_GE(bytes_written, 0u);
112 produced_bytes_ += bytes_written;
113 consumed_bytes_ += bytes_used;
127 114
128 base::CheckedNumeric<size_t> safe_bytes_written(output_buffer_size); 115 input_buffer->DidConsume(bytes_used);
129 safe_bytes_written -= available_out;
130 int bytes_written =
131 base::checked_cast<int>(safe_bytes_written.ValueOrDie());
132 116
133 switch (result) { 117 switch (result) {
134 case BROTLI_RESULT_NEEDS_MORE_OUTPUT: 118 case BROTLI_RESULT_NEEDS_MORE_OUTPUT:
135 // Fall through. 119 // Fall through.
136 case BROTLI_RESULT_SUCCESS: 120 case BROTLI_RESULT_SUCCESS:
137 *dest_len = bytes_written; 121 decoding_status_ = DecodingStatus::DECODING_DONE;
138 stream_data_len_ = base::checked_cast<int>(available_in); 122 return bytes_written;
139 next_stream_data_ = bit_cast<char*>(next_in);
140 if (result == BROTLI_RESULT_SUCCESS) {
141 decoding_status_ = DecodingStatus::DECODING_DONE;
142 return Filter::FILTER_DONE;
143 }
144 return Filter::FILTER_OK;
145
146 case BROTLI_RESULT_NEEDS_MORE_INPUT: 123 case BROTLI_RESULT_NEEDS_MORE_INPUT:
147 *dest_len = bytes_written; 124 decoding_status_ = DecodingStatus::DECODING_IN_PROGRESS;
148 stream_data_len_ = 0; 125 break;
149 next_stream_data_ = nullptr; 126 // If the decompressor threw an error, fail synchronously.
150 return Filter::FILTER_NEED_MORE_DATA;
151
152 default: 127 default:
153 decoding_status_ = DecodingStatus::DECODING_ERROR; 128 decoding_status_ = DecodingStatus::DECODING_ERROR;
154 return Filter::FILTER_ERROR; 129 return ERR_CONTENT_DECODING_FAILED;
155 } 130 }
131
132 DCHECK_EQ(BROTLI_RESULT_NEEDS_MORE_INPUT, result);
133 // Decompress needs more input has consumed all existing input.
134 DCHECK_EQ(0, input_buffer->BytesRemaining());
135
136 return bytes_written;
156 } 137 }
157 138
158 private:
159 static void* AllocateMemory(void* opaque, size_t size) { 139 static void* AllocateMemory(void* opaque, size_t size) {
160 BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); 140 BrotliStreamSource* filter = reinterpret_cast<BrotliStreamSource*>(opaque);
161 return filter->AllocateMemoryInternal(size); 141 return filter->AllocateMemoryInternal(size);
162 } 142 }
163 143
164 static void FreeMemory(void* opaque, void* address) { 144 static void FreeMemory(void* opaque, void* address) {
165 BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); 145 BrotliStreamSource* filter = reinterpret_cast<BrotliStreamSource*>(opaque);
166 filter->FreeMemoryInternal(address); 146 filter->FreeMemoryInternal(address);
167 } 147 }
168 148
169 void* AllocateMemoryInternal(size_t size) { 149 void* AllocateMemoryInternal(size_t size) {
170 size_t* array = reinterpret_cast<size_t*>(malloc(size + sizeof(size_t))); 150 size_t* array = reinterpret_cast<size_t*>(malloc(size + sizeof(size_t)));
171 if (!array) 151 if (!array)
172 return nullptr; 152 return nullptr;
173 used_memory_ += size; 153 used_memory_ += size;
174 if (used_memory_maximum_ < used_memory_) 154 if (used_memory_maximum_ < used_memory_)
175 used_memory_maximum_ = used_memory_; 155 used_memory_maximum_ = used_memory_;
176 array[0] = size; 156 array[0] = size;
177 return &array[1]; 157 return &array[1];
178 } 158 }
179 159
180 void FreeMemoryInternal(void* address) { 160 void FreeMemoryInternal(void* address) {
181 if (!address) 161 if (!address)
182 return; 162 return;
183 size_t* array = reinterpret_cast<size_t*>(address); 163 size_t* array = reinterpret_cast<size_t*>(address);
184 used_memory_ -= array[-1]; 164 used_memory_ -= array[-1];
185 free(&array[-1]); 165 free(&array[-1]);
186 } 166 }
187 167
188 // Reported in UMA and must be kept in sync with the histograms.xml file. 168 BrotliState* brotli_state_;
189 enum class DecodingStatus : int {
190 DECODING_IN_PROGRESS = 0,
191 DECODING_DONE,
192 DECODING_ERROR,
193 169
194 DECODING_STATUS_COUNT
195 // DECODING_STATUS_COUNT must always be the last element in this enum.
196 };
197
198 // Tracks the status of decoding.
199 // This variable is updated only by ReadFilteredData.
200 DecodingStatus decoding_status_; 170 DecodingStatus decoding_status_;
201 171
202 BrotliState* brotli_state_;
203
204 size_t used_memory_; 172 size_t used_memory_;
205 size_t used_memory_maximum_; 173 size_t used_memory_maximum_;
174 size_t produced_bytes_;
206 size_t consumed_bytes_; 175 size_t consumed_bytes_;
207 size_t produced_bytes_;
208 176
209 bool gzip_header_detected_; 177 bool gzip_header_detected_;
210 178
211 DISALLOW_COPY_AND_ASSIGN(BrotliFilter); 179 DISALLOW_COPY_AND_ASSIGN(BrotliStreamSource);
212 }; 180 };
213 181
214 Filter* CreateBrotliFilter(Filter::FilterType type_id) { 182 } // namespace
215 return new BrotliFilter(type_id); 183
184 std::unique_ptr<FilterStreamSource> CreateBrotliStreamSource(
185 std::unique_ptr<StreamSource> previous) {
186 return base::WrapUnique(new BrotliStreamSource(std::move(previous)));
216 } 187 }
217 188
218 } // namespace net 189 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698