• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
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/shared_dictionary/shared_dictionary_network_transaction.h"
6 
7 #include <optional>
8 #include <string>
9 #include <string_view>
10 
11 #include "base/base64.h"
12 #include "base/feature_list.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/notreached.h"
18 #include "base/strings/strcat.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/types/expected.h"
22 #include "net/base/completion_once_callback.h"
23 #include "net/base/features.h"
24 #include "net/base/hash_value.h"
25 #include "net/base/io_buffer.h"
26 #include "net/base/load_flags.h"
27 #include "net/base/net_errors.h"
28 #include "net/base/transport_info.h"
29 #include "net/base/url_util.h"
30 #include "net/cert/x509_certificate.h"
31 #include "net/filter/brotli_source_stream.h"
32 #include "net/filter/filter_source_stream.h"
33 #include "net/filter/source_stream.h"
34 #include "net/filter/zstd_source_stream.h"
35 #include "net/http/http_request_info.h"
36 #include "net/http/structured_headers.h"
37 #include "net/shared_dictionary/shared_dictionary_constants.h"
38 #include "net/shared_dictionary/shared_dictionary_header_checker_source_stream.h"
39 #include "net/shared_dictionary/shared_dictionary_isolation_key.h"
40 #include "net/ssl/ssl_private_key.h"
41 
42 namespace net {
43 
44 namespace {
45 
46 // Convert the interface from HttpTransaction to SourceStream.
47 class ProxyingSourceStream : public SourceStream {
48  public:
ProxyingSourceStream(HttpTransaction * transaction)49   explicit ProxyingSourceStream(HttpTransaction* transaction)
50       : SourceStream(SourceStream::TYPE_NONE), transaction_(transaction) {}
51 
52   ProxyingSourceStream(const ProxyingSourceStream&) = delete;
53   ProxyingSourceStream& operator=(const ProxyingSourceStream&) = delete;
54 
55   ~ProxyingSourceStream() override = default;
56 
57   // SourceStream implementation:
Read(IOBuffer * dest_buffer,int buffer_size,CompletionOnceCallback callback)58   int Read(IOBuffer* dest_buffer,
59            int buffer_size,
60            CompletionOnceCallback callback) override {
61     DCHECK(transaction_);
62     return transaction_->Read(dest_buffer, buffer_size, std::move(callback));
63   }
64 
Description() const65   std::string Description() const override { return std::string(); }
66 
MayHaveMoreBytes() const67   bool MayHaveMoreBytes() const override { return true; }
68 
69  private:
70   const raw_ptr<HttpTransaction> transaction_;
71 };
72 
AddAcceptEncoding(HttpRequestHeaders * request_headers,std::string_view encoding_header)73 void AddAcceptEncoding(HttpRequestHeaders* request_headers,
74                        std::string_view encoding_header) {
75   std::optional<std::string> accept_encoding =
76       request_headers->GetHeader(HttpRequestHeaders::kAcceptEncoding);
77   request_headers->SetHeader(
78       HttpRequestHeaders::kAcceptEncoding,
79       accept_encoding ? base::StrCat({*accept_encoding, ", ", encoding_header})
80                       : std::string(encoding_header));
81 }
82 
83 }  // namespace
84 
PendingReadTask(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)85 SharedDictionaryNetworkTransaction::PendingReadTask::PendingReadTask(
86     IOBuffer* buf,
87     int buf_len,
88     CompletionOnceCallback callback)
89     : buf(buf), buf_len(buf_len), callback(std::move(callback)) {}
90 
91 SharedDictionaryNetworkTransaction::PendingReadTask::~PendingReadTask() =
92     default;
93 
SharedDictionaryNetworkTransaction(std::unique_ptr<HttpTransaction> network_transaction,bool enable_shared_zstd)94 SharedDictionaryNetworkTransaction::SharedDictionaryNetworkTransaction(
95     std::unique_ptr<HttpTransaction> network_transaction,
96     bool enable_shared_zstd)
97     : enable_shared_zstd_(enable_shared_zstd),
98       network_transaction_(std::move(network_transaction)) {
99   network_transaction_->SetConnectedCallback(
100       base::BindRepeating(&SharedDictionaryNetworkTransaction::OnConnected,
101                           base::Unretained(this)));
102 }
103 
104 SharedDictionaryNetworkTransaction::~SharedDictionaryNetworkTransaction() =
105     default;
106 
Start(const HttpRequestInfo * request,CompletionOnceCallback callback,const NetLogWithSource & net_log)107 int SharedDictionaryNetworkTransaction::Start(const HttpRequestInfo* request,
108                                               CompletionOnceCallback callback,
109                                               const NetLogWithSource& net_log) {
110   if (!(request->load_flags & LOAD_CAN_USE_SHARED_DICTIONARY) ||
111       !request->dictionary_getter) {
112     return network_transaction_->Start(request, std::move(callback), net_log);
113   }
114   std::optional<SharedDictionaryIsolationKey> isolation_key =
115       SharedDictionaryIsolationKey::MaybeCreate(request->network_isolation_key,
116                                                 request->frame_origin);
117   shared_dictionary_getter_ = base::BindRepeating(request->dictionary_getter,
118                                                    isolation_key, request->url);
119 
120   // Safe to bind unretained `this` because the callback is owned by
121   // `network_transaction_` which is owned by `this`.
122   network_transaction_->SetModifyRequestHeadersCallback(base::BindRepeating(
123       &SharedDictionaryNetworkTransaction::ModifyRequestHeaders,
124       base::Unretained(this), request->url));
125   return network_transaction_->Start(
126       request,
127       base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
128                      base::Unretained(this), std::move(callback)),
129       net_log);
130 }
131 
132 SharedDictionaryNetworkTransaction::SharedDictionaryEncodingType
ParseSharedDictionaryEncodingType(const HttpResponseHeaders & headers)133 SharedDictionaryNetworkTransaction::ParseSharedDictionaryEncodingType(
134     const HttpResponseHeaders& headers) {
135   std::optional<std::string> content_encoding =
136       headers.GetNormalizedHeader("Content-Encoding");
137   if (!content_encoding) {
138     return SharedDictionaryEncodingType::kNotUsed;
139   } else if (content_encoding ==
140              shared_dictionary::kSharedBrotliContentEncodingName) {
141     return SharedDictionaryEncodingType::kSharedBrotli;
142   } else if (enable_shared_zstd_ &&
143              content_encoding ==
144                  shared_dictionary::kSharedZstdContentEncodingName) {
145     return SharedDictionaryEncodingType::kSharedZstd;
146   }
147   return SharedDictionaryEncodingType::kNotUsed;
148 }
149 
OnStartCompleted(CompletionOnceCallback callback,int result)150 void SharedDictionaryNetworkTransaction::OnStartCompleted(
151     CompletionOnceCallback callback,
152     int result) {
153   if (shared_dictionary_) {
154     base::UmaHistogramSparse(
155         base::StrCat({"Net.SharedDictionaryTransaction.NetResultWithDict.",
156                       cert_is_issued_by_known_root_
157                           ? "KnownRootCert"
158                           : "UnknownRootCertOrNoCert"}),
159         -result);
160   }
161 
162   if (result != OK || !shared_dictionary_) {
163     std::move(callback).Run(result);
164     return;
165   }
166 
167   shared_dictionary_encoding_type_ = ParseSharedDictionaryEncodingType(
168       *network_transaction_->GetResponseInfo()->headers);
169   if (shared_dictionary_encoding_type_ ==
170       SharedDictionaryEncodingType::kNotUsed) {
171     std::move(callback).Run(result);
172     return;
173   }
174 
175   shared_dictionary_used_response_info_ = std::make_unique<HttpResponseInfo>(
176       *network_transaction_->GetResponseInfo());
177   shared_dictionary_used_response_info_->did_use_shared_dictionary = true;
178   std::move(callback).Run(result);
179 }
180 
ModifyRequestHeaders(const GURL & request_url,HttpRequestHeaders * request_headers)181 void SharedDictionaryNetworkTransaction::ModifyRequestHeaders(
182     const GURL& request_url,
183     HttpRequestHeaders* request_headers) {
184   // `shared_dictionary_` may have been already set if this transaction was
185   // restarted
186   if (!shared_dictionary_) {
187     shared_dictionary_ = shared_dictionary_getter_.Run();
188   }
189   if (!shared_dictionary_) {
190     return;
191   }
192 
193   if (!IsLocalhost(request_url)) {
194     if (!base::FeatureList::IsEnabled(
195             features::kCompressionDictionaryTransportOverHttp1) &&
196         negotiated_protocol_ != kProtoHTTP2 &&
197         negotiated_protocol_ != kProtoQUIC) {
198       shared_dictionary_.reset();
199       return;
200     }
201     if (!base::FeatureList::IsEnabled(
202             features::kCompressionDictionaryTransportOverHttp2) &&
203         negotiated_protocol_ == kProtoHTTP2) {
204       shared_dictionary_.reset();
205       return;
206     }
207   }
208   if (base::FeatureList::IsEnabled(
209           features::kCompressionDictionaryTransportRequireKnownRootCert) &&
210       !cert_is_issued_by_known_root_ && !IsLocalhost(request_url)) {
211     shared_dictionary_.reset();
212     return;
213   }
214 
215   // `is_shared_dictionary_read_allowed_callback_` triggers a notification of
216   // the shared dictionary usage to the browser process. So we need to call
217   // `is_shared_dictionary_read_allowed_callback_` after checking the result
218   // of `GetDictionarySync()`.
219   CHECK(is_shared_dictionary_read_allowed_callback_);
220   if (!is_shared_dictionary_read_allowed_callback_.Run()) {
221     shared_dictionary_.reset();
222     return;
223   }
224   dictionary_hash_base64_ = base::StrCat(
225       {":", base::Base64Encode(shared_dictionary_->hash().data), ":"});
226   request_headers->SetHeader(shared_dictionary::kAvailableDictionaryHeaderName,
227                              dictionary_hash_base64_);
228   if (enable_shared_zstd_) {
229     AddAcceptEncoding(
230         request_headers,
231         base::StrCat({shared_dictionary::kSharedBrotliContentEncodingName, ", ",
232                       shared_dictionary::kSharedZstdContentEncodingName}));
233   } else {
234     AddAcceptEncoding(request_headers,
235                       shared_dictionary::kSharedBrotliContentEncodingName);
236   }
237 
238   if (!shared_dictionary_->id().empty()) {
239     std::optional<std::string> serialized_id =
240         structured_headers::SerializeItem(shared_dictionary_->id());
241     if (serialized_id) {
242       request_headers->SetHeader("Dictionary-ID", *serialized_id);
243     }
244   }
245 
246   if (dictionary_status_ == DictionaryStatus::kNoDictionary) {
247     dictionary_status_ = DictionaryStatus::kReading;
248     auto split_callback = base::SplitOnceCallback(base::BindOnce(
249         [](base::WeakPtr<SharedDictionaryNetworkTransaction> self,
250            base::Time read_start_time, int result) {
251           if (!self) {
252             bool succeeded = result == OK;
253             base::UmaHistogramTimes(
254                 base::StrCat({"Net.SharedDictionaryTransaction."
255                               "AbortedWhileReadingDictionary.",
256                               succeeded ? "Success" : "Failure"}),
257                 base::Time::Now() - read_start_time);
258             return;
259           }
260           self->OnReadSharedDictionary(read_start_time, result);
261         },
262         weak_factory_.GetWeakPtr(), /*read_start_time=*/base::Time::Now()));
263 
264     int read_result =
265         shared_dictionary_->ReadAll(std::move(split_callback.first));
266     if (read_result != ERR_IO_PENDING) {
267       std::move(split_callback.second).Run(read_result);
268     }
269   }
270 }
271 
OnReadSharedDictionary(base::Time read_start_time,int result)272 void SharedDictionaryNetworkTransaction::OnReadSharedDictionary(
273     base::Time read_start_time,
274     int result) {
275   bool succeeded = result == OK;
276   base::UmaHistogramTimes(
277       base::StrCat({"Net.SharedDictionaryTransaction.DictionaryReadLatency.",
278                     succeeded ? "Success" : "Failure"}),
279       base::Time::Now() - read_start_time);
280   if (!succeeded) {
281     dictionary_status_ = DictionaryStatus::kFailed;
282   } else {
283     dictionary_status_ = DictionaryStatus::kFinished;
284     CHECK(shared_dictionary_->data());
285   }
286   if (pending_read_task_) {
287     auto task = std::move(pending_read_task_);
288     auto split_callback = base::SplitOnceCallback(std::move(task->callback));
289     int ret =
290         Read(task->buf.get(), task->buf_len, std::move(split_callback.first));
291     if (ret != ERR_IO_PENDING) {
292       std::move(split_callback.second).Run(ret);
293     }
294   }
295 }
296 
RestartIgnoringLastError(CompletionOnceCallback callback)297 int SharedDictionaryNetworkTransaction::RestartIgnoringLastError(
298     CompletionOnceCallback callback) {
299   shared_dictionary_used_response_info_.reset();
300   return network_transaction_->RestartIgnoringLastError(
301       base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
302                      base::Unretained(this), std::move(callback)));
303 }
304 
RestartWithCertificate(scoped_refptr<X509Certificate> client_cert,scoped_refptr<SSLPrivateKey> client_private_key,CompletionOnceCallback callback)305 int SharedDictionaryNetworkTransaction::RestartWithCertificate(
306     scoped_refptr<X509Certificate> client_cert,
307     scoped_refptr<SSLPrivateKey> client_private_key,
308     CompletionOnceCallback callback) {
309   shared_dictionary_used_response_info_.reset();
310   return network_transaction_->RestartWithCertificate(
311       std::move(client_cert), std::move(client_private_key),
312       base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
313                      base::Unretained(this), std::move(callback)));
314 }
315 
RestartWithAuth(const AuthCredentials & credentials,CompletionOnceCallback callback)316 int SharedDictionaryNetworkTransaction::RestartWithAuth(
317     const AuthCredentials& credentials,
318     CompletionOnceCallback callback) {
319   shared_dictionary_used_response_info_.reset();
320   return network_transaction_->RestartWithAuth(
321       credentials,
322       base::BindOnce(&SharedDictionaryNetworkTransaction::OnStartCompleted,
323                      base::Unretained(this), std::move(callback)));
324 }
325 
IsReadyToRestartForAuth()326 bool SharedDictionaryNetworkTransaction::IsReadyToRestartForAuth() {
327   return network_transaction_->IsReadyToRestartForAuth();
328 }
329 
Read(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)330 int SharedDictionaryNetworkTransaction::Read(IOBuffer* buf,
331                                              int buf_len,
332                                              CompletionOnceCallback callback) {
333   if (!shared_dictionary_used_response_info_) {
334     return network_transaction_->Read(buf, buf_len, std::move(callback));
335   }
336 
337   switch (dictionary_status_) {
338     case DictionaryStatus::kNoDictionary:
339       NOTREACHED();
340     case DictionaryStatus::kReading:
341       CHECK(!pending_read_task_);
342       pending_read_task_ =
343           std::make_unique<PendingReadTask>(buf, buf_len, std::move(callback));
344       return ERR_IO_PENDING;
345     case DictionaryStatus::kFinished:
346       if (!shared_compression_stream_) {
347         // Wrap the source `network_transaction_` with a
348         // SharedDictionaryHeaderCheckerSourceStream to check the header
349         // of Dictionary-Compressed stream.
350         std::unique_ptr<SourceStream> header_checker_source_stream =
351             std::make_unique<SharedDictionaryHeaderCheckerSourceStream>(
352                 std::make_unique<ProxyingSourceStream>(
353                     network_transaction_.get()),
354                 shared_dictionary_encoding_type_ ==
355                         SharedDictionaryEncodingType::kSharedBrotli
356                     ? SharedDictionaryHeaderCheckerSourceStream::Type::
357                           kDictionaryCompressedBrotli
358                     : SharedDictionaryHeaderCheckerSourceStream::Type::
359                           kDictionaryCompressedZstd,
360                 shared_dictionary_->hash());
361         if (shared_dictionary_encoding_type_ ==
362             SharedDictionaryEncodingType::kSharedBrotli) {
363           SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
364               "Network.SharedDictionary."
365               "CreateBrotliSourceStreamWithDictionary");
366           shared_compression_stream_ = CreateBrotliSourceStreamWithDictionary(
367               std::move(header_checker_source_stream),
368               shared_dictionary_->data(), shared_dictionary_->size());
369         } else if (shared_dictionary_encoding_type_ ==
370                    SharedDictionaryEncodingType::kSharedZstd) {
371           SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
372               "Network.SharedDictionary.CreateZstdSourceStreamWithDictionary");
373           shared_compression_stream_ = CreateZstdSourceStreamWithDictionary(
374               std::move(header_checker_source_stream),
375               shared_dictionary_->data(), shared_dictionary_->size());
376         }
377 
378         UMA_HISTOGRAM_ENUMERATION("Network.SharedDictionary.EncodingType",
379                                   shared_dictionary_encoding_type_);
380       }
381       // When NET_DISABLE_BROTLI or NET_DISABLE_ZSTD is set,
382       // `shared_compression_stream_` can be null.
383       if (!shared_compression_stream_) {
384         return ERR_CONTENT_DECODING_FAILED;
385       }
386       return shared_compression_stream_->Read(buf, buf_len,
387                                               std::move(callback));
388     case DictionaryStatus::kFailed:
389       return ERR_DICTIONARY_LOAD_FAILED;
390   }
391 }
392 
StopCaching()393 void SharedDictionaryNetworkTransaction::StopCaching() {
394   network_transaction_->StopCaching();
395 }
396 
GetTotalReceivedBytes() const397 int64_t SharedDictionaryNetworkTransaction::GetTotalReceivedBytes() const {
398   return network_transaction_->GetTotalReceivedBytes();
399 }
400 
GetTotalSentBytes() const401 int64_t SharedDictionaryNetworkTransaction::GetTotalSentBytes() const {
402   return network_transaction_->GetTotalSentBytes();
403 }
404 
GetReceivedBodyBytes() const405 int64_t SharedDictionaryNetworkTransaction::GetReceivedBodyBytes() const {
406   return network_transaction_->GetReceivedBodyBytes();
407 }
408 
DoneReading()409 void SharedDictionaryNetworkTransaction::DoneReading() {
410   network_transaction_->DoneReading();
411 }
412 
GetResponseInfo() const413 const HttpResponseInfo* SharedDictionaryNetworkTransaction::GetResponseInfo()
414     const {
415   if (shared_dictionary_used_response_info_) {
416     return shared_dictionary_used_response_info_.get();
417   }
418   return network_transaction_->GetResponseInfo();
419 }
420 
GetLoadState() const421 LoadState SharedDictionaryNetworkTransaction::GetLoadState() const {
422   return network_transaction_->GetLoadState();
423 }
424 
SetQuicServerInfo(QuicServerInfo * quic_server_info)425 void SharedDictionaryNetworkTransaction::SetQuicServerInfo(
426     QuicServerInfo* quic_server_info) {
427   network_transaction_->SetQuicServerInfo(quic_server_info);
428 }
429 
GetLoadTimingInfo(LoadTimingInfo * load_timing_info) const430 bool SharedDictionaryNetworkTransaction::GetLoadTimingInfo(
431     LoadTimingInfo* load_timing_info) const {
432   return network_transaction_->GetLoadTimingInfo(load_timing_info);
433 }
434 
GetRemoteEndpoint(IPEndPoint * endpoint) const435 bool SharedDictionaryNetworkTransaction::GetRemoteEndpoint(
436     IPEndPoint* endpoint) const {
437   return network_transaction_->GetRemoteEndpoint(endpoint);
438 }
439 
PopulateNetErrorDetails(NetErrorDetails * details) const440 void SharedDictionaryNetworkTransaction::PopulateNetErrorDetails(
441     NetErrorDetails* details) const {
442   return network_transaction_->PopulateNetErrorDetails(details);
443 }
444 
SetPriority(RequestPriority priority)445 void SharedDictionaryNetworkTransaction::SetPriority(RequestPriority priority) {
446   network_transaction_->SetPriority(priority);
447 }
448 
449 void SharedDictionaryNetworkTransaction::
SetWebSocketHandshakeStreamCreateHelper(WebSocketHandshakeStreamBase::CreateHelper * create_helper)450     SetWebSocketHandshakeStreamCreateHelper(
451         WebSocketHandshakeStreamBase::CreateHelper* create_helper) {
452   network_transaction_->SetWebSocketHandshakeStreamCreateHelper(create_helper);
453 }
454 
SetBeforeNetworkStartCallback(BeforeNetworkStartCallback callback)455 void SharedDictionaryNetworkTransaction::SetBeforeNetworkStartCallback(
456     BeforeNetworkStartCallback callback) {
457   network_transaction_->SetBeforeNetworkStartCallback(std::move(callback));
458 }
459 
SetRequestHeadersCallback(RequestHeadersCallback callback)460 void SharedDictionaryNetworkTransaction::SetRequestHeadersCallback(
461     RequestHeadersCallback callback) {
462   network_transaction_->SetRequestHeadersCallback(std::move(callback));
463 }
464 
SetResponseHeadersCallback(ResponseHeadersCallback callback)465 void SharedDictionaryNetworkTransaction::SetResponseHeadersCallback(
466     ResponseHeadersCallback callback) {
467   network_transaction_->SetResponseHeadersCallback(std::move(callback));
468 }
469 
SetEarlyResponseHeadersCallback(ResponseHeadersCallback callback)470 void SharedDictionaryNetworkTransaction::SetEarlyResponseHeadersCallback(
471     ResponseHeadersCallback callback) {
472   network_transaction_->SetEarlyResponseHeadersCallback(std::move(callback));
473 }
474 
SetConnectedCallback(const ConnectedCallback & callback)475 void SharedDictionaryNetworkTransaction::SetConnectedCallback(
476     const ConnectedCallback& callback) {
477   connected_callback_ = callback;
478 }
479 
ResumeNetworkStart()480 int SharedDictionaryNetworkTransaction::ResumeNetworkStart() {
481   return network_transaction_->ResumeNetworkStart();
482 }
483 
SetModifyRequestHeadersCallback(base::RepeatingCallback<void (HttpRequestHeaders *)> callback)484 void SharedDictionaryNetworkTransaction::SetModifyRequestHeadersCallback(
485     base::RepeatingCallback<void(HttpRequestHeaders*)> callback) {
486   // This method should not be called for this class.
487   NOTREACHED();
488 }
489 
490 void SharedDictionaryNetworkTransaction::
SetIsSharedDictionaryReadAllowedCallback(base::RepeatingCallback<bool ()> callback)491     SetIsSharedDictionaryReadAllowedCallback(
492         base::RepeatingCallback<bool()> callback) {
493   is_shared_dictionary_read_allowed_callback_ = std::move(callback);
494 }
495 
GetConnectionAttempts() const496 ConnectionAttempts SharedDictionaryNetworkTransaction::GetConnectionAttempts()
497     const {
498   return network_transaction_->GetConnectionAttempts();
499 }
500 
CloseConnectionOnDestruction()501 void SharedDictionaryNetworkTransaction::CloseConnectionOnDestruction() {
502   network_transaction_->CloseConnectionOnDestruction();
503 }
504 
IsMdlMatchForMetrics() const505 bool SharedDictionaryNetworkTransaction::IsMdlMatchForMetrics() const {
506   return network_transaction_->IsMdlMatchForMetrics();
507 }
508 
OnConnected(const TransportInfo & info,CompletionOnceCallback callback)509 int SharedDictionaryNetworkTransaction::OnConnected(
510     const TransportInfo& info,
511     CompletionOnceCallback callback) {
512   cert_is_issued_by_known_root_ = info.cert_is_issued_by_known_root;
513   negotiated_protocol_ = info.negotiated_protocol;
514 
515   if (connected_callback_) {
516     return connected_callback_.Run(info, std::move(callback));
517   }
518   return OK;
519 }
520 
521 }  // namespace net
522