OLD | NEW |
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_source_stream.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" | 13 #include "net/base/io_buffer.h" |
11 #include "base/numerics/safe_math.h" | |
12 #include "third_party/brotli/dec/decode.h" | 14 #include "third_party/brotli/dec/decode.h" |
13 | 15 |
14 namespace net { | 16 namespace net { |
15 | 17 |
16 namespace { | 18 namespace { |
| 19 |
| 20 const char kBrotli[] = "BROTLI"; |
17 const uint8_t kGzipHeader[] = {0x1f, 0x8b, 0x08}; | 21 const uint8_t kGzipHeader[] = {0x1f, 0x8b, 0x08}; |
18 } | |
19 | 22 |
20 // BrotliFilter applies Brotli content decoding to a data stream. | 23 // BrotliSourceStream applies Brotli content decoding to a data stream. |
21 // Brotli format specification: http://www.ietf.org/id/draft-alakuijala-brotli | 24 // Brotli format specification: http://www.ietf.org/id/draft-alakuijala-brotli. |
22 // | 25 class BrotliSourceStream : public FilterSourceStream { |
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: | 26 public: |
27 BrotliFilter(FilterType type) | 27 explicit BrotliSourceStream(std::unique_ptr<SourceStream> upstream) |
28 : Filter(type), | 28 : FilterSourceStream(SourceStream::TYPE_BROTLI, std::move(upstream)), |
29 decoding_status_(DecodingStatus::DECODING_IN_PROGRESS), | 29 decoding_status_(DecodingStatus::DECODING_IN_PROGRESS), |
30 used_memory_(0), | 30 used_memory_(0), |
31 used_memory_maximum_(0), | 31 used_memory_maximum_(0), |
32 consumed_bytes_(0), | 32 consumed_bytes_(0), |
33 produced_bytes_(0), | 33 produced_bytes_(0), |
34 gzip_header_detected_(true) { | 34 gzip_header_detected_(true) { |
35 brotli_state_ = BrotliCreateState(BrotliFilter::AllocateMemory, | 35 brotli_state_ = BrotliCreateState(AllocateMemory, FreeMemory, this); |
36 BrotliFilter::FreeMemory, this); | |
37 CHECK(brotli_state_); | 36 CHECK(brotli_state_); |
38 } | 37 } |
39 | 38 |
40 ~BrotliFilter() override { | 39 ~BrotliSourceStream() override { |
41 BrotliErrorCode error_code = BrotliGetErrorCode(brotli_state_); | 40 BrotliErrorCode error_code = BrotliGetErrorCode(brotli_state_); |
42 BrotliDestroyState(brotli_state_); | 41 BrotliDestroyState(brotli_state_); |
43 brotli_state_ = nullptr; | 42 brotli_state_ = nullptr; |
44 DCHECK(used_memory_ == 0); | 43 DCHECK_EQ(0u, used_memory_); |
45 | 44 |
46 // Don't report that gzip header was detected in case of lack of input. | 45 // Don't report that gzip header was detected in case of lack of input. |
47 gzip_header_detected_ &= (consumed_bytes_ >= sizeof(kGzipHeader)); | 46 gzip_header_detected_ &= (consumed_bytes_ >= sizeof(kGzipHeader)); |
48 | 47 |
49 UMA_HISTOGRAM_ENUMERATION( | 48 UMA_HISTOGRAM_ENUMERATION( |
50 "BrotliFilter.Status", static_cast<int>(decoding_status_), | 49 "BrotliFilter.Status", static_cast<int>(decoding_status_), |
51 static_cast<int>(DecodingStatus::DECODING_STATUS_COUNT)); | 50 static_cast<int>(DecodingStatus::DECODING_STATUS_COUNT)); |
52 UMA_HISTOGRAM_BOOLEAN("BrotliFilter.GzipHeaderDetected", | 51 UMA_HISTOGRAM_BOOLEAN("BrotliFilter.GzipHeaderDetected", |
53 gzip_header_detected_); | 52 gzip_header_detected_); |
54 if (decoding_status_ == DecodingStatus::DECODING_DONE) { | 53 if (decoding_status_ == DecodingStatus::DECODING_DONE) { |
55 // CompressionPercent is undefined when there is no output produced. | 54 // CompressionPercent is undefined when there is no output produced. |
56 if (produced_bytes_ != 0) { | 55 if (produced_bytes_ != 0) { |
57 UMA_HISTOGRAM_PERCENTAGE( | 56 UMA_HISTOGRAM_PERCENTAGE( |
58 "BrotliFilter.CompressionPercent", | 57 "BrotliFilter.CompressionPercent", |
59 static_cast<int>((consumed_bytes_ * 100) / produced_bytes_)); | 58 static_cast<int>((consumed_bytes_ * 100) / produced_bytes_)); |
60 } | 59 } |
61 } | 60 } |
62 if (error_code < 0) { | 61 if (error_code < 0) { |
63 UMA_HISTOGRAM_ENUMERATION("BrotliFilter.ErrorCode", | 62 UMA_HISTOGRAM_ENUMERATION("BrotliFilter.ErrorCode", |
64 -static_cast<int>(error_code), | 63 -static_cast<int>(error_code), |
65 1 - BROTLI_LAST_ERROR_CODE); | 64 1 - BROTLI_LAST_ERROR_CODE); |
66 } | 65 } |
67 | 66 |
68 // All code here is for gathering stats, and can be removed when | 67 // All code here is for gathering stats, and can be removed when |
69 // BrotliFilter is considered stable. | 68 // BrotliSourceStream is considered stable. |
70 static const int kBuckets = 48; | 69 const int kBuckets = 48; |
71 static const int64_t kMaxKb = 1 << (kBuckets / 3); // 64MiB in KiB | 70 const int64_t kMaxKb = 1 << (kBuckets / 3); // 64MiB in KiB |
72 UMA_HISTOGRAM_CUSTOM_COUNTS("BrotliFilter.UsedMemoryKB", | 71 UMA_HISTOGRAM_CUSTOM_COUNTS("BrotliFilter.UsedMemoryKB", |
73 used_memory_maximum_ / 1024, 1, kMaxKb, | 72 used_memory_maximum_ / 1024, 1, kMaxKb, |
74 kBuckets); | 73 kBuckets); |
75 } | 74 } |
76 | 75 |
77 // Decodes the pre-filter data and writes the output into the |dest_buffer| | 76 private: |
78 // passed in. | 77 // 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. | 78 enum class DecodingStatus : int { |
80 // | 79 DECODING_IN_PROGRESS = 0, |
81 // Upon entry, |*dest_len| is the total size (in number of chars) of the | 80 DECODING_DONE, |
82 // destination buffer. Upon exit, |*dest_len| is the actual number of chars | 81 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 | 82 |
93 if (decoding_status_ == DecodingStatus::DECODING_DONE) { | 83 DECODING_STATUS_COUNT |
94 *dest_len = 0; | 84 // DECODING_STATUS_COUNT must always be the last element in this enum. |
95 return Filter::FILTER_DONE; | 85 }; |
96 } | |
97 | 86 |
98 if (decoding_status_ != DecodingStatus::DECODING_IN_PROGRESS) | 87 // SourceStream implementation |
99 return Filter::FILTER_ERROR; | 88 std::string GetTypeAsString() const override { return kBrotli; } |
100 | 89 |
101 size_t output_buffer_size = base::checked_cast<size_t>(*dest_len); | 90 int FilterData(IOBuffer* output_buffer, |
102 size_t input_buffer_size = base::checked_cast<size_t>(stream_data_len_); | 91 int output_buffer_size, |
103 | 92 IOBuffer* input_buffer, |
| 93 int input_buffer_size, |
| 94 int* consumed_bytes, |
| 95 bool /*upstream_eof_reached*/) override { |
| 96 const uint8_t* next_in = bit_cast<uint8_t*>(input_buffer->data()); |
104 size_t available_in = input_buffer_size; | 97 size_t available_in = input_buffer_size; |
105 const uint8_t* next_in = bit_cast<uint8_t*>(next_stream_data_); | 98 uint8_t* next_out = bit_cast<uint8_t*>(output_buffer->data()); |
106 size_t available_out = output_buffer_size; | 99 size_t available_out = output_buffer_size; |
107 uint8_t* next_out = bit_cast<uint8_t*>(dest_buffer); | |
108 size_t total_out = 0; | 100 size_t total_out = 0; |
109 | |
110 // Check if start of the input stream looks like gzip stream. | 101 // Check if start of the input stream looks like gzip stream. |
111 for (size_t i = consumed_bytes_; i < sizeof(kGzipHeader); ++i) { | 102 for (size_t i = consumed_bytes_; i < sizeof(kGzipHeader); ++i) { |
112 if (!gzip_header_detected_) | 103 if (!gzip_header_detected_) |
113 break; | 104 break; |
114 size_t j = i - consumed_bytes_; | 105 size_t j = i - consumed_bytes_; |
115 if (j < available_in && kGzipHeader[i] != next_in[j]) | 106 if (j < available_in && kGzipHeader[i] != next_in[j]) |
116 gzip_header_detected_ = false; | 107 gzip_header_detected_ = false; |
117 } | 108 } |
118 | 109 |
119 BrotliResult result = | 110 BrotliResult result = |
120 BrotliDecompressStream(&available_in, &next_in, &available_out, | 111 BrotliDecompressStream(&available_in, &next_in, &available_out, |
121 &next_out, &total_out, brotli_state_); | 112 &next_out, &total_out, brotli_state_); |
122 | 113 |
123 CHECK(available_in <= input_buffer_size); | 114 size_t bytes_used = input_buffer_size - available_in; |
124 CHECK(available_out <= output_buffer_size); | 115 size_t bytes_written = output_buffer_size - available_out; |
125 consumed_bytes_ += input_buffer_size - available_in; | 116 CHECK_GE(bytes_used, 0u); |
126 produced_bytes_ += output_buffer_size - available_out; | 117 CHECK_GE(bytes_written, 0u); |
| 118 produced_bytes_ += bytes_written; |
| 119 consumed_bytes_ += bytes_used; |
127 | 120 |
128 base::CheckedNumeric<size_t> safe_bytes_written(output_buffer_size); | 121 *consumed_bytes = bytes_used; |
129 safe_bytes_written -= available_out; | |
130 int bytes_written = | |
131 base::checked_cast<int>(safe_bytes_written.ValueOrDie()); | |
132 | 122 |
133 switch (result) { | 123 switch (result) { |
134 case BROTLI_RESULT_NEEDS_MORE_OUTPUT: | 124 case BROTLI_RESULT_NEEDS_MORE_OUTPUT: |
135 // Fall through. | 125 return bytes_written; |
136 case BROTLI_RESULT_SUCCESS: | 126 case BROTLI_RESULT_SUCCESS: |
137 *dest_len = bytes_written; | 127 decoding_status_ = DecodingStatus::DECODING_DONE; |
138 stream_data_len_ = base::checked_cast<int>(available_in); | 128 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: | 129 case BROTLI_RESULT_NEEDS_MORE_INPUT: |
147 *dest_len = bytes_written; | 130 // Decompress needs more input has consumed all existing input. |
148 stream_data_len_ = 0; | 131 DCHECK_EQ(*consumed_bytes, input_buffer_size); |
149 next_stream_data_ = nullptr; | 132 decoding_status_ = DecodingStatus::DECODING_IN_PROGRESS; |
150 return Filter::FILTER_NEED_MORE_DATA; | 133 return bytes_written; |
151 | 134 // If the decompressor threw an error, fail synchronously. |
152 default: | 135 default: |
153 decoding_status_ = DecodingStatus::DECODING_ERROR; | 136 decoding_status_ = DecodingStatus::DECODING_ERROR; |
154 return Filter::FILTER_ERROR; | 137 return ERR_CONTENT_DECODING_FAILED; |
155 } | 138 } |
156 } | 139 } |
157 | 140 |
158 private: | |
159 static void* AllocateMemory(void* opaque, size_t size) { | 141 static void* AllocateMemory(void* opaque, size_t size) { |
160 BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); | 142 BrotliSourceStream* filter = reinterpret_cast<BrotliSourceStream*>(opaque); |
161 return filter->AllocateMemoryInternal(size); | 143 return filter->AllocateMemoryInternal(size); |
162 } | 144 } |
163 | 145 |
164 static void FreeMemory(void* opaque, void* address) { | 146 static void FreeMemory(void* opaque, void* address) { |
165 BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); | 147 BrotliSourceStream* filter = reinterpret_cast<BrotliSourceStream*>(opaque); |
166 filter->FreeMemoryInternal(address); | 148 filter->FreeMemoryInternal(address); |
167 } | 149 } |
168 | 150 |
169 void* AllocateMemoryInternal(size_t size) { | 151 void* AllocateMemoryInternal(size_t size) { |
170 size_t* array = reinterpret_cast<size_t*>(malloc(size + sizeof(size_t))); | 152 size_t* array = reinterpret_cast<size_t*>(malloc(size + sizeof(size_t))); |
171 if (!array) | 153 if (!array) |
172 return nullptr; | 154 return nullptr; |
173 used_memory_ += size; | 155 used_memory_ += size; |
174 if (used_memory_maximum_ < used_memory_) | 156 if (used_memory_maximum_ < used_memory_) |
175 used_memory_maximum_ = used_memory_; | 157 used_memory_maximum_ = used_memory_; |
176 array[0] = size; | 158 array[0] = size; |
177 return &array[1]; | 159 return &array[1]; |
178 } | 160 } |
179 | 161 |
180 void FreeMemoryInternal(void* address) { | 162 void FreeMemoryInternal(void* address) { |
181 if (!address) | 163 if (!address) |
182 return; | 164 return; |
183 size_t* array = reinterpret_cast<size_t*>(address); | 165 size_t* array = reinterpret_cast<size_t*>(address); |
184 used_memory_ -= array[-1]; | 166 used_memory_ -= array[-1]; |
185 free(&array[-1]); | 167 free(&array[-1]); |
186 } | 168 } |
187 | 169 |
188 // Reported in UMA and must be kept in sync with the histograms.xml file. | 170 BrotliState* brotli_state_; |
189 enum class DecodingStatus : int { | |
190 DECODING_IN_PROGRESS = 0, | |
191 DECODING_DONE, | |
192 DECODING_ERROR, | |
193 | 171 |
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_; | 172 DecodingStatus decoding_status_; |
201 | 173 |
202 BrotliState* brotli_state_; | |
203 | |
204 size_t used_memory_; | 174 size_t used_memory_; |
205 size_t used_memory_maximum_; | 175 size_t used_memory_maximum_; |
206 size_t consumed_bytes_; | 176 size_t consumed_bytes_; |
207 size_t produced_bytes_; | 177 size_t produced_bytes_; |
208 | 178 |
209 bool gzip_header_detected_; | 179 bool gzip_header_detected_; |
210 | 180 |
211 DISALLOW_COPY_AND_ASSIGN(BrotliFilter); | 181 DISALLOW_COPY_AND_ASSIGN(BrotliSourceStream); |
212 }; | 182 }; |
213 | 183 |
214 Filter* CreateBrotliFilter(Filter::FilterType type_id) { | 184 } // namespace |
215 return new BrotliFilter(type_id); | 185 |
| 186 std::unique_ptr<FilterSourceStream> CreateBrotliSourceStream( |
| 187 std::unique_ptr<SourceStream> previous) { |
| 188 return base::WrapUnique(new BrotliSourceStream(std::move(previous))); |
216 } | 189 } |
217 | 190 |
218 } // namespace net | 191 } // namespace net |
OLD | NEW |