• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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/dns/host_resolver_internal_result.h"
6 
7 #include <map>
8 #include <memory>
9 #include <ostream>
10 #include <string>
11 #include <utility>
12 #include <vector>
13 
14 #include "base/check_op.h"
15 #include "base/json/values_util.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/strings/string_piece.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "net/base/connection_endpoint_metadata.h"
21 #include "net/base/host_port_pair.h"
22 #include "net/base/ip_endpoint.h"
23 #include "net/base/net_errors.h"
24 #include "net/dns/https_record_rdata.h"
25 #include "net/dns/public/dns_query_type.h"
26 #include "third_party/abseil-cpp/absl/types/optional.h"
27 #include "url/url_canon.h"
28 #include "url/url_canon_stdstring.h"
29 
30 namespace net {
31 
32 namespace {
33 
34 // base::Value keys
35 constexpr base::StringPiece kValueDomainNameKey = "domain_name";
36 constexpr base::StringPiece kValueQueryTypeKey = "query_type";
37 constexpr base::StringPiece kValueTypeKey = "type";
38 constexpr base::StringPiece kValueSourceKey = "source";
39 constexpr base::StringPiece kValueTimedExpirationKey = "timed_expiration";
40 constexpr base::StringPiece kValueEndpointsKey = "endpoints";
41 constexpr base::StringPiece kValueStringsKey = "strings";
42 constexpr base::StringPiece kValueHostsKey = "hosts";
43 constexpr base::StringPiece kValueMetadatasKey = "metadatas";
44 constexpr base::StringPiece kValueMetadataWeightKey = "metadata_weight";
45 constexpr base::StringPiece kValueMetadataValueKey = "metadata_value";
46 constexpr base::StringPiece kValueErrorKey = "error";
47 constexpr base::StringPiece kValueAliasTargetKey = "alias_target";
48 
49 // Returns `domain_name` as-is if it could not be canonicalized.
MaybeCanonicalizeName(std::string domain_name)50 std::string MaybeCanonicalizeName(std::string domain_name) {
51   std::string canonicalized;
52   url::StdStringCanonOutput output(&canonicalized);
53   url::CanonHostInfo host_info;
54 
55   url::CanonicalizeHostVerbose(domain_name.data(),
56                                url::Component(0, domain_name.size()), &output,
57                                &host_info);
58 
59   if (host_info.family == url::CanonHostInfo::Family::NEUTRAL) {
60     output.Complete();
61     return canonicalized;
62   } else {
63     return domain_name;
64   }
65 }
66 
EndpointMetadataPairToValue(const std::pair<HttpsRecordPriority,ConnectionEndpointMetadata> & pair)67 base::Value EndpointMetadataPairToValue(
68     const std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>& pair) {
69   base::Value::Dict dictionary;
70   dictionary.Set(kValueMetadataWeightKey, pair.first);
71   dictionary.Set(kValueMetadataValueKey, pair.second.ToValue());
72   return base::Value(std::move(dictionary));
73 }
74 
75 absl::optional<std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>>
EndpointMetadataPairFromValue(const base::Value & value)76 EndpointMetadataPairFromValue(const base::Value& value) {
77   const base::Value::Dict* dict = value.GetIfDict();
78   if (!dict)
79     return absl::nullopt;
80 
81   absl::optional<int> weight = dict->FindInt(kValueMetadataWeightKey);
82   if (!weight || !base::IsValueInRangeForNumericType<HttpsRecordPriority>(
83                      weight.value())) {
84     return absl::nullopt;
85   }
86 
87   const base::Value* metadata_value = dict->Find(kValueMetadataValueKey);
88   if (!metadata_value)
89     return absl::nullopt;
90   absl::optional<ConnectionEndpointMetadata> metadata =
91       ConnectionEndpointMetadata::FromValue(*metadata_value);
92   if (!metadata)
93     return absl::nullopt;
94 
95   return std::make_pair(base::checked_cast<HttpsRecordPriority>(weight.value()),
96                         std::move(metadata).value());
97 }
98 
QueryTypeFromValue(const base::Value & value)99 absl::optional<DnsQueryType> QueryTypeFromValue(const base::Value& value) {
100   const std::string* query_type_string = value.GetIfString();
101   if (!query_type_string)
102     return absl::nullopt;
103   const auto* query_type_it =
104       base::ranges::find(kDnsQueryTypes, *query_type_string,
105                          &decltype(kDnsQueryTypes)::value_type::second);
106   if (query_type_it == kDnsQueryTypes.end())
107     return absl::nullopt;
108 
109   return query_type_it->first;
110 }
111 
TypeToValue(HostResolverInternalResult::Type type)112 base::Value TypeToValue(HostResolverInternalResult::Type type) {
113   switch (type) {
114     case HostResolverInternalResult::Type::kData:
115       return base::Value("data");
116     case HostResolverInternalResult::Type::kMetadata:
117       return base::Value("metadata");
118     case HostResolverInternalResult::Type::kError:
119       return base::Value("error");
120     case HostResolverInternalResult::Type::kAlias:
121       return base::Value("alias");
122   }
123 }
124 
TypeFromValue(const base::Value & value)125 absl::optional<HostResolverInternalResult::Type> TypeFromValue(
126     const base::Value& value) {
127   const std::string* string = value.GetIfString();
128   if (!string)
129     return absl::nullopt;
130 
131   if (*string == "data") {
132     return HostResolverInternalResult::Type::kData;
133   } else if (*string == "metadata") {
134     return HostResolverInternalResult::Type::kMetadata;
135   } else if (*string == "error") {
136     return HostResolverInternalResult::Type::kError;
137   } else if (*string == "alias") {
138     return HostResolverInternalResult::Type::kAlias;
139   } else {
140     return absl::nullopt;
141   }
142 }
143 
SourceToValue(HostResolverInternalResult::Source source)144 base::Value SourceToValue(HostResolverInternalResult::Source source) {
145   switch (source) {
146     case HostResolverInternalResult::Source::kDns:
147       return base::Value("dns");
148     case HostResolverInternalResult::Source::kHosts:
149       return base::Value("hosts");
150     case HostResolverInternalResult::Source::kUnknown:
151       return base::Value("unknown");
152   }
153 }
154 
SourceFromValue(const base::Value & value)155 absl::optional<HostResolverInternalResult::Source> SourceFromValue(
156     const base::Value& value) {
157   const std::string* string = value.GetIfString();
158   if (!string)
159     return absl::nullopt;
160 
161   if (*string == "dns") {
162     return HostResolverInternalResult::Source::kDns;
163   } else if (*string == "hosts") {
164     return HostResolverInternalResult::Source::kHosts;
165   } else if (*string == "unknown") {
166     return HostResolverInternalResult::Source::kUnknown;
167   } else {
168     return absl::nullopt;
169   }
170 }
171 
172 }  // namespace
173 
174 // static
175 std::unique_ptr<HostResolverInternalResult>
FromValue(const base::Value & value)176 HostResolverInternalResult::FromValue(const base::Value& value) {
177   const base::Value::Dict* dict = value.GetIfDict();
178   if (!dict)
179     return nullptr;
180 
181   const base::Value* type_value = dict->Find(kValueTypeKey);
182   if (!type_value)
183     return nullptr;
184   absl::optional<Type> type = TypeFromValue(*type_value);
185   if (!type.has_value())
186     return nullptr;
187 
188   switch (type.value()) {
189     case Type::kData:
190       return HostResolverInternalDataResult::FromValue(value);
191     case Type::kMetadata:
192       return HostResolverInternalMetadataResult::FromValue(value);
193     case Type::kError:
194       return HostResolverInternalErrorResult::FromValue(value);
195     case Type::kAlias:
196       return HostResolverInternalAliasResult::FromValue(value);
197   }
198 }
199 
AsData() const200 const HostResolverInternalDataResult& HostResolverInternalResult::AsData()
201     const {
202   CHECK_EQ(type_, Type::kData);
203   return *static_cast<const HostResolverInternalDataResult*>(this);
204 }
205 
206 const HostResolverInternalMetadataResult&
AsMetadata() const207 HostResolverInternalResult::AsMetadata() const {
208   CHECK_EQ(type_, Type::kMetadata);
209   return *static_cast<const HostResolverInternalMetadataResult*>(this);
210 }
211 
AsError() const212 const HostResolverInternalErrorResult& HostResolverInternalResult::AsError()
213     const {
214   CHECK_EQ(type_, Type::kError);
215   return *static_cast<const HostResolverInternalErrorResult*>(this);
216 }
217 
AsAlias() const218 const HostResolverInternalAliasResult& HostResolverInternalResult::AsAlias()
219     const {
220   CHECK_EQ(type_, Type::kAlias);
221   return *static_cast<const HostResolverInternalAliasResult*>(this);
222 }
223 
HostResolverInternalResult(std::string domain_name,DnsQueryType query_type,absl::optional<base::TimeTicks> expiration,absl::optional<base::Time> timed_expiration,Type type,Source source)224 HostResolverInternalResult::HostResolverInternalResult(
225     std::string domain_name,
226     DnsQueryType query_type,
227     absl::optional<base::TimeTicks> expiration,
228     absl::optional<base::Time> timed_expiration,
229     Type type,
230     Source source)
231     : domain_name_(MaybeCanonicalizeName(std::move(domain_name))),
232       query_type_(query_type),
233       type_(type),
234       source_(source),
235       expiration_(expiration),
236       timed_expiration_(timed_expiration) {
237   DCHECK(!domain_name_.empty());
238   // If `expiration` has a value, `timed_expiration` must too.
239   DCHECK(!expiration_.has_value() || timed_expiration.has_value());
240 }
241 
HostResolverInternalResult(const base::Value::Dict & dict)242 HostResolverInternalResult::HostResolverInternalResult(
243     const base::Value::Dict& dict)
244     : domain_name_(*dict.FindString(kValueDomainNameKey)),
245       query_type_(QueryTypeFromValue(*dict.Find(kValueQueryTypeKey)).value()),
246       type_(TypeFromValue(*dict.Find(kValueTypeKey)).value()),
247       source_(SourceFromValue(*dict.Find(kValueSourceKey)).value()),
248       timed_expiration_(
249           dict.contains(kValueTimedExpirationKey)
250               ? base::ValueToTime(*dict.Find(kValueTimedExpirationKey))
251               : absl::optional<base::Time>()) {}
252 
253 // static
ValidateValueBaseDict(const base::Value::Dict & dict,bool require_timed_expiration)254 bool HostResolverInternalResult::ValidateValueBaseDict(
255     const base::Value::Dict& dict,
256     bool require_timed_expiration) {
257   const std::string* domain_name = dict.FindString(kValueDomainNameKey);
258   if (!domain_name)
259     return false;
260 
261   const std::string* query_type_string = dict.FindString(kValueQueryTypeKey);
262   if (!query_type_string)
263     return false;
264   const auto* query_type_it =
265       base::ranges::find(kDnsQueryTypes, *query_type_string,
266                          &decltype(kDnsQueryTypes)::value_type::second);
267   if (query_type_it == kDnsQueryTypes.end())
268     return false;
269 
270   const base::Value* type_value = dict.Find(kValueTypeKey);
271   if (!type_value)
272     return false;
273   absl::optional<Type> type = TypeFromValue(*type_value);
274   if (!type.has_value())
275     return false;
276 
277   const base::Value* source_value = dict.Find(kValueSourceKey);
278   if (!source_value)
279     return false;
280   absl::optional<Source> source = SourceFromValue(*source_value);
281   if (!source.has_value())
282     return false;
283 
284   absl::optional<base::Time> timed_expiration;
285   const base::Value* timed_expiration_value =
286       dict.Find(kValueTimedExpirationKey);
287   if (require_timed_expiration && !timed_expiration_value)
288     return false;
289   if (timed_expiration_value) {
290     timed_expiration = base::ValueToTime(timed_expiration_value);
291     if (!timed_expiration.has_value())
292       return false;
293   }
294 
295   return true;
296 }
297 
ToValueBaseDict() const298 base::Value::Dict HostResolverInternalResult::ToValueBaseDict() const {
299   base::Value::Dict dict;
300 
301   dict.Set(kValueDomainNameKey, domain_name_);
302   dict.Set(kValueQueryTypeKey, kDnsQueryTypes.at(query_type_));
303   dict.Set(kValueTypeKey, TypeToValue(type_));
304   dict.Set(kValueSourceKey, SourceToValue(source_));
305 
306   // `expiration_` is not serialized because it is TimeTicks.
307 
308   if (timed_expiration_.has_value()) {
309     dict.Set(kValueTimedExpirationKey,
310              base::TimeToValue(timed_expiration_.value()));
311   }
312 
313   return dict;
314 }
315 
316 // static
317 std::unique_ptr<HostResolverInternalDataResult>
FromValue(const base::Value & value)318 HostResolverInternalDataResult::FromValue(const base::Value& value) {
319   const base::Value::Dict* dict = value.GetIfDict();
320   if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true))
321     return nullptr;
322 
323   const base::Value::List* endpoint_values = dict->FindList(kValueEndpointsKey);
324   if (!endpoint_values)
325     return nullptr;
326 
327   std::vector<IPEndPoint> endpoints;
328   endpoints.reserve(endpoint_values->size());
329   for (const base::Value& endpoint_value : *endpoint_values) {
330     absl::optional<IPEndPoint> endpoint = IPEndPoint::FromValue(endpoint_value);
331     if (!endpoint.has_value())
332       return nullptr;
333 
334     endpoints.push_back(std::move(endpoint).value());
335   }
336 
337   const base::Value::List* string_values = dict->FindList(kValueStringsKey);
338   if (!string_values)
339     return nullptr;
340 
341   std::vector<std::string> strings;
342   strings.reserve(string_values->size());
343   for (const base::Value& string_value : *string_values) {
344     const std::string* string = string_value.GetIfString();
345     if (!string)
346       return nullptr;
347 
348     strings.push_back(*string);
349   }
350 
351   const base::Value::List* host_values = dict->FindList(kValueHostsKey);
352   if (!host_values)
353     return nullptr;
354 
355   std::vector<HostPortPair> hosts;
356   hosts.reserve(host_values->size());
357   for (const base::Value& host_value : *host_values) {
358     absl::optional<HostPortPair> host = HostPortPair::FromValue(host_value);
359     if (!host.has_value())
360       return nullptr;
361 
362     hosts.push_back(std::move(host).value());
363   }
364 
365   // WrapUnique due to private constructor.
366   return base::WrapUnique(new HostResolverInternalDataResult(
367       *dict, std::move(endpoints), std::move(strings), std::move(hosts)));
368 }
369 
HostResolverInternalDataResult(std::string domain_name,DnsQueryType query_type,absl::optional<base::TimeTicks> expiration,base::Time timed_expiration,Source source,std::vector<IPEndPoint> endpoints,std::vector<std::string> strings,std::vector<HostPortPair> hosts)370 HostResolverInternalDataResult::HostResolverInternalDataResult(
371     std::string domain_name,
372     DnsQueryType query_type,
373     absl::optional<base::TimeTicks> expiration,
374     base::Time timed_expiration,
375     Source source,
376     std::vector<IPEndPoint> endpoints,
377     std::vector<std::string> strings,
378     std::vector<HostPortPair> hosts)
379     : HostResolverInternalResult(std::move(domain_name),
380                                  query_type,
381                                  expiration,
382                                  timed_expiration,
383                                  Type::kData,
384                                  source),
385       endpoints_(std::move(endpoints)),
386       strings_(std::move(strings)),
387       hosts_(std::move(hosts)) {
388   DCHECK(!endpoints_.empty() || !strings_.empty() || !hosts_.empty());
389 }
390 
391 HostResolverInternalDataResult::~HostResolverInternalDataResult() = default;
392 
ToValue() const393 base::Value HostResolverInternalDataResult::ToValue() const {
394   base::Value::Dict dict = ToValueBaseDict();
395 
396   base::Value::List endpoints_list;
397   endpoints_list.reserve(endpoints_.size());
398   for (IPEndPoint endpoint : endpoints_) {
399     endpoints_list.Append(endpoint.ToValue());
400   }
401   dict.Set(kValueEndpointsKey, std::move(endpoints_list));
402 
403   base::Value::List strings_list;
404   strings_list.reserve(strings_.size());
405   for (const std::string& string : strings_) {
406     strings_list.Append(string);
407   }
408   dict.Set(kValueStringsKey, std::move(strings_list));
409 
410   base::Value::List hosts_list;
411   hosts_list.reserve(hosts_.size());
412   for (const HostPortPair& host : hosts_) {
413     hosts_list.Append(host.ToValue());
414   }
415   dict.Set(kValueHostsKey, std::move(hosts_list));
416 
417   return base::Value(std::move(dict));
418 }
419 
HostResolverInternalDataResult(const base::Value::Dict & dict,std::vector<IPEndPoint> endpoints,std::vector<std::string> strings,std::vector<HostPortPair> hosts)420 HostResolverInternalDataResult::HostResolverInternalDataResult(
421     const base::Value::Dict& dict,
422     std::vector<IPEndPoint> endpoints,
423     std::vector<std::string> strings,
424     std::vector<HostPortPair> hosts)
425     : HostResolverInternalResult(dict),
426       endpoints_(std::move(endpoints)),
427       strings_(std::move(strings)),
428       hosts_(std::move(hosts)) {}
429 
430 // static
431 std::unique_ptr<HostResolverInternalMetadataResult>
FromValue(const base::Value & value)432 HostResolverInternalMetadataResult::FromValue(const base::Value& value) {
433   const base::Value::Dict* dict = value.GetIfDict();
434   if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true))
435     return nullptr;
436 
437   const base::Value::List* metadata_values = dict->FindList(kValueMetadatasKey);
438   if (!metadata_values)
439     return nullptr;
440 
441   std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas;
442   for (const base::Value& metadata_value : *metadata_values) {
443     absl::optional<std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>>
444         metadata = EndpointMetadataPairFromValue(metadata_value);
445     if (!metadata.has_value())
446       return nullptr;
447     metadatas.insert(std::move(metadata).value());
448   }
449 
450   // WrapUnique due to private constructor.
451   return base::WrapUnique(
452       new HostResolverInternalMetadataResult(*dict, std::move(metadatas)));
453 }
454 
HostResolverInternalMetadataResult(std::string domain_name,DnsQueryType query_type,absl::optional<base::TimeTicks> expiration,base::Time timed_expiration,Source source,std::multimap<HttpsRecordPriority,ConnectionEndpointMetadata> metadatas)455 HostResolverInternalMetadataResult::HostResolverInternalMetadataResult(
456     std::string domain_name,
457     DnsQueryType query_type,
458     absl::optional<base::TimeTicks> expiration,
459     base::Time timed_expiration,
460     Source source,
461     std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas)
462     : HostResolverInternalResult(std::move(domain_name),
463                                  query_type,
464                                  expiration,
465                                  timed_expiration,
466                                  Type::kMetadata,
467                                  source),
468       metadatas_(std::move(metadatas)) {}
469 
470 HostResolverInternalMetadataResult::~HostResolverInternalMetadataResult() =
471     default;
472 
ToValue() const473 base::Value HostResolverInternalMetadataResult::ToValue() const {
474   base::Value::Dict dict = ToValueBaseDict();
475 
476   base::Value::List metadatas_list;
477   metadatas_list.reserve(metadatas_.size());
478   for (const std::pair<const HttpsRecordPriority, ConnectionEndpointMetadata>&
479            metadata_pair : metadatas_) {
480     metadatas_list.Append(EndpointMetadataPairToValue(metadata_pair));
481   }
482   dict.Set(kValueMetadatasKey, std::move(metadatas_list));
483 
484   return base::Value(std::move(dict));
485 }
486 
HostResolverInternalMetadataResult(const base::Value::Dict & dict,std::multimap<HttpsRecordPriority,ConnectionEndpointMetadata> metadatas)487 HostResolverInternalMetadataResult::HostResolverInternalMetadataResult(
488     const base::Value::Dict& dict,
489     std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas)
490     : HostResolverInternalResult(dict), metadatas_(std::move(metadatas)) {}
491 
492 // static
493 std::unique_ptr<HostResolverInternalErrorResult>
FromValue(const base::Value & value)494 HostResolverInternalErrorResult::FromValue(const base::Value& value) {
495   const base::Value::Dict* dict = value.GetIfDict();
496   if (!dict ||
497       !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/false)) {
498     return nullptr;
499   }
500 
501   absl::optional<int> error = dict->FindInt(kValueErrorKey);
502   if (!error.has_value())
503     return nullptr;
504 
505   // WrapUnique due to private constructor.
506   return base::WrapUnique(
507       new HostResolverInternalErrorResult(*dict, error.value()));
508 }
509 
HostResolverInternalErrorResult(std::string domain_name,DnsQueryType query_type,absl::optional<base::TimeTicks> expiration,absl::optional<base::Time> timed_expiration,Source source,int error)510 HostResolverInternalErrorResult::HostResolverInternalErrorResult(
511     std::string domain_name,
512     DnsQueryType query_type,
513     absl::optional<base::TimeTicks> expiration,
514     absl::optional<base::Time> timed_expiration,
515     Source source,
516     int error)
517     : HostResolverInternalResult(std::move(domain_name),
518                                  query_type,
519                                  expiration,
520                                  timed_expiration,
521                                  Type::kError,
522                                  source),
523       error_(error) {}
524 
ToValue() const525 base::Value HostResolverInternalErrorResult::ToValue() const {
526   base::Value::Dict dict = ToValueBaseDict();
527 
528   dict.Set(kValueErrorKey, error_);
529 
530   return base::Value(std::move(dict));
531 }
532 
HostResolverInternalErrorResult(const base::Value::Dict & dict,int error)533 HostResolverInternalErrorResult::HostResolverInternalErrorResult(
534     const base::Value::Dict& dict,
535     int error)
536     : HostResolverInternalResult(dict), error_(error) {
537   DCHECK_NE(error_, OK);
538 }
539 
540 // static
541 std::unique_ptr<HostResolverInternalAliasResult>
FromValue(const base::Value & value)542 HostResolverInternalAliasResult::FromValue(const base::Value& value) {
543   const base::Value::Dict* dict = value.GetIfDict();
544   if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true))
545     return nullptr;
546 
547   const std::string* target = dict->FindString(kValueAliasTargetKey);
548   if (!target)
549     return nullptr;
550 
551   // WrapUnique due to private constructor.
552   return base::WrapUnique(new HostResolverInternalAliasResult(*dict, *target));
553 }
554 
HostResolverInternalAliasResult(std::string domain_name,DnsQueryType query_type,absl::optional<base::TimeTicks> expiration,base::Time timed_expiration,Source source,std::string alias_target)555 HostResolverInternalAliasResult::HostResolverInternalAliasResult(
556     std::string domain_name,
557     DnsQueryType query_type,
558     absl::optional<base::TimeTicks> expiration,
559     base::Time timed_expiration,
560     Source source,
561     std::string alias_target)
562     : HostResolverInternalResult(std::move(domain_name),
563                                  query_type,
564                                  expiration,
565                                  timed_expiration,
566                                  Type::kAlias,
567                                  source),
568       alias_target_(MaybeCanonicalizeName(std::move(alias_target))) {
569   DCHECK(!alias_target_.empty());
570 }
571 
ToValue() const572 base::Value HostResolverInternalAliasResult::ToValue() const {
573   base::Value::Dict dict = ToValueBaseDict();
574 
575   dict.Set(kValueAliasTargetKey, alias_target_);
576 
577   return base::Value(std::move(dict));
578 }
579 
HostResolverInternalAliasResult(const base::Value::Dict & dict,std::string alias_target)580 HostResolverInternalAliasResult::HostResolverInternalAliasResult(
581     const base::Value::Dict& dict,
582     std::string alias_target)
583     : HostResolverInternalResult(dict),
584       alias_target_(MaybeCanonicalizeName(std::move(alias_target))) {}
585 
586 }  // namespace net
587