| OLD | NEW |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 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 <limits.h> | 5 #include <limits.h> |
| 6 #include <ctype.h> | 6 #include <ctype.h> |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
| 10 #include "base/histogram.h" | 10 #include "base/histogram.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "net/base/sdch_filter.h" | 12 #include "net/base/sdch_filter.h" |
| 13 #include "net/base/sdch_manager.h" | 13 #include "net/base/sdch_manager.h" |
| 14 | 14 |
| 15 #include "sdch/open-vcdiff/src/google/vcdecoder.h" | 15 #include "sdch/open-vcdiff/src/google/vcdecoder.h" |
| 16 | 16 |
| 17 SdchFilter::SdchFilter(const FilterContext& filter_context) | 17 SdchFilter::SdchFilter(const FilterContext& filter_context) |
| 18 : Filter(filter_context), | 18 : Filter(filter_context), |
| 19 decoding_status_(DECODING_UNINITIALIZED), | 19 decoding_status_(DECODING_UNINITIALIZED), |
| 20 vcdiff_streaming_decoder_(NULL), | 20 vcdiff_streaming_decoder_(NULL), |
| 21 dictionary_hash_(), | 21 dictionary_hash_(), |
| 22 dictionary_hash_is_plausible_(false), | 22 dictionary_hash_is_plausible_(false), |
| 23 dictionary_(NULL), | 23 dictionary_(NULL), |
| 24 dest_buffer_excess_(), | 24 dest_buffer_excess_(), |
| 25 dest_buffer_excess_index_(0), | 25 dest_buffer_excess_index_(0), |
| 26 source_bytes_(0), | 26 source_bytes_(0), |
| 27 output_bytes_(0), | 27 output_bytes_(0), |
| 28 observed_packet_count_(0), | |
| 29 bytes_observed_in_packets_(0), | |
| 30 final_packet_time_(), | |
| 31 possible_pass_through_(false), | 28 possible_pass_through_(false), |
| 32 connect_time_(filter_context.GetRequestTime()), | |
| 33 was_cached_(filter_context.IsCachedContent()) { | 29 was_cached_(filter_context.IsCachedContent()) { |
| 34 bool success = filter_context.GetMimeType(&mime_type_); | 30 bool success = filter_context.GetMimeType(&mime_type_); |
| 35 DCHECK(success); | 31 DCHECK(success); |
| 36 success = filter_context.GetURL(&url_); | 32 success = filter_context.GetURL(&url_); |
| 37 DCHECK(success); | 33 DCHECK(success); |
| 38 } | 34 } |
| 39 | 35 |
| 40 SdchFilter::~SdchFilter() { | 36 SdchFilter::~SdchFilter() { |
| 41 // All code here is for gathering stats, and can be removed when SDCH is | 37 // All code here is for gathering stats, and can be removed when SDCH is |
| 42 // considered stable. | 38 // considered stable. |
| (...skipping 30 matching lines...) Expand all Loading... |
| 73 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffOut", output_bytes_); | 69 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffOut", output_bytes_); |
| 74 } | 70 } |
| 75 | 71 |
| 76 if (was_cached_) { | 72 if (was_cached_) { |
| 77 // Not a real error, but it is useful to have this tally. | 73 // Not a real error, but it is useful to have this tally. |
| 78 // TODO(jar): Remove this stat after SDCH stability is validated. | 74 // TODO(jar): Remove this stat after SDCH stability is validated. |
| 79 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); | 75 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); |
| 80 return; // We don't need timing stats, and we aready got ratios. | 76 return; // We don't need timing stats, and we aready got ratios. |
| 81 } | 77 } |
| 82 | 78 |
| 83 if (base::Time() == connect_time_ || read_times_.empty()) { | |
| 84 // Not a real error, but it is useful to have this tally. | |
| 85 // TODO(jar): Remove this stat after SDCH stability is validated. | |
| 86 SdchManager::SdchErrorRecovery(SdchManager::MISSING_TIME_STATS); | |
| 87 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeBytesIn", | |
| 88 static_cast<int>(filter_context().GetByteReadCount())); | |
| 89 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeVcdiffIn", source_bytes_); | |
| 90 return; | |
| 91 } | |
| 92 // Since read_times_ is not empty, we can assert we have some packets times. | |
| 93 DCHECK(final_packet_time_ != base::Time()); | |
| 94 | |
| 95 base::TimeDelta duration = final_packet_time_ - connect_time_; | |
| 96 // We clip our logging at 10 minutes to prevent anamolous data from being | |
| 97 // considered (per suggestion from Jake Brutlag). | |
| 98 if (10 < duration.InMinutes()) { | |
| 99 SdchManager::SdchErrorRecovery(SdchManager::OVER_10_MINUTES); | |
| 100 return; | |
| 101 } | |
| 102 | |
| 103 switch (decoding_status_) { | 79 switch (decoding_status_) { |
| 104 case DECODING_IN_PROGRESS: { | 80 case DECODING_IN_PROGRESS: { |
| 105 UMA_HISTOGRAM_PERCENTAGE("Sdch.Network_Decode_Ratio_a", static_cast<int>( | 81 UMA_HISTOGRAM_PERCENTAGE("Sdch.Network_Decode_Ratio_a", static_cast<int>( |
| 106 (filter_context().GetByteReadCount() * 100) / output_bytes_)); | 82 (filter_context().GetByteReadCount() * 100) / output_bytes_)); |
| 107 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_Latency_F_a", duration, | |
| 108 base::TimeDelta::FromMilliseconds(20), | |
| 109 base::TimeDelta::FromMinutes(10), 100); | |
| 110 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_Last_a", | |
| 111 final_packet_time_ - read_times_[0], | |
| 112 base::TimeDelta::FromMilliseconds(20), | |
| 113 base::TimeDelta::FromMinutes(10), 100); | |
| 114 if (read_times_.size() > 4) { | |
| 115 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_2nd_b", | |
| 116 read_times_[1] - read_times_[0], | |
| 117 base::TimeDelta::FromMilliseconds(10), | |
| 118 base::TimeDelta::FromSeconds(3), 100); | |
| 119 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_2nd_To_3rd_b", | |
| 120 read_times_[2] - read_times_[1], | |
| 121 base::TimeDelta::FromMilliseconds(10), | |
| 122 base::TimeDelta::FromSeconds(3), 100); | |
| 123 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_3rd_To_4th_b", | |
| 124 read_times_[3] - read_times_[2], | |
| 125 base::TimeDelta::FromMilliseconds(10), | |
| 126 base::TimeDelta::FromSeconds(3), 100); | |
| 127 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_4th_To_5th_b", | |
| 128 read_times_[4] - read_times_[3], | |
| 129 base::TimeDelta::FromMilliseconds(10), | |
| 130 base::TimeDelta::FromSeconds(3), 100); | |
| 131 } | |
| 132 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Decode_Packets_b", | |
| 133 observed_packet_count_); | |
| 134 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_Processed_a", | |
| 135 static_cast<int>(filter_context().GetByteReadCount())); | |
| 136 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_VcdiffOut_a", | 83 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_VcdiffOut_a", |
| 137 output_bytes_); | 84 output_bytes_); |
| 85 filter_context().RecordPacketStats(FilterContext::SDCH_DECODE); |
| 86 |
| 87 // Allow latency experiments to proceed. |
| 88 SdchManager::Global()->SetAllowLatencyExperiment(url_, true); |
| 138 return; | 89 return; |
| 139 } | 90 } |
| 140 case PASS_THROUGH: { | 91 case PASS_THROUGH: { |
| 141 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_Latency_F_a", | 92 filter_context().RecordPacketStats(FilterContext::SDCH_PASSTHROUGH); |
| 142 duration, | |
| 143 base::TimeDelta::FromMilliseconds(20), | |
| 144 base::TimeDelta::FromMinutes(10), 100); | |
| 145 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_Last_a", | |
| 146 final_packet_time_ - read_times_[0], | |
| 147 base::TimeDelta::FromMilliseconds(20), | |
| 148 base::TimeDelta::FromMinutes(10), 100); | |
| 149 if (read_times_.size() > 4) { | |
| 150 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_2nd_b", | |
| 151 read_times_[1] - read_times_[0], | |
| 152 base::TimeDelta::FromMilliseconds(10), | |
| 153 base::TimeDelta::FromSeconds(3), 100); | |
| 154 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_2nd_To_3rd_b", | |
| 155 read_times_[2] - read_times_[1], | |
| 156 base::TimeDelta::FromMilliseconds(10), | |
| 157 base::TimeDelta::FromSeconds(3), 100); | |
| 158 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_3rd_To_4th_b", | |
| 159 read_times_[3] - read_times_[2], | |
| 160 base::TimeDelta::FromMilliseconds(10), | |
| 161 base::TimeDelta::FromSeconds(3), 100); | |
| 162 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_4th_To_5th_b", | |
| 163 read_times_[4] - read_times_[3], | |
| 164 base::TimeDelta::FromMilliseconds(10), | |
| 165 base::TimeDelta::FromSeconds(3), 100); | |
| 166 } | |
| 167 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Pass-through_Packets_b", | |
| 168 observed_packet_count_); | |
| 169 return; | 93 return; |
| 170 } | 94 } |
| 171 case DECODING_UNINITIALIZED: { | 95 case DECODING_UNINITIALIZED: { |
| 172 SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED); | 96 SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED); |
| 173 return; | 97 return; |
| 174 } | 98 } |
| 175 case WAITING_FOR_DICTIONARY_SELECTION: { | 99 case WAITING_FOR_DICTIONARY_SELECTION: { |
| 176 SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY); | 100 SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY); |
| 177 return; | 101 return; |
| 178 } | 102 } |
| 179 case DECODING_ERROR: { | 103 case DECODING_ERROR: { |
| 180 SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR); | 104 SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR); |
| 181 return; | 105 return; |
| 182 } | 106 } |
| 183 case META_REFRESH_RECOVERY: { | 107 case META_REFRESH_RECOVERY: { |
| 184 // Already accounted for when set. | 108 // Already accounted for when set. |
| 185 return; | 109 return; |
| 186 } | 110 } |
| 187 } // end of switch. | 111 } // end of switch. |
| 188 } | 112 } |
| 189 | 113 |
| 190 void SdchFilter::UpdateReadTimes() { | |
| 191 if (!next_stream_data_ || (stream_data_len_ == 0)) { | |
| 192 // Don't update when we're called to just flush out our internal buffers. | |
| 193 return; | |
| 194 } | |
| 195 const size_t bytes_read_so_far = | |
| 196 static_cast<size_t>(filter_context().GetByteReadCount()); | |
| 197 if (bytes_read_so_far <= bytes_observed_in_packets_) { | |
| 198 DCHECK(bytes_read_so_far == bytes_observed_in_packets_); | |
| 199 return; // No new bytes have arrived. | |
| 200 } | |
| 201 | |
| 202 // We only save distinct times for the first 5 packets. | |
| 203 const size_t kMaxTimesInArray = 5; | |
| 204 const size_t kTypicalPacketSize = 1430; | |
| 205 final_packet_time_ = base::Time::Now(); | |
| 206 while (bytes_read_so_far > bytes_observed_in_packets_) { | |
| 207 if (++observed_packet_count_ <= kMaxTimesInArray) { | |
| 208 read_times_.push_back(final_packet_time_); | |
| 209 } | |
| 210 bytes_observed_in_packets_ += kTypicalPacketSize; | |
| 211 } | |
| 212 // Since packets may not be full, we'll remember the number of bytes we've | |
| 213 // accounted for in packets thus far. | |
| 214 bytes_observed_in_packets_ = bytes_read_so_far; | |
| 215 } | |
| 216 | |
| 217 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { | 114 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { |
| 218 if (decoding_status_ != DECODING_UNINITIALIZED) | 115 if (decoding_status_ != DECODING_UNINITIALIZED) |
| 219 return false; | 116 return false; |
| 220 | 117 |
| 221 // Handle case where sdch filter is guessed, but not required. | 118 // Handle case where sdch filter is guessed, but not required. |
| 222 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) | 119 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) |
| 223 possible_pass_through_ = true; | 120 possible_pass_through_ = true; |
| 224 | 121 |
| 225 // Initialize decoder only after we have a dictionary in hand. | 122 // Initialize decoder only after we have a dictionary in hand. |
| 226 decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION; | 123 decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 238 | 135 |
| 239 | 136 |
| 240 Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, | 137 Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, |
| 241 int* dest_len) { | 138 int* dest_len) { |
| 242 int available_space = *dest_len; | 139 int available_space = *dest_len; |
| 243 *dest_len = 0; // Nothing output yet. | 140 *dest_len = 0; // Nothing output yet. |
| 244 | 141 |
| 245 if (!dest_buffer || available_space <= 0) | 142 if (!dest_buffer || available_space <= 0) |
| 246 return FILTER_ERROR; | 143 return FILTER_ERROR; |
| 247 | 144 |
| 248 UpdateReadTimes(); | |
| 249 | |
| 250 if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) { | 145 if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) { |
| 251 FilterStatus status = InitializeDictionary(); | 146 FilterStatus status = InitializeDictionary(); |
| 252 if (FILTER_NEED_MORE_DATA == status) | 147 if (FILTER_NEED_MORE_DATA == status) |
| 253 return FILTER_NEED_MORE_DATA; | 148 return FILTER_NEED_MORE_DATA; |
| 254 if (FILTER_ERROR == status) { | 149 if (FILTER_ERROR == status) { |
| 255 DCHECK(DECODING_ERROR == decoding_status_); | 150 DCHECK(DECODING_ERROR == decoding_status_); |
| 256 DCHECK(0 == dest_buffer_excess_index_); | 151 DCHECK_EQ(0u, dest_buffer_excess_index_); |
| 257 DCHECK(dest_buffer_excess_.empty()); | 152 DCHECK(dest_buffer_excess_.empty()); |
| 258 // This is where we try very hard to do error recovery, and make this | 153 // This is where we try very hard to do error recovery, and make this |
| 259 // protocol robust in teh face of proxies that do many different things. | 154 // protocol robust in teh face of proxies that do many different things. |
| 260 // If we decide that things are looking very bad (too hard to recover), | 155 // If we decide that things are looking very bad (too hard to recover), |
| 261 // we may even issue a "meta-refresh" to reload the page without an SDCH | 156 // we may even issue a "meta-refresh" to reload the page without an SDCH |
| 262 // advertisement (so that we are sure we're not hurting anything). First | 157 // advertisement (so that we are sure we're not hurting anything). First |
| 263 // we try for some light weight recovery, and teh final else clause below | 158 // we try for some light weight recovery, and teh final else clause below |
| 264 // supports the last ditch meta-refresh approach. | 159 // supports the last ditch meta-refresh approach. |
| 265 // | 160 // |
| 266 // Watch out for an error page inserted by the proxy as part of a 40x | 161 // Watch out for an error page inserted by the proxy as part of a 40x |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 } | 221 } |
| 327 } else { | 222 } else { |
| 328 DCHECK(DECODING_IN_PROGRESS == decoding_status_); | 223 DCHECK(DECODING_IN_PROGRESS == decoding_status_); |
| 329 } | 224 } |
| 330 } | 225 } |
| 331 | 226 |
| 332 int amount = OutputBufferExcess(dest_buffer, available_space); | 227 int amount = OutputBufferExcess(dest_buffer, available_space); |
| 333 *dest_len += amount; | 228 *dest_len += amount; |
| 334 dest_buffer += amount; | 229 dest_buffer += amount; |
| 335 available_space -= amount; | 230 available_space -= amount; |
| 336 DCHECK(available_space >= 0); | 231 DCHECK_GE(available_space, 0); |
| 337 | 232 |
| 338 if (available_space <= 0) | 233 if (available_space <= 0) |
| 339 return FILTER_OK; | 234 return FILTER_OK; |
| 340 DCHECK(dest_buffer_excess_.empty()); | 235 DCHECK(dest_buffer_excess_.empty()); |
| 341 DCHECK(0 == dest_buffer_excess_index_); | 236 DCHECK_EQ(0u, dest_buffer_excess_index_); |
| 342 | 237 |
| 343 if (decoding_status_ != DECODING_IN_PROGRESS) { | 238 if (decoding_status_ != DECODING_IN_PROGRESS) { |
| 344 if (META_REFRESH_RECOVERY == decoding_status_) { | 239 if (META_REFRESH_RECOVERY == decoding_status_) { |
| 345 // Absorb all input data. We've already output page reload HTML. | 240 // Absorb all input data. We've already output page reload HTML. |
| 346 next_stream_data_ = NULL; | 241 next_stream_data_ = NULL; |
| 347 stream_data_len_ = 0; | 242 stream_data_len_ = 0; |
| 348 return FILTER_NEED_MORE_DATA; | 243 return FILTER_NEED_MORE_DATA; |
| 349 } | 244 } |
| 350 if (PASS_THROUGH == decoding_status_) { | 245 if (PASS_THROUGH == decoding_status_) { |
| 351 // We must pass in available_space, but it will be changed to bytes_used. | 246 // We must pass in available_space, but it will be changed to bytes_used. |
| (...skipping 29 matching lines...) Expand all Loading... |
| 381 dest_buffer += amount; | 276 dest_buffer += amount; |
| 382 available_space -= amount; | 277 available_space -= amount; |
| 383 if (0 == available_space && !dest_buffer_excess_.empty()) | 278 if (0 == available_space && !dest_buffer_excess_.empty()) |
| 384 return FILTER_OK; | 279 return FILTER_OK; |
| 385 return FILTER_NEED_MORE_DATA; | 280 return FILTER_NEED_MORE_DATA; |
| 386 } | 281 } |
| 387 | 282 |
| 388 Filter::FilterStatus SdchFilter::InitializeDictionary() { | 283 Filter::FilterStatus SdchFilter::InitializeDictionary() { |
| 389 const size_t kServerIdLength = 9; // Dictionary hash plus null from server. | 284 const size_t kServerIdLength = 9; // Dictionary hash plus null from server. |
| 390 size_t bytes_needed = kServerIdLength - dictionary_hash_.size(); | 285 size_t bytes_needed = kServerIdLength - dictionary_hash_.size(); |
| 391 DCHECK(bytes_needed > 0); | 286 DCHECK_GT(bytes_needed, 0u); |
| 392 if (!next_stream_data_) | 287 if (!next_stream_data_) |
| 393 return FILTER_NEED_MORE_DATA; | 288 return FILTER_NEED_MORE_DATA; |
| 394 if (static_cast<size_t>(stream_data_len_) < bytes_needed) { | 289 if (static_cast<size_t>(stream_data_len_) < bytes_needed) { |
| 395 dictionary_hash_.append(next_stream_data_, stream_data_len_); | 290 dictionary_hash_.append(next_stream_data_, stream_data_len_); |
| 396 next_stream_data_ = NULL; | 291 next_stream_data_ = NULL; |
| 397 stream_data_len_ = 0; | 292 stream_data_len_ = 0; |
| 398 return FILTER_NEED_MORE_DATA; | 293 return FILTER_NEED_MORE_DATA; |
| 399 } | 294 } |
| 400 dictionary_hash_.append(next_stream_data_, bytes_needed); | 295 dictionary_hash_.append(next_stream_data_, bytes_needed); |
| 401 DCHECK(kServerIdLength == dictionary_hash_.size()); | 296 DCHECK(kServerIdLength == dictionary_hash_.size()); |
| 402 stream_data_len_ -= bytes_needed; | 297 stream_data_len_ -= bytes_needed; |
| 403 DCHECK(0 <= stream_data_len_); | 298 DCHECK_LE(0, stream_data_len_); |
| 404 if (stream_data_len_ > 0) | 299 if (stream_data_len_ > 0) |
| 405 next_stream_data_ += bytes_needed; | 300 next_stream_data_ += bytes_needed; |
| 406 else | 301 else |
| 407 next_stream_data_ = NULL; | 302 next_stream_data_ = NULL; |
| 408 | 303 |
| 409 DCHECK(!dictionary_.get()); | 304 DCHECK(!dictionary_.get()); |
| 410 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. | 305 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. |
| 411 | 306 |
| 412 SdchManager::Dictionary* dictionary = NULL; | 307 SdchManager::Dictionary* dictionary = NULL; |
| 413 if ('\0' == dictionary_hash_[kServerIdLength - 1]) | 308 if ('\0' == dictionary_hash_[kServerIdLength - 1]) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 453 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, | 348 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, |
| 454 amount); | 349 amount); |
| 455 dest_buffer_excess_index_ += amount; | 350 dest_buffer_excess_index_ += amount; |
| 456 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { | 351 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { |
| 457 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); | 352 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); |
| 458 dest_buffer_excess_.clear(); | 353 dest_buffer_excess_.clear(); |
| 459 dest_buffer_excess_index_ = 0; | 354 dest_buffer_excess_index_ = 0; |
| 460 } | 355 } |
| 461 return amount; | 356 return amount; |
| 462 } | 357 } |
| OLD | NEW |