1 // Copyright 2012 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/http/http_response_info.h"
6
7 #include <optional>
8
9 #include "base/logging.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/pickle.h"
12 #include "base/time/time.h"
13 #include "net/base/net_errors.h"
14 #include "net/cert/sct_status_flags.h"
15 #include "net/cert/signed_certificate_timestamp.h"
16 #include "net/cert/x509_certificate.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/ssl/ssl_cert_request_info.h"
19 #include "net/ssl/ssl_connection_status_flags.h"
20 #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
21 #include "third_party/boringssl/src/include/openssl/ssl.h"
22
23 using base::Time;
24
25 namespace net {
26
27 namespace {
28
KeyExchangeGroupIsValid(int ssl_connection_status)29 bool KeyExchangeGroupIsValid(int ssl_connection_status) {
30 // TLS 1.3 and later always treat the field correctly.
31 if (SSLConnectionStatusToVersion(ssl_connection_status) >=
32 SSL_CONNECTION_VERSION_TLS1_3) {
33 return true;
34 }
35
36 // Prior to TLS 1.3, only ECDHE ciphers have groups.
37 const SSL_CIPHER* cipher = SSL_get_cipher_by_value(
38 SSLConnectionStatusToCipherSuite(ssl_connection_status));
39 return cipher && SSL_CIPHER_get_kx_nid(cipher) == NID_kx_ecdhe;
40 }
41
42 } // namespace
43
44 // These values can be bit-wise combined to form the flags field of the
45 // serialized HttpResponseInfo.
46 enum {
47 // The version of the response info used when persisting response info.
48 RESPONSE_INFO_VERSION = 3,
49
50 // The minimum version supported for deserializing response info.
51 RESPONSE_INFO_MINIMUM_VERSION = 3,
52
53 // We reserve up to 8 bits for the version number.
54 RESPONSE_INFO_VERSION_MASK = 0xFF,
55
56 // This bit is set if the response info has a cert at the end.
57 // Version 1 serialized only the end-entity certificate, while subsequent
58 // versions include the available certificate chain.
59 RESPONSE_INFO_HAS_CERT = 1 << 8,
60
61 // This bit was historically set if the response info had a security-bits
62 // field (security strength, in bits, of the SSL connection) at the end.
63 RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9,
64
65 // This bit is set if the response info has a cert status at the end.
66 RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10,
67
68 // This bit is set if the response info has vary header data.
69 RESPONSE_INFO_HAS_VARY_DATA = 1 << 11,
70
71 // This bit is set if the request was cancelled before completion.
72 RESPONSE_INFO_TRUNCATED = 1 << 12,
73
74 // This bit is set if the response was received via SPDY.
75 RESPONSE_INFO_WAS_SPDY = 1 << 13,
76
77 // This bit is set if the request has ALPN negotiated.
78 RESPONSE_INFO_WAS_ALPN = 1 << 14,
79
80 // This bit is set if the request was fetched via an explicit proxy.
81 // This bit is deprecated.
82 RESPONSE_INFO_WAS_PROXY = 1 << 15,
83
84 // This bit is set if the response info has an SSL connection status field.
85 // This contains the ciphersuite used to fetch the resource as well as the
86 // protocol version, compression method and whether SSLv3 fallback was used.
87 RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS = 1 << 16,
88
89 // This bit is set if the response info has protocol version.
90 RESPONSE_INFO_HAS_ALPN_NEGOTIATED_PROTOCOL = 1 << 17,
91
92 // This bit is set if the response info has connection info.
93 RESPONSE_INFO_HAS_CONNECTION_INFO = 1 << 18,
94
95 // This bit is set if the request has http authentication.
96 RESPONSE_INFO_USE_HTTP_AUTHENTICATION = 1 << 19,
97
98 // This bit is set if ssl_info has SCTs.
99 RESPONSE_INFO_HAS_SIGNED_CERTIFICATE_TIMESTAMPS = 1 << 20,
100
101 RESPONSE_INFO_UNUSED_SINCE_PREFETCH = 1 << 21,
102
103 // This bit is set if the response has a key exchange group.
104 RESPONSE_INFO_HAS_KEY_EXCHANGE_GROUP = 1 << 22,
105
106 // This bit is set if ssl_info recorded that PKP was bypassed due to a local
107 // trust anchor.
108 RESPONSE_INFO_PKP_BYPASSED = 1 << 23,
109
110 // This bit is set if stale_revalidate_time is stored.
111 RESPONSE_INFO_HAS_STALENESS = 1 << 24,
112
113 // This bit is set if the response has a peer signature algorithm.
114 RESPONSE_INFO_HAS_PEER_SIGNATURE_ALGORITHM = 1 << 25,
115
116 // This bit is set if the response is a prefetch whose reuse should be
117 // restricted in some way.
118 RESPONSE_INFO_RESTRICTED_PREFETCH = 1 << 26,
119
120 // This bit is set if the response has a nonempty `dns_aliases` entry.
121 RESPONSE_INFO_HAS_DNS_ALIASES = 1 << 27,
122
123 // This bit is now unused. It may be set on existing entries. Previously it
124 // was set for an entry in the single-keyed cache that had been marked
125 // unusable due to the cache transparency checksum not matching.
126 RESPONSE_INFO_UNUSED_WAS_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE = 1 << 28,
127
128 // This bit is set if the response has `encrypted_client_hello` set.
129 RESPONSE_INFO_ENCRYPTED_CLIENT_HELLO = 1 << 29,
130
131 // This bit is set if the response has `browser_run_id` set.
132 RESPONSE_INFO_BROWSER_RUN_ID = 1 << 30,
133
134 // This bit is set if the response has extra bit set.
135 RESPONSE_INFO_HAS_EXTRA_FLAGS = 1 << 31,
136 };
137
138 // These values can be bit-wise combined to form the extra flags field of the
139 // serialized HttpResponseInfo.
140 enum {
141 // This bit is set if the request usd a shared dictionary for decoding its
142 // body.
143 RESPONSE_EXTRA_INFO_DID_USE_SHARED_DICTIONARY = 1,
144
145 // This bit is set if the response has valid `proxy_chain`.
146 RESPONSE_EXTRA_INFO_HAS_PROXY_CHAIN = 1 << 1,
147
148 // This bit is set if the response has original_response_time.
149 RESPONSE_EXTRA_INFO_HAS_ORIGINAL_RESPONSE_TIME = 1 << 2
150 };
151
152 HttpResponseInfo::HttpResponseInfo() = default;
153
154 HttpResponseInfo::HttpResponseInfo(const HttpResponseInfo& rhs) = default;
155
156 HttpResponseInfo::~HttpResponseInfo() = default;
157
158 HttpResponseInfo& HttpResponseInfo::operator=(const HttpResponseInfo& rhs) =
159 default;
160
InitFromPickle(const base::Pickle & pickle,bool * response_truncated)161 bool HttpResponseInfo::InitFromPickle(const base::Pickle& pickle,
162 bool* response_truncated) {
163 base::PickleIterator iter(pickle);
164
165 // Read flags and verify version
166 int flags;
167 int extra_flags = 0;
168 if (!iter.ReadInt(&flags))
169 return false;
170 if (flags & RESPONSE_INFO_HAS_EXTRA_FLAGS) {
171 if (!iter.ReadInt(&extra_flags)) {
172 return false;
173 }
174 }
175 int version = flags & RESPONSE_INFO_VERSION_MASK;
176 if (version < RESPONSE_INFO_MINIMUM_VERSION ||
177 version > RESPONSE_INFO_VERSION) {
178 DLOG(ERROR) << "unexpected response info version: " << version;
179 return false;
180 }
181
182 // Read request-time
183 int64_t time_val;
184 if (!iter.ReadInt64(&time_val))
185 return false;
186 request_time = Time::FromInternalValue(time_val);
187 was_cached = true; // Set status to show cache resurrection.
188
189 // Read response-time
190 if (!iter.ReadInt64(&time_val))
191 return false;
192 response_time = Time::FromInternalValue(time_val);
193
194 // Read original-response-time
195 if ((extra_flags & RESPONSE_EXTRA_INFO_HAS_ORIGINAL_RESPONSE_TIME) != 0) {
196 if (!iter.ReadInt64(&time_val)) {
197 return false;
198 }
199 original_response_time = Time::FromInternalValue(time_val);
200 }
201
202 // Read response-headers
203 headers = base::MakeRefCounted<HttpResponseHeaders>(&iter);
204 if (headers->response_code() == -1)
205 return false;
206
207 // Read ssl-info
208 if (flags & RESPONSE_INFO_HAS_CERT) {
209 ssl_info.cert = X509Certificate::CreateFromPickle(&iter);
210 if (!ssl_info.cert.get())
211 return false;
212 }
213 if (flags & RESPONSE_INFO_HAS_CERT_STATUS) {
214 CertStatus cert_status;
215 if (!iter.ReadUInt32(&cert_status))
216 return false;
217 ssl_info.cert_status = cert_status;
218 }
219 if (flags & RESPONSE_INFO_HAS_SECURITY_BITS) {
220 // The security_bits field has been removed from ssl_info. For backwards
221 // compatibility, we should still read the value out of iter.
222 int security_bits;
223 if (!iter.ReadInt(&security_bits))
224 return false;
225 }
226
227 if (flags & RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS) {
228 int connection_status;
229 if (!iter.ReadInt(&connection_status))
230 return false;
231
232 // SSLv3 is gone, so drop cached entries that were loaded over SSLv3.
233 if (SSLConnectionStatusToVersion(connection_status) ==
234 SSL_CONNECTION_VERSION_SSL3) {
235 return false;
236 }
237 ssl_info.connection_status = connection_status;
238 }
239
240 // Signed Certificate Timestamps are no longer persisted to the cache, so
241 // ignore them when reading them out.
242 if (flags & RESPONSE_INFO_HAS_SIGNED_CERTIFICATE_TIMESTAMPS) {
243 int num_scts;
244 if (!iter.ReadInt(&num_scts))
245 return false;
246 for (int i = 0; i < num_scts; ++i) {
247 scoped_refptr<ct::SignedCertificateTimestamp> sct(
248 ct::SignedCertificateTimestamp::CreateFromPickle(&iter));
249 uint16_t status;
250 if (!sct.get() || !iter.ReadUInt16(&status))
251 return false;
252 }
253 }
254
255 // Read vary-data
256 if (flags & RESPONSE_INFO_HAS_VARY_DATA) {
257 if (!vary_data.InitFromPickle(&iter))
258 return false;
259 }
260
261 // Read socket_address.
262 std::string socket_address_host;
263 if (!iter.ReadString(&socket_address_host))
264 return false;
265 // If the host was written, we always expect the port to follow.
266 uint16_t socket_address_port;
267 if (!iter.ReadUInt16(&socket_address_port))
268 return false;
269
270 IPAddress ip_address;
271 if (ip_address.AssignFromIPLiteral(socket_address_host)) {
272 remote_endpoint = IPEndPoint(ip_address, socket_address_port);
273 } else if (ParseURLHostnameToAddress(socket_address_host, &ip_address)) {
274 remote_endpoint = IPEndPoint(ip_address, socket_address_port);
275 }
276
277 // Read protocol-version.
278 if (flags & RESPONSE_INFO_HAS_ALPN_NEGOTIATED_PROTOCOL) {
279 if (!iter.ReadString(&alpn_negotiated_protocol))
280 return false;
281 }
282
283 // Read connection info.
284 if (flags & RESPONSE_INFO_HAS_CONNECTION_INFO) {
285 int value;
286 if (!iter.ReadInt(&value))
287 return false;
288
289 if (value > static_cast<int>(HttpConnectionInfo::kUNKNOWN) &&
290 value <= static_cast<int>(HttpConnectionInfo::kMaxValue)) {
291 connection_info = static_cast<HttpConnectionInfo>(value);
292 }
293 }
294
295 // Read key_exchange_group
296 if (flags & RESPONSE_INFO_HAS_KEY_EXCHANGE_GROUP) {
297 int key_exchange_group;
298 if (!iter.ReadInt(&key_exchange_group))
299 return false;
300
301 // Historically, the key_exchange_group field was key_exchange_info which
302 // conflated a number of different values based on the cipher suite, so some
303 // values must be discarded. See https://crbug.com/639421.
304 if (KeyExchangeGroupIsValid(ssl_info.connection_status))
305 ssl_info.key_exchange_group = key_exchange_group;
306 }
307
308 // Read staleness time.
309 if (flags & RESPONSE_INFO_HAS_STALENESS) {
310 if (!iter.ReadInt64(&time_val))
311 return false;
312 stale_revalidate_timeout = base::Time() + base::Microseconds(time_val);
313 }
314
315 was_fetched_via_spdy = (flags & RESPONSE_INFO_WAS_SPDY) != 0;
316
317 was_alpn_negotiated = (flags & RESPONSE_INFO_WAS_ALPN) != 0;
318
319 *response_truncated = (flags & RESPONSE_INFO_TRUNCATED) != 0;
320
321 did_use_http_auth = (flags & RESPONSE_INFO_USE_HTTP_AUTHENTICATION) != 0;
322
323 unused_since_prefetch = (flags & RESPONSE_INFO_UNUSED_SINCE_PREFETCH) != 0;
324
325 restricted_prefetch = (flags & RESPONSE_INFO_RESTRICTED_PREFETCH) != 0;
326
327 // RESPONSE_INFO_UNUSED_WAS_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE is unused.
328
329 ssl_info.pkp_bypassed = (flags & RESPONSE_INFO_PKP_BYPASSED) != 0;
330
331 // Read peer_signature_algorithm.
332 if (flags & RESPONSE_INFO_HAS_PEER_SIGNATURE_ALGORITHM) {
333 int peer_signature_algorithm;
334 if (!iter.ReadInt(&peer_signature_algorithm) ||
335 !base::IsValueInRangeForNumericType<uint16_t>(
336 peer_signature_algorithm)) {
337 return false;
338 }
339 ssl_info.peer_signature_algorithm =
340 base::checked_cast<uint16_t>(peer_signature_algorithm);
341 }
342
343 // Read DNS aliases.
344 if (flags & RESPONSE_INFO_HAS_DNS_ALIASES) {
345 int num_aliases;
346 if (!iter.ReadInt(&num_aliases))
347 return false;
348
349 std::string alias;
350 for (int i = 0; i < num_aliases; i++) {
351 if (!iter.ReadString(&alias))
352 return false;
353 dns_aliases.insert(alias);
354 }
355 }
356
357 ssl_info.encrypted_client_hello =
358 (flags & RESPONSE_INFO_ENCRYPTED_CLIENT_HELLO) != 0;
359
360 // Read browser_run_id.
361 if (flags & RESPONSE_INFO_BROWSER_RUN_ID) {
362 int64_t id;
363 if (!iter.ReadInt64(&id))
364 return false;
365 browser_run_id = std::make_optional(id);
366 }
367
368 did_use_shared_dictionary =
369 (extra_flags & RESPONSE_EXTRA_INFO_DID_USE_SHARED_DICTIONARY) != 0;
370
371 if (extra_flags & RESPONSE_EXTRA_INFO_HAS_PROXY_CHAIN) {
372 if (!proxy_chain.InitFromPickle(&iter)) {
373 return false;
374 }
375 }
376
377 return true;
378 }
379
Persist(base::Pickle * pickle,bool skip_transient_headers,bool response_truncated) const380 void HttpResponseInfo::Persist(base::Pickle* pickle,
381 bool skip_transient_headers,
382 bool response_truncated) const {
383 int flags = RESPONSE_INFO_VERSION;
384 int extra_flags = 0;
385 if (ssl_info.is_valid()) {
386 flags |= RESPONSE_INFO_HAS_CERT;
387 flags |= RESPONSE_INFO_HAS_CERT_STATUS;
388 if (ssl_info.key_exchange_group != 0)
389 flags |= RESPONSE_INFO_HAS_KEY_EXCHANGE_GROUP;
390 if (ssl_info.connection_status != 0)
391 flags |= RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS;
392 if (ssl_info.peer_signature_algorithm != 0)
393 flags |= RESPONSE_INFO_HAS_PEER_SIGNATURE_ALGORITHM;
394 }
395 if (vary_data.is_valid())
396 flags |= RESPONSE_INFO_HAS_VARY_DATA;
397 if (response_truncated)
398 flags |= RESPONSE_INFO_TRUNCATED;
399 if (was_fetched_via_spdy)
400 flags |= RESPONSE_INFO_WAS_SPDY;
401 if (was_alpn_negotiated) {
402 flags |= RESPONSE_INFO_WAS_ALPN;
403 flags |= RESPONSE_INFO_HAS_ALPN_NEGOTIATED_PROTOCOL;
404 }
405 if (connection_info != HttpConnectionInfo::kUNKNOWN) {
406 flags |= RESPONSE_INFO_HAS_CONNECTION_INFO;
407 }
408 if (did_use_http_auth)
409 flags |= RESPONSE_INFO_USE_HTTP_AUTHENTICATION;
410 if (unused_since_prefetch)
411 flags |= RESPONSE_INFO_UNUSED_SINCE_PREFETCH;
412 if (restricted_prefetch)
413 flags |= RESPONSE_INFO_RESTRICTED_PREFETCH;
414 // RESPONSE_INFO_UNUSED_WAS_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE is not used.
415 if (ssl_info.pkp_bypassed)
416 flags |= RESPONSE_INFO_PKP_BYPASSED;
417 if (!stale_revalidate_timeout.is_null())
418 flags |= RESPONSE_INFO_HAS_STALENESS;
419 if (!dns_aliases.empty())
420 flags |= RESPONSE_INFO_HAS_DNS_ALIASES;
421 if (ssl_info.encrypted_client_hello)
422 flags |= RESPONSE_INFO_ENCRYPTED_CLIENT_HELLO;
423 if (browser_run_id.has_value())
424 flags |= RESPONSE_INFO_BROWSER_RUN_ID;
425
426 if (did_use_shared_dictionary) {
427 extra_flags |= RESPONSE_EXTRA_INFO_DID_USE_SHARED_DICTIONARY;
428 }
429
430 if (proxy_chain.IsValid()) {
431 extra_flags |= RESPONSE_EXTRA_INFO_HAS_PROXY_CHAIN;
432 }
433
434 extra_flags |= RESPONSE_EXTRA_INFO_HAS_ORIGINAL_RESPONSE_TIME;
435 flags |= RESPONSE_INFO_HAS_EXTRA_FLAGS;
436
437 pickle->WriteInt(flags);
438 pickle->WriteInt(extra_flags);
439 pickle->WriteInt64(request_time.ToInternalValue());
440 pickle->WriteInt64(response_time.ToInternalValue());
441 pickle->WriteInt64(original_response_time.ToInternalValue());
442
443 HttpResponseHeaders::PersistOptions persist_options =
444 HttpResponseHeaders::PERSIST_RAW;
445
446 if (skip_transient_headers) {
447 persist_options = HttpResponseHeaders::PERSIST_SANS_COOKIES |
448 HttpResponseHeaders::PERSIST_SANS_CHALLENGES |
449 HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP |
450 HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
451 HttpResponseHeaders::PERSIST_SANS_RANGES |
452 HttpResponseHeaders::PERSIST_SANS_SECURITY_STATE;
453 }
454
455 headers->Persist(pickle, persist_options);
456
457 if (ssl_info.is_valid()) {
458 ssl_info.cert->Persist(pickle);
459 pickle->WriteUInt32(ssl_info.cert_status);
460 if (ssl_info.connection_status != 0)
461 pickle->WriteInt(ssl_info.connection_status);
462 }
463
464 if (vary_data.is_valid())
465 vary_data.Persist(pickle);
466
467 pickle->WriteString(remote_endpoint.ToStringWithoutPort());
468 pickle->WriteUInt16(remote_endpoint.port());
469
470 if (was_alpn_negotiated)
471 pickle->WriteString(alpn_negotiated_protocol);
472
473 if (connection_info != HttpConnectionInfo::kUNKNOWN) {
474 pickle->WriteInt(static_cast<int>(connection_info));
475 }
476
477 if (ssl_info.is_valid() && ssl_info.key_exchange_group != 0)
478 pickle->WriteInt(ssl_info.key_exchange_group);
479
480 if (flags & RESPONSE_INFO_HAS_STALENESS) {
481 pickle->WriteInt64(
482 (stale_revalidate_timeout - base::Time()).InMicroseconds());
483 }
484
485 if (ssl_info.is_valid() && ssl_info.peer_signature_algorithm != 0)
486 pickle->WriteInt(ssl_info.peer_signature_algorithm);
487
488 if (!dns_aliases.empty()) {
489 pickle->WriteInt(dns_aliases.size());
490 for (const auto& alias : dns_aliases)
491 pickle->WriteString(alias);
492 }
493
494 if (browser_run_id.has_value()) {
495 pickle->WriteInt64(browser_run_id.value());
496 }
497
498 if (proxy_chain.IsValid()) {
499 proxy_chain.Persist(pickle);
500 }
501 }
502
DidUseQuic() const503 bool HttpResponseInfo::DidUseQuic() const {
504 switch (connection_info) {
505 case HttpConnectionInfo::kUNKNOWN:
506 case HttpConnectionInfo::kHTTP1_1:
507 case HttpConnectionInfo::kDEPRECATED_SPDY2:
508 case HttpConnectionInfo::kDEPRECATED_SPDY3:
509 case HttpConnectionInfo::kHTTP2:
510 case HttpConnectionInfo::kDEPRECATED_HTTP2_14:
511 case HttpConnectionInfo::kDEPRECATED_HTTP2_15:
512 case HttpConnectionInfo::kHTTP0_9:
513 case HttpConnectionInfo::kHTTP1_0:
514 return false;
515 case HttpConnectionInfo::kQUIC_UNKNOWN_VERSION:
516 case HttpConnectionInfo::kQUIC_32:
517 case HttpConnectionInfo::kQUIC_33:
518 case HttpConnectionInfo::kQUIC_34:
519 case HttpConnectionInfo::kQUIC_35:
520 case HttpConnectionInfo::kQUIC_36:
521 case HttpConnectionInfo::kQUIC_37:
522 case HttpConnectionInfo::kQUIC_38:
523 case HttpConnectionInfo::kQUIC_39:
524 case HttpConnectionInfo::kQUIC_40:
525 case HttpConnectionInfo::kQUIC_41:
526 case HttpConnectionInfo::kQUIC_42:
527 case HttpConnectionInfo::kQUIC_43:
528 case HttpConnectionInfo::kQUIC_44:
529 case HttpConnectionInfo::kQUIC_45:
530 case HttpConnectionInfo::kQUIC_46:
531 case HttpConnectionInfo::kQUIC_47:
532 case HttpConnectionInfo::kQUIC_Q048:
533 case HttpConnectionInfo::kQUIC_T048:
534 case HttpConnectionInfo::kQUIC_Q049:
535 case HttpConnectionInfo::kQUIC_T049:
536 case HttpConnectionInfo::kQUIC_Q050:
537 case HttpConnectionInfo::kQUIC_T050:
538 case HttpConnectionInfo::kQUIC_Q099:
539 case HttpConnectionInfo::kQUIC_T099:
540 case HttpConnectionInfo::kQUIC_999:
541 case HttpConnectionInfo::kQUIC_DRAFT_25:
542 case HttpConnectionInfo::kQUIC_DRAFT_27:
543 case HttpConnectionInfo::kQUIC_DRAFT_28:
544 case HttpConnectionInfo::kQUIC_DRAFT_29:
545 case HttpConnectionInfo::kQUIC_T051:
546 case HttpConnectionInfo::kQUIC_RFC_V1:
547 case HttpConnectionInfo::kDEPRECATED_QUIC_2_DRAFT_1:
548 case HttpConnectionInfo::kQUIC_2_DRAFT_8:
549 return true;
550 }
551 }
552
WasFetchedViaProxy() const553 bool HttpResponseInfo::WasFetchedViaProxy() const {
554 return proxy_chain.IsValid() && !proxy_chain.is_direct();
555 }
556
557 } // namespace net
558