| 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_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 |
| OLD | NEW |