| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |  | 
| 2 // Use of this source code is governed by a BSD-style license that can be |  | 
| 3 // found in the LICENSE file. |  | 
| 4 |  | 
| 5 #include "net/filter/brotli_filter.h" |  | 
| 6 |  | 
| 7 #include "base/bit_cast.h" |  | 
| 8 #include "base/macros.h" |  | 
| 9 #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 |  | 
| 14 namespace net { |  | 
| 15 |  | 
| 16 namespace { |  | 
| 17 const uint8_t kGzipHeader[] = {0x1f, 0x8b, 0x08}; |  | 
| 18 } |  | 
| 19 |  | 
| 20 // BrotliFilter applies Brotli content decoding to a data stream. |  | 
| 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: |  | 
| 27   BrotliFilter(FilterType type) |  | 
| 28       : Filter(type), |  | 
| 29         decoding_status_(DecodingStatus::DECODING_IN_PROGRESS), |  | 
| 30         used_memory_(0), |  | 
| 31         used_memory_maximum_(0), |  | 
| 32         consumed_bytes_(0), |  | 
| 33         produced_bytes_(0), |  | 
| 34         gzip_header_detected_(true) { |  | 
| 35     brotli_state_ = BrotliCreateState(BrotliFilter::AllocateMemory, |  | 
| 36                                       BrotliFilter::FreeMemory, this); |  | 
| 37     CHECK(brotli_state_); |  | 
| 38   } |  | 
| 39 |  | 
| 40   ~BrotliFilter() override { |  | 
| 41     BrotliErrorCode error_code = BrotliGetErrorCode(brotli_state_); |  | 
| 42     BrotliDestroyState(brotli_state_); |  | 
| 43     brotli_state_ = nullptr; |  | 
| 44     DCHECK(used_memory_ == 0); |  | 
| 45 |  | 
| 46     // Don't report that gzip header was detected in case of lack of input. |  | 
| 47     gzip_header_detected_ &= (consumed_bytes_ >= sizeof(kGzipHeader)); |  | 
| 48 |  | 
| 49     UMA_HISTOGRAM_ENUMERATION( |  | 
| 50         "BrotliFilter.Status", static_cast<int>(decoding_status_), |  | 
| 51         static_cast<int>(DecodingStatus::DECODING_STATUS_COUNT)); |  | 
| 52     UMA_HISTOGRAM_BOOLEAN("BrotliFilter.GzipHeaderDetected", |  | 
| 53                           gzip_header_detected_); |  | 
| 54     if (decoding_status_ == DecodingStatus::DECODING_DONE) { |  | 
| 55       // CompressionPercent is undefined when there is no output produced. |  | 
| 56       if (produced_bytes_ != 0) { |  | 
| 57         UMA_HISTOGRAM_PERCENTAGE( |  | 
| 58             "BrotliFilter.CompressionPercent", |  | 
| 59             static_cast<int>((consumed_bytes_ * 100) / produced_bytes_)); |  | 
| 60       } |  | 
| 61     } |  | 
| 62     if (error_code < 0) { |  | 
| 63       UMA_HISTOGRAM_ENUMERATION("BrotliFilter.ErrorCode", |  | 
| 64                                 -static_cast<int>(error_code), |  | 
| 65                                 1 - BROTLI_LAST_ERROR_CODE); |  | 
| 66     } |  | 
| 67 |  | 
| 68     // All code here is for gathering stats, and can be removed when |  | 
| 69     // BrotliFilter is considered stable. |  | 
| 70     static const int kBuckets = 48; |  | 
| 71     static const int64_t kMaxKb = 1 << (kBuckets / 3);  // 64MiB in KiB |  | 
| 72     UMA_HISTOGRAM_CUSTOM_COUNTS("BrotliFilter.UsedMemoryKB", |  | 
| 73                                 used_memory_maximum_ / 1024, 1, kMaxKb, |  | 
| 74                                 kBuckets); |  | 
| 75   } |  | 
| 76 |  | 
| 77   // Decodes the pre-filter data and writes the output into the |dest_buffer| |  | 
| 78   // passed in. |  | 
| 79   // The function returns FilterStatus. See filter.h for its description. |  | 
| 80   // |  | 
| 81   // Upon entry, |*dest_len| is the total size (in number of chars) of the |  | 
| 82   // destination buffer. Upon exit, |*dest_len| is the actual number of chars |  | 
| 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 |  | 
| 93     if (decoding_status_ == DecodingStatus::DECODING_DONE) { |  | 
| 94       *dest_len = 0; |  | 
| 95       return Filter::FILTER_DONE; |  | 
| 96     } |  | 
| 97 |  | 
| 98     if (decoding_status_ != DecodingStatus::DECODING_IN_PROGRESS) |  | 
| 99       return Filter::FILTER_ERROR; |  | 
| 100 |  | 
| 101     size_t output_buffer_size = base::checked_cast<size_t>(*dest_len); |  | 
| 102     size_t input_buffer_size = base::checked_cast<size_t>(stream_data_len_); |  | 
| 103 |  | 
| 104     size_t available_in = input_buffer_size; |  | 
| 105     const uint8_t* next_in = bit_cast<uint8_t*>(next_stream_data_); |  | 
| 106     size_t available_out = output_buffer_size; |  | 
| 107     uint8_t* next_out = bit_cast<uint8_t*>(dest_buffer); |  | 
| 108     size_t total_out = 0; |  | 
| 109 |  | 
| 110     // Check if start of the input stream looks like gzip stream. |  | 
| 111     for (size_t i = consumed_bytes_; i < sizeof(kGzipHeader); ++i) { |  | 
| 112       if (!gzip_header_detected_) |  | 
| 113         break; |  | 
| 114       size_t j = i - consumed_bytes_; |  | 
| 115       if (j < available_in && kGzipHeader[i] != next_in[j]) |  | 
| 116         gzip_header_detected_ = false; |  | 
| 117     } |  | 
| 118 |  | 
| 119     BrotliResult result = |  | 
| 120         BrotliDecompressStream(&available_in, &next_in, &available_out, |  | 
| 121                                &next_out, &total_out, brotli_state_); |  | 
| 122 |  | 
| 123     CHECK(available_in <= input_buffer_size); |  | 
| 124     CHECK(available_out <= output_buffer_size); |  | 
| 125     consumed_bytes_ += input_buffer_size - available_in; |  | 
| 126     produced_bytes_ += output_buffer_size - available_out; |  | 
| 127 |  | 
| 128     base::CheckedNumeric<size_t> safe_bytes_written(output_buffer_size); |  | 
| 129     safe_bytes_written -= available_out; |  | 
| 130     int bytes_written = |  | 
| 131         base::checked_cast<int>(safe_bytes_written.ValueOrDie()); |  | 
| 132 |  | 
| 133     switch (result) { |  | 
| 134       case BROTLI_RESULT_NEEDS_MORE_OUTPUT: |  | 
| 135       // Fall through. |  | 
| 136       case BROTLI_RESULT_SUCCESS: |  | 
| 137         *dest_len = bytes_written; |  | 
| 138         stream_data_len_ = base::checked_cast<int>(available_in); |  | 
| 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: |  | 
| 147         *dest_len = bytes_written; |  | 
| 148         stream_data_len_ = 0; |  | 
| 149         next_stream_data_ = nullptr; |  | 
| 150         return Filter::FILTER_NEED_MORE_DATA; |  | 
| 151 |  | 
| 152       default: |  | 
| 153         decoding_status_ = DecodingStatus::DECODING_ERROR; |  | 
| 154         return Filter::FILTER_ERROR; |  | 
| 155     } |  | 
| 156   } |  | 
| 157 |  | 
| 158  private: |  | 
| 159   static void* AllocateMemory(void* opaque, size_t size) { |  | 
| 160     BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); |  | 
| 161     return filter->AllocateMemoryInternal(size); |  | 
| 162   } |  | 
| 163 |  | 
| 164   static void FreeMemory(void* opaque, void* address) { |  | 
| 165     BrotliFilter* filter = reinterpret_cast<BrotliFilter*>(opaque); |  | 
| 166     filter->FreeMemoryInternal(address); |  | 
| 167   } |  | 
| 168 |  | 
| 169   void* AllocateMemoryInternal(size_t size) { |  | 
| 170     size_t* array = reinterpret_cast<size_t*>(malloc(size + sizeof(size_t))); |  | 
| 171     if (!array) |  | 
| 172       return nullptr; |  | 
| 173     used_memory_ += size; |  | 
| 174     if (used_memory_maximum_ < used_memory_) |  | 
| 175       used_memory_maximum_ = used_memory_; |  | 
| 176     array[0] = size; |  | 
| 177     return &array[1]; |  | 
| 178   } |  | 
| 179 |  | 
| 180   void FreeMemoryInternal(void* address) { |  | 
| 181     if (!address) |  | 
| 182       return; |  | 
| 183     size_t* array = reinterpret_cast<size_t*>(address); |  | 
| 184     used_memory_ -= array[-1]; |  | 
| 185     free(&array[-1]); |  | 
| 186   } |  | 
| 187 |  | 
| 188   // Reported in UMA and must be kept in sync with the histograms.xml file. |  | 
| 189   enum class DecodingStatus : int { |  | 
| 190     DECODING_IN_PROGRESS = 0, |  | 
| 191     DECODING_DONE, |  | 
| 192     DECODING_ERROR, |  | 
| 193 |  | 
| 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_; |  | 
| 201 |  | 
| 202   BrotliState* brotli_state_; |  | 
| 203 |  | 
| 204   size_t used_memory_; |  | 
| 205   size_t used_memory_maximum_; |  | 
| 206   size_t consumed_bytes_; |  | 
| 207   size_t produced_bytes_; |  | 
| 208 |  | 
| 209   bool gzip_header_detected_; |  | 
| 210 |  | 
| 211   DISALLOW_COPY_AND_ASSIGN(BrotliFilter); |  | 
| 212 }; |  | 
| 213 |  | 
| 214 Filter* CreateBrotliFilter(Filter::FilterType type_id) { |  | 
| 215   return new BrotliFilter(type_id); |  | 
| 216 } |  | 
| 217 |  | 
| 218 }  // namespace net |  | 
| OLD | NEW | 
|---|