| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/http/partial_data.h" | 5 #include "net/http/partial_data.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/bind_helpers.h" | 8 #include "base/bind_helpers.h" |
| 9 #include "base/format_macros.h" | 9 #include "base/format_macros.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 25 const char kRangeHeader[] = "Content-Range"; | 25 const char kRangeHeader[] = "Content-Range"; |
| 26 const int kDataStream = 1; | 26 const int kDataStream = 1; |
| 27 | 27 |
| 28 } // namespace | 28 } // namespace |
| 29 | 29 |
| 30 // A core object that can be detached from the Partialdata object at destruction | 30 // A core object that can be detached from the Partialdata object at destruction |
| 31 // so that asynchronous operations cleanup can be performed. | 31 // so that asynchronous operations cleanup can be performed. |
| 32 class PartialData::Core { | 32 class PartialData::Core { |
| 33 public: | 33 public: |
| 34 // Build a new core object. Lifetime management is automatic. | 34 // Build a new core object. Lifetime management is automatic. |
| 35 static Core* CreateCore(PartialData* owner) { | 35 static Core* CreateCore(PartialData* owner) { return new Core(owner); } |
| 36 return new Core(owner); | |
| 37 } | |
| 38 | 36 |
| 39 // Wrapper for Entry::GetAvailableRange. If this method returns ERR_IO_PENDING | 37 // Wrapper for Entry::GetAvailableRange. If this method returns ERR_IO_PENDING |
| 40 // PartialData::GetAvailableRangeCompleted() will be invoked on the owner | 38 // PartialData::GetAvailableRangeCompleted() will be invoked on the owner |
| 41 // object when finished (unless Cancel() is called first). | 39 // object when finished (unless Cancel() is called first). |
| 42 int GetAvailableRange(disk_cache::Entry* entry, int64 offset, int len, | 40 int GetAvailableRange(disk_cache::Entry* entry, |
| 41 int64 offset, |
| 42 int len, |
| 43 int64* start); | 43 int64* start); |
| 44 | 44 |
| 45 // Cancels a pending operation. It is a mistake to call this method if there | 45 // Cancels a pending operation. It is a mistake to call this method if there |
| 46 // is no operation in progress; in fact, there will be no object to do so. | 46 // is no operation in progress; in fact, there will be no object to do so. |
| 47 void Cancel(); | 47 void Cancel(); |
| 48 | 48 |
| 49 private: | 49 private: |
| 50 explicit Core(PartialData* owner); | 50 explicit Core(PartialData* owner); |
| 51 ~Core(); | 51 ~Core(); |
| 52 | 52 |
| 53 // Pending io completion routine. | 53 // Pending io completion routine. |
| 54 void OnIOComplete(int result); | 54 void OnIOComplete(int result); |
| 55 | 55 |
| 56 PartialData* owner_; | 56 PartialData* owner_; |
| 57 int64 start_; | 57 int64 start_; |
| 58 | 58 |
| 59 DISALLOW_COPY_AND_ASSIGN(Core); | 59 DISALLOW_COPY_AND_ASSIGN(Core); |
| 60 }; | 60 }; |
| 61 | 61 |
| 62 PartialData::Core::Core(PartialData* owner) | 62 PartialData::Core::Core(PartialData* owner) : owner_(owner), start_(0) { |
| 63 : owner_(owner), start_(0) { | |
| 64 DCHECK(!owner_->core_); | 63 DCHECK(!owner_->core_); |
| 65 owner_->core_ = this; | 64 owner_->core_ = this; |
| 66 } | 65 } |
| 67 | 66 |
| 68 PartialData::Core::~Core() { | 67 PartialData::Core::~Core() { |
| 69 if (owner_) | 68 if (owner_) |
| 70 owner_->core_ = NULL; | 69 owner_->core_ = NULL; |
| 71 } | 70 } |
| 72 | 71 |
| 73 void PartialData::Core::Cancel() { | 72 void PartialData::Core::Cancel() { |
| 74 DCHECK(owner_); | 73 DCHECK(owner_); |
| 75 owner_ = NULL; | 74 owner_ = NULL; |
| 76 } | 75 } |
| 77 | 76 |
| 78 int PartialData::Core::GetAvailableRange(disk_cache::Entry* entry, int64 offset, | 77 int PartialData::Core::GetAvailableRange(disk_cache::Entry* entry, |
| 79 int len, int64* start) { | 78 int64 offset, |
| 79 int len, |
| 80 int64* start) { |
| 80 int rv = entry->GetAvailableRange( | 81 int rv = entry->GetAvailableRange( |
| 81 offset, len, &start_, base::Bind(&PartialData::Core::OnIOComplete, | 82 offset, |
| 82 base::Unretained(this))); | 83 len, |
| 84 &start_, |
| 85 base::Bind(&PartialData::Core::OnIOComplete, base::Unretained(this))); |
| 83 if (rv != net::ERR_IO_PENDING) { | 86 if (rv != net::ERR_IO_PENDING) { |
| 84 // The callback will not be invoked. Lets cleanup. | 87 // The callback will not be invoked. Lets cleanup. |
| 85 *start = start_; | 88 *start = start_; |
| 86 delete this; | 89 delete this; |
| 87 } | 90 } |
| 88 return rv; | 91 return rv; |
| 89 } | 92 } |
| 90 | 93 |
| 91 void PartialData::Core::OnIOComplete(int result) { | 94 void PartialData::Core::OnIOComplete(int result) { |
| 92 if (owner_) | 95 if (owner_) |
| (...skipping 27 matching lines...) Expand all Loading... |
| 120 return false; | 123 return false; |
| 121 | 124 |
| 122 // We can handle this range request. | 125 // We can handle this range request. |
| 123 byte_range_ = ranges[0]; | 126 byte_range_ = ranges[0]; |
| 124 if (!byte_range_.IsValid()) | 127 if (!byte_range_.IsValid()) |
| 125 return false; | 128 return false; |
| 126 | 129 |
| 127 resource_size_ = 0; | 130 resource_size_ = 0; |
| 128 current_range_start_ = byte_range_.first_byte_position(); | 131 current_range_start_ = byte_range_.first_byte_position(); |
| 129 | 132 |
| 130 DVLOG(1) << "Range start: " << current_range_start_ << " end: " << | 133 DVLOG(1) << "Range start: " << current_range_start_ |
| 131 byte_range_.last_byte_position(); | 134 << " end: " << byte_range_.last_byte_position(); |
| 132 return true; | 135 return true; |
| 133 } | 136 } |
| 134 | 137 |
| 135 void PartialData::SetHeaders(const HttpRequestHeaders& headers) { | 138 void PartialData::SetHeaders(const HttpRequestHeaders& headers) { |
| 136 DCHECK(extra_headers_.IsEmpty()); | 139 DCHECK(extra_headers_.IsEmpty()); |
| 137 extra_headers_.CopyFrom(headers); | 140 extra_headers_.CopyFrom(headers); |
| 138 } | 141 } |
| 139 | 142 |
| 140 void PartialData::RestoreHeaders(HttpRequestHeaders* headers) const { | 143 void PartialData::RestoreHeaders(HttpRequestHeaders* headers) const { |
| 141 DCHECK(current_range_start_ >= 0 || byte_range_.IsSuffixByteRange()); | 144 DCHECK(current_range_start_ >= 0 || byte_range_.IsSuffixByteRange()); |
| 142 int64 end = byte_range_.IsSuffixByteRange() ? | 145 int64 end = byte_range_.IsSuffixByteRange() |
| 143 byte_range_.suffix_length() : byte_range_.last_byte_position(); | 146 ? byte_range_.suffix_length() |
| 147 : byte_range_.last_byte_position(); |
| 144 | 148 |
| 145 headers->CopyFrom(extra_headers_); | 149 headers->CopyFrom(extra_headers_); |
| 146 if (truncated_ || !byte_range_.IsValid()) | 150 if (truncated_ || !byte_range_.IsValid()) |
| 147 return; | 151 return; |
| 148 | 152 |
| 149 if (current_range_start_ < 0) { | 153 if (current_range_start_ < 0) { |
| 150 headers->SetHeader(HttpRequestHeaders::kRange, | 154 headers->SetHeader(HttpRequestHeaders::kRange, |
| 151 HttpByteRange::Suffix(end).GetHeaderValue()); | 155 HttpByteRange::Suffix(end).GetHeaderValue()); |
| 152 } else { | 156 } else { |
| 153 headers->SetHeader(HttpRequestHeaders::kRange, | 157 headers->SetHeader( |
| 154 HttpByteRange::Bounded( | 158 HttpRequestHeaders::kRange, |
| 155 current_range_start_, end).GetHeaderValue()); | 159 HttpByteRange::Bounded(current_range_start_, end).GetHeaderValue()); |
| 156 } | 160 } |
| 157 } | 161 } |
| 158 | 162 |
| 159 int PartialData::ShouldValidateCache(disk_cache::Entry* entry, | 163 int PartialData::ShouldValidateCache(disk_cache::Entry* entry, |
| 160 const CompletionCallback& callback) { | 164 const CompletionCallback& callback) { |
| 161 DCHECK_GE(current_range_start_, 0); | 165 DCHECK_GE(current_range_start_, 0); |
| 162 | 166 |
| 163 // Scan the disk cache for the first cached portion within this range. | 167 // Scan the disk cache for the first cached portion within this range. |
| 164 int len = GetNextRangeLen(); | 168 int len = GetNextRangeLen(); |
| 165 if (!len) | 169 if (!len) |
| 166 return 0; | 170 return 0; |
| 167 | 171 |
| 168 DVLOG(3) << "ShouldValidateCache len: " << len; | 172 DVLOG(3) << "ShouldValidateCache len: " << len; |
| 169 | 173 |
| 170 if (sparse_entry_) { | 174 if (sparse_entry_) { |
| 171 DCHECK(callback_.is_null()); | 175 DCHECK(callback_.is_null()); |
| 172 Core* core = Core::CreateCore(this); | 176 Core* core = Core::CreateCore(this); |
| 173 cached_min_len_ = core->GetAvailableRange(entry, current_range_start_, len, | 177 cached_min_len_ = core->GetAvailableRange( |
| 174 &cached_start_); | 178 entry, current_range_start_, len, &cached_start_); |
| 175 | 179 |
| 176 if (cached_min_len_ == ERR_IO_PENDING) { | 180 if (cached_min_len_ == ERR_IO_PENDING) { |
| 177 callback_ = callback; | 181 callback_ = callback; |
| 178 return ERR_IO_PENDING; | 182 return ERR_IO_PENDING; |
| 179 } | 183 } |
| 180 } else if (!truncated_) { | 184 } else if (!truncated_) { |
| 181 if (byte_range_.HasFirstBytePosition() && | 185 if (byte_range_.HasFirstBytePosition() && |
| 182 byte_range_.first_byte_position() >= resource_size_) { | 186 byte_range_.first_byte_position() >= resource_size_) { |
| 183 // The caller should take care of this condition because we should have | 187 // The caller should take care of this condition because we should have |
| 184 // failed IsRequestedRangeOK(), but it's better to be consistent here. | 188 // failed IsRequestedRangeOK(), but it's better to be consistent here. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 203 int len = GetNextRangeLen(); | 207 int len = GetNextRangeLen(); |
| 204 DCHECK_NE(0, len); | 208 DCHECK_NE(0, len); |
| 205 range_present_ = false; | 209 range_present_ = false; |
| 206 | 210 |
| 207 headers->CopyFrom(extra_headers_); | 211 headers->CopyFrom(extra_headers_); |
| 208 | 212 |
| 209 if (!cached_min_len_) { | 213 if (!cached_min_len_) { |
| 210 // We don't have anything else stored. | 214 // We don't have anything else stored. |
| 211 final_range_ = true; | 215 final_range_ = true; |
| 212 cached_start_ = | 216 cached_start_ = |
| 213 byte_range_.HasLastBytePosition() ? current_range_start_ + len : 0; | 217 byte_range_.HasLastBytePosition() ? current_range_start_ + len : 0; |
| 214 } | 218 } |
| 215 | 219 |
| 216 if (current_range_start_ == cached_start_) { | 220 if (current_range_start_ == cached_start_) { |
| 217 // The data lives in the cache. | 221 // The data lives in the cache. |
| 218 range_present_ = true; | 222 range_present_ = true; |
| 219 if (len == cached_min_len_) | 223 if (len == cached_min_len_) |
| 220 final_range_ = true; | 224 final_range_ = true; |
| 221 headers->SetHeader( | 225 headers->SetHeader( |
| 222 HttpRequestHeaders::kRange, | 226 HttpRequestHeaders::kRange, |
| 223 net::HttpByteRange::Bounded( | 227 net::HttpByteRange::Bounded(current_range_start_, |
| 224 current_range_start_, | 228 cached_start_ + cached_min_len_ - 1) |
| 225 cached_start_ + cached_min_len_ - 1).GetHeaderValue()); | 229 .GetHeaderValue()); |
| 226 } else { | 230 } else { |
| 227 // This range is not in the cache. | 231 // This range is not in the cache. |
| 228 headers->SetHeader( | 232 headers->SetHeader( |
| 229 HttpRequestHeaders::kRange, | 233 HttpRequestHeaders::kRange, |
| 230 net::HttpByteRange::Bounded( | 234 net::HttpByteRange::Bounded(current_range_start_, cached_start_ - 1) |
| 231 current_range_start_, cached_start_ - 1).GetHeaderValue()); | 235 .GetHeaderValue()); |
| 232 } | 236 } |
| 233 } | 237 } |
| 234 | 238 |
| 235 bool PartialData::IsCurrentRangeCached() const { | 239 bool PartialData::IsCurrentRangeCached() const { |
| 236 return range_present_; | 240 return range_present_; |
| 237 } | 241 } |
| 238 | 242 |
| 239 bool PartialData::IsLastRange() const { | 243 bool PartialData::IsLastRange() const { |
| 240 return final_range_; | 244 return final_range_; |
| 241 } | 245 } |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 323 return rv; | 327 return rv; |
| 324 } | 328 } |
| 325 | 329 |
| 326 bool PartialData::ResponseHeadersOK(const HttpResponseHeaders* headers) { | 330 bool PartialData::ResponseHeadersOK(const HttpResponseHeaders* headers) { |
| 327 if (headers->response_code() == 304) { | 331 if (headers->response_code() == 304) { |
| 328 if (!byte_range_.IsValid() || truncated_) | 332 if (!byte_range_.IsValid() || truncated_) |
| 329 return true; | 333 return true; |
| 330 | 334 |
| 331 // We must have a complete range here. | 335 // We must have a complete range here. |
| 332 return byte_range_.HasFirstBytePosition() && | 336 return byte_range_.HasFirstBytePosition() && |
| 333 byte_range_.HasLastBytePosition(); | 337 byte_range_.HasLastBytePosition(); |
| 334 } | 338 } |
| 335 | 339 |
| 336 int64 start, end, total_length; | 340 int64 start, end, total_length; |
| 337 if (!headers->GetContentRange(&start, &end, &total_length)) | 341 if (!headers->GetContentRange(&start, &end, &total_length)) |
| 338 return false; | 342 return false; |
| 339 if (total_length <= 0) | 343 if (total_length <= 0) |
| 340 return false; | 344 return false; |
| 341 | 345 |
| 342 DCHECK_EQ(headers->response_code(), 206); | 346 DCHECK_EQ(headers->response_code(), 206); |
| 343 | 347 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 385 if (byte_range_.IsValid() && success) { | 389 if (byte_range_.IsValid() && success) { |
| 386 headers->UpdateWithNewRange(byte_range_, resource_size_, !sparse_entry_); | 390 headers->UpdateWithNewRange(byte_range_, resource_size_, !sparse_entry_); |
| 387 return; | 391 return; |
| 388 } | 392 } |
| 389 | 393 |
| 390 headers->RemoveHeader(kLengthHeader); | 394 headers->RemoveHeader(kLengthHeader); |
| 391 headers->RemoveHeader(kRangeHeader); | 395 headers->RemoveHeader(kRangeHeader); |
| 392 | 396 |
| 393 if (byte_range_.IsValid()) { | 397 if (byte_range_.IsValid()) { |
| 394 headers->ReplaceStatusLine("HTTP/1.1 416 Requested Range Not Satisfiable"); | 398 headers->ReplaceStatusLine("HTTP/1.1 416 Requested Range Not Satisfiable"); |
| 395 headers->AddHeader(base::StringPrintf("%s: bytes 0-0/%" PRId64, | 399 headers->AddHeader(base::StringPrintf( |
| 396 kRangeHeader, resource_size_)); | 400 "%s: bytes 0-0/%" PRId64, kRangeHeader, resource_size_)); |
| 397 headers->AddHeader(base::StringPrintf("%s: 0", kLengthHeader)); | 401 headers->AddHeader(base::StringPrintf("%s: 0", kLengthHeader)); |
| 398 } else { | 402 } else { |
| 399 // TODO(rvargas): Is it safe to change the protocol version? | 403 // TODO(rvargas): Is it safe to change the protocol version? |
| 400 headers->ReplaceStatusLine("HTTP/1.1 200 OK"); | 404 headers->ReplaceStatusLine("HTTP/1.1 200 OK"); |
| 401 DCHECK_NE(resource_size_, 0); | 405 DCHECK_NE(resource_size_, 0); |
| 402 headers->AddHeader(base::StringPrintf("%s: %" PRId64, kLengthHeader, | 406 headers->AddHeader( |
| 403 resource_size_)); | 407 base::StringPrintf("%s: %" PRId64, kLengthHeader, resource_size_)); |
| 404 } | 408 } |
| 405 } | 409 } |
| 406 | 410 |
| 407 void PartialData::FixContentLength(HttpResponseHeaders* headers) { | 411 void PartialData::FixContentLength(HttpResponseHeaders* headers) { |
| 408 headers->RemoveHeader(kLengthHeader); | 412 headers->RemoveHeader(kLengthHeader); |
| 409 headers->AddHeader(base::StringPrintf("%s: %" PRId64, kLengthHeader, | 413 headers->AddHeader( |
| 410 resource_size_)); | 414 base::StringPrintf("%s: %" PRId64, kLengthHeader, resource_size_)); |
| 411 } | 415 } |
| 412 | 416 |
| 413 int PartialData::CacheRead( | 417 int PartialData::CacheRead(disk_cache::Entry* entry, |
| 414 disk_cache::Entry* entry, IOBuffer* data, int data_len, | 418 IOBuffer* data, |
| 415 const net::CompletionCallback& callback) { | 419 int data_len, |
| 420 const net::CompletionCallback& callback) { |
| 416 int read_len = std::min(data_len, cached_min_len_); | 421 int read_len = std::min(data_len, cached_min_len_); |
| 417 if (!read_len) | 422 if (!read_len) |
| 418 return 0; | 423 return 0; |
| 419 | 424 |
| 420 int rv = 0; | 425 int rv = 0; |
| 421 if (sparse_entry_) { | 426 if (sparse_entry_) { |
| 422 rv = entry->ReadSparseData(current_range_start_, data, read_len, | 427 rv = entry->ReadSparseData(current_range_start_, data, read_len, callback); |
| 423 callback); | |
| 424 } else { | 428 } else { |
| 425 if (current_range_start_ > kint32max) | 429 if (current_range_start_ > kint32max) |
| 426 return ERR_INVALID_ARGUMENT; | 430 return ERR_INVALID_ARGUMENT; |
| 427 | 431 |
| 428 rv = entry->ReadData(kDataStream, static_cast<int>(current_range_start_), | 432 rv = entry->ReadData(kDataStream, |
| 429 data, read_len, callback); | 433 static_cast<int>(current_range_start_), |
| 434 data, |
| 435 read_len, |
| 436 callback); |
| 430 } | 437 } |
| 431 return rv; | 438 return rv; |
| 432 } | 439 } |
| 433 | 440 |
| 434 int PartialData::CacheWrite( | 441 int PartialData::CacheWrite(disk_cache::Entry* entry, |
| 435 disk_cache::Entry* entry, IOBuffer* data, int data_len, | 442 IOBuffer* data, |
| 436 const net::CompletionCallback& callback) { | 443 int data_len, |
| 444 const net::CompletionCallback& callback) { |
| 437 DVLOG(3) << "To write: " << data_len; | 445 DVLOG(3) << "To write: " << data_len; |
| 438 if (sparse_entry_) { | 446 if (sparse_entry_) { |
| 439 return entry->WriteSparseData( | 447 return entry->WriteSparseData( |
| 440 current_range_start_, data, data_len, callback); | 448 current_range_start_, data, data_len, callback); |
| 441 } else { | 449 } else { |
| 442 if (current_range_start_ > kint32max) | 450 if (current_range_start_ > kint32max) |
| 443 return ERR_INVALID_ARGUMENT; | 451 return ERR_INVALID_ARGUMENT; |
| 444 | 452 |
| 445 return entry->WriteData(kDataStream, static_cast<int>(current_range_start_), | 453 return entry->WriteData(kDataStream, |
| 446 data, data_len, callback, true); | 454 static_cast<int>(current_range_start_), |
| 455 data, |
| 456 data_len, |
| 457 callback, |
| 458 true); |
| 447 } | 459 } |
| 448 } | 460 } |
| 449 | 461 |
| 450 void PartialData::OnCacheReadCompleted(int result) { | 462 void PartialData::OnCacheReadCompleted(int result) { |
| 451 DVLOG(3) << "Read: " << result; | 463 DVLOG(3) << "Read: " << result; |
| 452 if (result > 0) { | 464 if (result > 0) { |
| 453 current_range_start_ += result; | 465 current_range_start_ += result; |
| 454 cached_min_len_ -= result; | 466 cached_min_len_ -= result; |
| 455 DCHECK_GE(cached_min_len_, 0); | 467 DCHECK_GE(cached_min_len_, 0); |
| 456 } | 468 } |
| 457 } | 469 } |
| 458 | 470 |
| 459 void PartialData::OnNetworkReadCompleted(int result) { | 471 void PartialData::OnNetworkReadCompleted(int result) { |
| 460 if (result > 0) | 472 if (result > 0) |
| 461 current_range_start_ += result; | 473 current_range_start_ += result; |
| 462 } | 474 } |
| 463 | 475 |
| 464 int PartialData::GetNextRangeLen() { | 476 int PartialData::GetNextRangeLen() { |
| 465 int64 range_len = | 477 int64 range_len = |
| 466 byte_range_.HasLastBytePosition() ? | 478 byte_range_.HasLastBytePosition() |
| 467 byte_range_.last_byte_position() - current_range_start_ + 1 : | 479 ? byte_range_.last_byte_position() - current_range_start_ + 1 |
| 468 kint32max; | 480 : kint32max; |
| 469 if (range_len > kint32max) | 481 if (range_len > kint32max) |
| 470 range_len = kint32max; | 482 range_len = kint32max; |
| 471 return static_cast<int32>(range_len); | 483 return static_cast<int32>(range_len); |
| 472 } | 484 } |
| 473 | 485 |
| 474 void PartialData::GetAvailableRangeCompleted(int result, int64 start) { | 486 void PartialData::GetAvailableRangeCompleted(int result, int64 start) { |
| 475 DCHECK(!callback_.is_null()); | 487 DCHECK(!callback_.is_null()); |
| 476 DCHECK_NE(ERR_IO_PENDING, result); | 488 DCHECK_NE(ERR_IO_PENDING, result); |
| 477 | 489 |
| 478 cached_start_ = start; | 490 cached_start_ = start; |
| 479 cached_min_len_ = result; | 491 cached_min_len_ = result; |
| 480 if (result >= 0) | 492 if (result >= 0) |
| 481 result = 1; // Return success, go ahead and validate the entry. | 493 result = 1; // Return success, go ahead and validate the entry. |
| 482 | 494 |
| 483 CompletionCallback cb = callback_; | 495 CompletionCallback cb = callback_; |
| 484 callback_.Reset(); | 496 callback_.Reset(); |
| 485 cb.Run(result); | 497 cb.Run(result); |
| 486 } | 498 } |
| 487 | 499 |
| 488 } // namespace net | 500 } // namespace net |
| OLD | NEW |