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 possible_pass_through_(false) { | 28 possible_pass_through_(false), |
| 29 connect_time_(filter_context.GetRequestTime()), |
| 30 was_cached_(filter_context.IsCachedContent()) { |
| 31 bool success = filter_context.GetMimeType(&mime_type_); |
| 32 DCHECK(success); |
| 33 success = filter_context.GetURL(&url_); |
| 34 DCHECK(success); |
29 } | 35 } |
30 | 36 |
31 SdchFilter::~SdchFilter() { | 37 SdchFilter::~SdchFilter() { |
| 38 // All code here is for gathering stats, and can be removed when SDCH is |
| 39 // considered stable. |
| 40 |
32 static int filter_use_count = 0; | 41 static int filter_use_count = 0; |
33 ++filter_use_count; | 42 ++filter_use_count; |
34 if (META_REFRESH_RECOVERY == decoding_status_) { | 43 if (META_REFRESH_RECOVERY == decoding_status_) { |
35 UMA_HISTOGRAM_COUNTS("Sdch.FilterUseBeforeDisabling", filter_use_count); | 44 UMA_HISTOGRAM_COUNTS("Sdch.FilterUseBeforeDisabling", filter_use_count); |
36 } | 45 } |
37 | 46 |
38 if (vcdiff_streaming_decoder_.get()) { | 47 if (vcdiff_streaming_decoder_.get()) { |
39 if (!vcdiff_streaming_decoder_->FinishDecoding()) { | 48 if (!vcdiff_streaming_decoder_->FinishDecoding()) { |
40 decoding_status_ = DECODING_ERROR; | 49 decoding_status_ = DECODING_ERROR; |
41 SdchManager::SdchErrorRecovery(SdchManager::INCOMPLETE_SDCH_CONTENT); | 50 SdchManager::SdchErrorRecovery(SdchManager::INCOMPLETE_SDCH_CONTENT); |
42 // Make it possible for the user to hit reload, and get non-sdch content. | 51 // Make it possible for the user to hit reload, and get non-sdch content. |
43 // Note this will "wear off" quickly enough, and is just meant to assure | 52 // Note this will "wear off" quickly enough, and is just meant to assure |
44 // in some rare case that the user is not stuck. | 53 // in some rare case that the user is not stuck. |
45 SdchManager::BlacklistDomain(url()); | 54 SdchManager::BlacklistDomain(url_); |
| 55 UMA_HISTOGRAM_COUNTS("Sdch.PartialBytesIn", |
| 56 static_cast<int>(filter_context().GetByteReadCount())); |
| 57 UMA_HISTOGRAM_COUNTS("Sdch.PartialVcdiffIn", source_bytes_); |
| 58 UMA_HISTOGRAM_COUNTS("Sdch.PartialVcdiffOut", output_bytes_); |
46 } | 59 } |
47 } | 60 } |
48 | 61 |
49 if (!dest_buffer_excess_.empty()) { | 62 if (!dest_buffer_excess_.empty()) { |
50 // Filter chaining error, or premature teardown. | 63 // Filter chaining error, or premature teardown. |
51 SdchManager::SdchErrorRecovery(SdchManager::UNFLUSHED_CONTENT); | 64 SdchManager::SdchErrorRecovery(SdchManager::UNFLUSHED_CONTENT); |
| 65 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedBytesIn", |
| 66 static_cast<int>(filter_context().GetByteReadCount())); |
| 67 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffIn", source_bytes_); |
| 68 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffOut", output_bytes_); |
52 } | 69 } |
53 | 70 |
54 if (was_cached()) { | 71 if (was_cached_) { |
55 // Not a real error, but it is useful to have this tally. | 72 // Not a real error, but it is useful to have this tally. |
56 // TODO(jar): Remove this stat after SDCH stability is validated. | 73 // TODO(jar): Remove this stat after SDCH stability is validated. |
57 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); | 74 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); |
58 } else if (base::Time() == connect_time() | 75 return; // We don't need timing stats, and we aready got ratios. |
59 || read_times_.empty()) { | 76 } |
| 77 |
| 78 if (base::Time() == connect_time_ || read_times_.empty()) { |
60 // Not a real error, but it is useful to have this tally. | 79 // Not a real error, but it is useful to have this tally. |
61 // TODO(jar): Remove this stat after SDCH stability is validated. | 80 // TODO(jar): Remove this stat after SDCH stability is validated. |
62 SdchManager::SdchErrorRecovery(SdchManager::MISSING_TIME_STATS); | 81 SdchManager::SdchErrorRecovery(SdchManager::MISSING_TIME_STATS); |
63 } else { | 82 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeBytesIn", |
64 base::TimeDelta duration = read_times_.back() - connect_time(); | 83 static_cast<int>(filter_context().GetByteReadCount())); |
65 // We clip our logging at 10 minutes to prevent anamolous data from being | 84 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeVcdiffIn", source_bytes_); |
66 // considered (per suggestion from Jake Brutlag). | 85 return; |
67 if (10 >= duration.InMinutes()) { | |
68 if (DECODING_IN_PROGRESS == decoding_status_) { | |
69 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_Latency_F", duration, | |
70 base::TimeDelta::FromMilliseconds(20), | |
71 base::TimeDelta::FromMinutes(10), 100); | |
72 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_Last", | |
73 read_times_.back() - read_times_[0], | |
74 base::TimeDelta::FromMilliseconds(20), | |
75 base::TimeDelta::FromMinutes(10), 100); | |
76 if (read_times_.size() > 3) { | |
77 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_3rd_To_4th", | |
78 read_times_[3] - read_times_[2], | |
79 base::TimeDelta::FromMilliseconds(10), | |
80 base::TimeDelta::FromSeconds(3), 100); | |
81 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_2nd_To_3rd", | |
82 read_times_[2] - read_times_[1], | |
83 base::TimeDelta::FromMilliseconds(10), | |
84 base::TimeDelta::FromSeconds(3), 100); | |
85 } | |
86 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Decode_Reads", | |
87 read_times_.size()); | |
88 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_Read", output_bytes_); | |
89 } else if (PASS_THROUGH == decoding_status_) { | |
90 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_Latency_F", | |
91 duration, | |
92 base::TimeDelta::FromMilliseconds(20), | |
93 base::TimeDelta::FromMinutes(10), 100); | |
94 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_Last", | |
95 read_times_.back() - read_times_[0], | |
96 base::TimeDelta::FromMilliseconds(20), | |
97 base::TimeDelta::FromMinutes(10), 100); | |
98 if (read_times_.size() > 3) { | |
99 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_3rd_To_4th", | |
100 read_times_[3] - read_times_[2], | |
101 base::TimeDelta::FromMilliseconds(10), | |
102 base::TimeDelta::FromSeconds(3), 100); | |
103 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_2nd_To_3rd", | |
104 read_times_[2] - read_times_[1], | |
105 base::TimeDelta::FromMilliseconds(10), | |
106 base::TimeDelta::FromSeconds(3), 100); | |
107 } | |
108 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Pass-through_Reads", | |
109 read_times_.size()); | |
110 } | |
111 } | |
112 } | 86 } |
113 | 87 |
114 if (dictionary_) | 88 base::TimeDelta duration = read_times_.back() - connect_time_; |
115 dictionary_->Release(); | 89 // We clip our logging at 10 minutes to prevent anamolous data from being |
| 90 // considered (per suggestion from Jake Brutlag). |
| 91 if (10 < duration.InMinutes()) { |
| 92 SdchManager::SdchErrorRecovery(SdchManager::OVER_10_MINUTES); |
| 93 return; |
| 94 } |
| 95 |
| 96 switch (decoding_status_) { |
| 97 case DECODING_IN_PROGRESS: { |
| 98 UMA_HISTOGRAM_PERCENTAGE("Sdch.Network_Decode_Ratio",static_cast<int>( |
| 99 (filter_context().GetByteReadCount() * 100) / output_bytes_)); |
| 100 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_Latency_F", duration, |
| 101 base::TimeDelta::FromMilliseconds(20), |
| 102 base::TimeDelta::FromMinutes(10), 100); |
| 103 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_Last", |
| 104 read_times_.back() - read_times_[0], |
| 105 base::TimeDelta::FromMilliseconds(20), |
| 106 base::TimeDelta::FromMinutes(10), 100); |
| 107 if (read_times_.size() > 3) { |
| 108 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_3rd_To_4th", |
| 109 read_times_[3] - read_times_[2], |
| 110 base::TimeDelta::FromMilliseconds(10), |
| 111 base::TimeDelta::FromSeconds(3), 100); |
| 112 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_2nd_To_3rd", |
| 113 read_times_[2] - read_times_[1], |
| 114 base::TimeDelta::FromMilliseconds(10), |
| 115 base::TimeDelta::FromSeconds(3), 100); |
| 116 } |
| 117 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Decode_Reads", |
| 118 read_times_.size()); |
| 119 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_Read", output_bytes_); |
| 120 return; |
| 121 } |
| 122 case PASS_THROUGH: { |
| 123 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_Latency_F", |
| 124 duration, |
| 125 base::TimeDelta::FromMilliseconds(20), |
| 126 base::TimeDelta::FromMinutes(10), 100); |
| 127 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_Last", |
| 128 read_times_.back() - read_times_[0], |
| 129 base::TimeDelta::FromMilliseconds(20), |
| 130 base::TimeDelta::FromMinutes(10), 100); |
| 131 if (read_times_.size() > 3) { |
| 132 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_3rd_To_4th", |
| 133 read_times_[3] - read_times_[2], |
| 134 base::TimeDelta::FromMilliseconds(10), |
| 135 base::TimeDelta::FromSeconds(3), 100); |
| 136 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_2nd_To_3rd", |
| 137 read_times_[2] - read_times_[1], |
| 138 base::TimeDelta::FromMilliseconds(10), |
| 139 base::TimeDelta::FromSeconds(3), 100); |
| 140 } |
| 141 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Pass-through_Reads", |
| 142 read_times_.size()); |
| 143 return; |
| 144 } |
| 145 case DECODING_UNINITIALIZED: { |
| 146 SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED); |
| 147 return; |
| 148 } |
| 149 case WAITING_FOR_DICTIONARY_SELECTION: { |
| 150 SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY); |
| 151 return; |
| 152 } |
| 153 case DECODING_ERROR: { |
| 154 SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR); |
| 155 return; |
| 156 } |
| 157 case META_REFRESH_RECOVERY: { |
| 158 // Already accounted for when set. |
| 159 return; |
| 160 } |
| 161 } // end of switch. |
116 } | 162 } |
117 | 163 |
118 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { | 164 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { |
119 if (decoding_status_ != DECODING_UNINITIALIZED) | 165 if (decoding_status_ != DECODING_UNINITIALIZED) |
120 return false; | 166 return false; |
121 | 167 |
122 // Handle case where sdch filter is guessed, but not required. | 168 // Handle case where sdch filter is guessed, but not required. |
123 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) | 169 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) |
124 possible_pass_through_ = true; | 170 possible_pass_through_ = true; |
125 | 171 |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
174 // though it is not! | 220 // though it is not! |
175 // The good news is that error recovery is clear... | 221 // The good news is that error recovery is clear... |
176 SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH); | 222 SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH); |
177 decoding_status_ = PASS_THROUGH; | 223 decoding_status_ = PASS_THROUGH; |
178 dest_buffer_excess_ = dictionary_hash_; // Send what we scanned. | 224 dest_buffer_excess_ = dictionary_hash_; // Send what we scanned. |
179 } else { | 225 } else { |
180 // We don't have the dictionary that was demanded. | 226 // We don't have the dictionary that was demanded. |
181 // With very low probability, random garbage data looked like a | 227 // With very low probability, random garbage data looked like a |
182 // dictionary specifier (8 ASCII characters followed by a null), but | 228 // dictionary specifier (8 ASCII characters followed by a null), but |
183 // that is sufficiently unlikely that we ignore it. | 229 // that is sufficiently unlikely that we ignore it. |
184 if (std::string::npos == mime_type().find_first_of("text/html")) { | 230 if (std::string::npos == mime_type_.find_first_of("text/html")) { |
185 SdchManager::BlacklistDomainForever(url()); | 231 SdchManager::BlacklistDomainForever(url_); |
186 if (was_cached()) | 232 if (was_cached_) |
187 SdchManager::SdchErrorRecovery( | 233 SdchManager::SdchErrorRecovery( |
188 SdchManager::CACHED_META_REFRESH_UNSUPPORTED); | 234 SdchManager::CACHED_META_REFRESH_UNSUPPORTED); |
189 else | 235 else |
190 SdchManager::SdchErrorRecovery( | 236 SdchManager::SdchErrorRecovery( |
191 SdchManager::META_REFRESH_UNSUPPORTED); | 237 SdchManager::META_REFRESH_UNSUPPORTED); |
192 return FILTER_ERROR; | 238 return FILTER_ERROR; |
193 } | 239 } |
194 // HTML content means we can issue a meta-refresh, and get the content | 240 // HTML content means we can issue a meta-refresh, and get the content |
195 // again, perhaps without SDCH (to be safe). | 241 // again, perhaps without SDCH (to be safe). |
196 if (was_cached()) { | 242 if (was_cached_) { |
197 // Cached content is probably a startup tab, so we'll just get fresh | 243 // Cached content is probably a startup tab, so we'll just get fresh |
198 // content and try again, without disabling sdch. | 244 // content and try again, without disabling sdch. |
199 SdchManager::SdchErrorRecovery( | 245 SdchManager::SdchErrorRecovery( |
200 SdchManager::META_REFRESH_CACHED_RECOVERY); | 246 SdchManager::META_REFRESH_CACHED_RECOVERY); |
201 } else { | 247 } else { |
202 // Since it wasn't in the cache, we definately need at least some | 248 // Since it wasn't in the cache, we definately need at least some |
203 // period of blacklisting to get the correct content. | 249 // period of blacklisting to get the correct content. |
204 SdchManager::BlacklistDomain(url()); | 250 SdchManager::BlacklistDomain(url_); |
205 SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY); | 251 SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY); |
206 } | 252 } |
207 decoding_status_ = META_REFRESH_RECOVERY; | 253 decoding_status_ = META_REFRESH_RECOVERY; |
208 // Issue a meta redirect with SDCH disabled. | 254 // Issue a meta redirect with SDCH disabled. |
209 dest_buffer_excess_ = kDecompressionErrorHtml; | 255 dest_buffer_excess_ = kDecompressionErrorHtml; |
210 } | 256 } |
211 } else { | 257 } else { |
212 DCHECK(DECODING_IN_PROGRESS == decoding_status_); | 258 DCHECK(DECODING_IN_PROGRESS == decoding_status_); |
213 } | 259 } |
214 } | 260 } |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
283 } | 329 } |
284 dictionary_hash_.append(next_stream_data_, bytes_needed); | 330 dictionary_hash_.append(next_stream_data_, bytes_needed); |
285 DCHECK(kServerIdLength == dictionary_hash_.size()); | 331 DCHECK(kServerIdLength == dictionary_hash_.size()); |
286 stream_data_len_ -= bytes_needed; | 332 stream_data_len_ -= bytes_needed; |
287 DCHECK(0 <= stream_data_len_); | 333 DCHECK(0 <= stream_data_len_); |
288 if (stream_data_len_ > 0) | 334 if (stream_data_len_ > 0) |
289 next_stream_data_ += bytes_needed; | 335 next_stream_data_ += bytes_needed; |
290 else | 336 else |
291 next_stream_data_ = NULL; | 337 next_stream_data_ = NULL; |
292 | 338 |
293 DCHECK(!dictionary_); | 339 DCHECK(!dictionary_.get()); |
294 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. | 340 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. |
| 341 |
| 342 SdchManager::Dictionary* dictionary = NULL; |
295 if ('\0' == dictionary_hash_[kServerIdLength - 1]) | 343 if ('\0' == dictionary_hash_[kServerIdLength - 1]) |
296 SdchManager::Global()->GetVcdiffDictionary(std::string(dictionary_hash_, 0, | 344 SdchManager::Global()->GetVcdiffDictionary(std::string(dictionary_hash_, 0, |
297 kServerIdLength - 1), | 345 kServerIdLength - 1), |
298 url(), &dictionary_); | 346 url_, &dictionary); |
299 else | 347 else |
300 dictionary_hash_is_plausible_ = false; | 348 dictionary_hash_is_plausible_ = false; |
301 | 349 |
302 if (!dictionary_) { | 350 if (!dictionary) { |
303 DCHECK(dictionary_hash_.size() == kServerIdLength); | 351 DCHECK(dictionary_hash_.size() == kServerIdLength); |
304 // Since dictionary was not found, check to see if hash was even plausible. | 352 // Since dictionary was not found, check to see if hash was even plausible. |
305 for (size_t i = 0; i < kServerIdLength - 1; ++i) { | 353 for (size_t i = 0; i < kServerIdLength - 1; ++i) { |
306 char base64_char = dictionary_hash_[i]; | 354 char base64_char = dictionary_hash_[i]; |
307 if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) { | 355 if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) { |
308 dictionary_hash_is_plausible_ = false; | 356 dictionary_hash_is_plausible_ = false; |
309 break; | 357 break; |
310 } | 358 } |
311 } | 359 } |
312 if (dictionary_hash_is_plausible_) | 360 if (dictionary_hash_is_plausible_) |
313 SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND); | 361 SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND); |
314 else | 362 else |
315 SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED); | 363 SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED); |
316 decoding_status_ = DECODING_ERROR; | 364 decoding_status_ = DECODING_ERROR; |
317 return FILTER_ERROR; | 365 return FILTER_ERROR; |
318 } | 366 } |
319 dictionary_->AddRef(); | 367 dictionary_ = dictionary; |
320 vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder); | 368 vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder); |
321 vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(), | 369 vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(), |
322 dictionary_->text().size()); | 370 dictionary_->text().size()); |
323 decoding_status_ = DECODING_IN_PROGRESS; | 371 decoding_status_ = DECODING_IN_PROGRESS; |
324 return FILTER_OK; | 372 return FILTER_OK; |
325 } | 373 } |
326 | 374 |
327 int SdchFilter::OutputBufferExcess(char* const dest_buffer, | 375 int SdchFilter::OutputBufferExcess(char* const dest_buffer, |
328 size_t available_space) { | 376 size_t available_space) { |
329 if (dest_buffer_excess_.empty()) | 377 if (dest_buffer_excess_.empty()) |
330 return 0; | 378 return 0; |
331 DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_); | 379 DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_); |
332 size_t amount = std::min(available_space, | 380 size_t amount = std::min(available_space, |
333 dest_buffer_excess_.size() - dest_buffer_excess_index_); | 381 dest_buffer_excess_.size() - dest_buffer_excess_index_); |
334 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, | 382 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, |
335 amount); | 383 amount); |
336 dest_buffer_excess_index_ += amount; | 384 dest_buffer_excess_index_ += amount; |
337 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { | 385 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { |
338 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); | 386 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); |
339 dest_buffer_excess_.clear(); | 387 dest_buffer_excess_.clear(); |
340 dest_buffer_excess_index_ = 0; | 388 dest_buffer_excess_index_ = 0; |
341 } | 389 } |
342 return amount; | 390 return amount; |
343 } | 391 } |
OLD | NEW |