LCOV - code coverage report
Current view: top level - src/evo - netinfo.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 285 376 75.8 %
Date: 2026-06-25 07:23:51 Functions: 53 81 65.4 %

          Line data    Source code
       1             : // Copyright (c) 2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <evo/netinfo.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <evo/providertx.h>
       9             : #include <netbase.h>
      10             : #include <span.h>
      11             : #include <util/check.h>
      12             : #include <util/string.h>
      13             : #include <util/system.h>
      14             : 
      15             : #include <univalue.h>
      16             : 
      17             : namespace {
      18             : static std::unique_ptr<const CChainParams> g_main_params{nullptr};
      19             : static std::once_flag g_main_params_flag;
      20             : 
      21             : /** Maximum length of a label in a domain per RFC 1035 */
      22             : static constexpr uint8_t DOMAIN_LABEL_MAX_LEN{63};
      23             : /** Maximum possible length of a ASCII FQDN */
      24             : static constexpr uint8_t DOMAIN_MAX_LEN{253};
      25             : /** Minimum length of a FQDN */
      26             : static constexpr uint8_t DOMAIN_MIN_LEN{3};
      27             : 
      28             : static constexpr std::string_view SAFE_CHARS_ALPHA{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
      29             : static constexpr std::string_view SAFE_CHARS_IPV4{"1234567890."};
      30             : static constexpr std::string_view SAFE_CHARS_IPV4_6{"abcdefABCDEF1234567890.:[]"};
      31             : static constexpr std::string_view SAFE_CHARS_RFC1035{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"};
      32             : static constexpr std::array<std::string_view, 13> TLDS_BAD{
      33             :     // ICANN resolution 2018.02.04.12
      34             :     ".mail",
      35             :     // Infrastructure TLD
      36             :     ".arpa",
      37             :     // RFC 6761
      38             :     ".example", ".invalid", ".localhost", ".test",
      39             :     // RFC 6762
      40             :     ".local",
      41             :     // RFC 6762, Appendix G
      42             :     ".corp", ".home", ".internal", ".intranet", ".lan", ".private",
      43             : };
      44             : static constexpr std::array<std::string_view, 2> TLDS_PRIVACY{".i2p", ".onion"};
      45             : 
      46         213 : bool MatchCharsFilter(std::string_view input, std::string_view filter)
      47             : {
      48        2406 :     return std::all_of(input.begin(), input.end(), [&filter](char c) { return filter.find(c) != std::string_view::npos; });
      49             : }
      50             : 
      51          36 : bool MatchSuffix(const std::string& str, Span<const std::string_view> list)
      52             : {
      53          36 :     if (str.empty()) return false;
      54         202 :     for (const auto& suffix : list) {
      55         174 :         if (suffix.size() > str.size()) continue;
      56         174 :         if (std::string_view{str}.ends_with(suffix)) return true;
      57             :     }
      58          28 :     return false;
      59          36 : }
      60             : 
      61           6 : bool IsAllowedPlatformHTTPPort(uint16_t port)
      62             : {
      63           6 :     switch (port) {
      64             :     case 443:
      65           5 :         return true;
      66             :     }
      67           1 :     return false;
      68           6 : }
      69             : } // anonymous namespace
      70             : 
      71         140 : bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; }
      72             : 
      73           0 : bool IsServiceDeprecatedRPCEnabled()
      74             : {
      75           0 :     const auto args = gArgs.GetArgs("-deprecatedrpc");
      76           0 :     return std::find(args.begin(), args.end(), "service") != args.end();
      77           0 : }
      78             : 
      79         140 : const CChainParams& MainParams()
      80             : {
      81         144 :     std::call_once(g_main_params_flag, [&]() { g_main_params = CreateChainParams(::gArgs, CBaseChainParams::MAIN); });
      82         140 :     return *Assert(g_main_params);
      83             : }
      84             : 
      85           0 : UniValue ArrFromService(const CService& addr)
      86             : {
      87           0 :     UniValue obj(UniValue::VARR);
      88           0 :     obj.push_back(addr.ToStringAddrPort());
      89           0 :     return obj;
      90           0 : }
      91             : 
      92          53 : DomainPort::Status DomainPort::ValidateDomain(const std::string& addr)
      93             : {
      94          53 :     if (addr.length() > DOMAIN_MAX_LEN || addr.length() < DOMAIN_MIN_LEN) {
      95           2 :         return DomainPort::Status::BadLen;
      96             :     }
      97          51 :     if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
      98           2 :         return DomainPort::Status::BadChar;
      99             :     }
     100          49 :     if (addr.front() == '.' || addr.back() == '.') {
     101           2 :         return DomainPort::Status::BadCharPos;
     102             :     }
     103          47 :     std::vector<std::string> labels{SplitString(addr, '.')};
     104          47 :     if (labels.size() < 2) {
     105           2 :         return DomainPort::Status::BadDotless;
     106             :     }
     107         148 :     for (const auto& label : labels) {
     108         106 :         if (label.empty() || label.length() > DOMAIN_LABEL_MAX_LEN) {
     109           2 :             return DomainPort::Status::BadLabelLen;
     110             :         }
     111         104 :         if (label.front() == '-' || label.back() == '-') {
     112           1 :             return DomainPort::Status::BadLabelCharPos;
     113             :         }
     114             :     }
     115          42 :     return DomainPort::Status::Success;
     116          53 : }
     117             : 
     118          25 : DomainPort::Status DomainPort::Set(const std::string& addr, const uint16_t port)
     119             : {
     120          25 :     if (port == 0) {
     121           1 :         return DomainPort::Status::BadPort;
     122             :     }
     123          24 :     const auto ret{ValidateDomain(addr)};
     124          24 :     if (ret == DomainPort::Status::Success) {
     125             :         // Convert to lowercase to avoid duplication by changing case (domains are case-insensitive)
     126          13 :         m_addr = ToLower(addr);
     127          13 :         m_port = port;
     128          13 :     }
     129          24 :     return ret;
     130          25 : }
     131             : 
     132          40 : DomainPort::Status DomainPort::Validate() const
     133             : {
     134          40 :     if (m_addr.empty() || m_addr != ToLower(m_addr)) {
     135          11 :         return DomainPort::Status::Malformed;
     136             :     }
     137          29 :     if (m_port == 0) {
     138           0 :         return DomainPort::Status::BadPort;
     139             :     }
     140          29 :     return ValidateDomain(m_addr);
     141          40 : }
     142             : 
     143        1940 : bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const
     144             : {
     145        1940 :     if (m_type != rhs.m_type) return false;
     146         585 :     return std::visit(
     147         585 :         [](auto&& lhs, auto&& rhs) -> bool {
     148             :             if constexpr (std::is_same_v<decltype(lhs), decltype(rhs)>) {
     149         585 :                 return lhs == rhs;
     150             :             }
     151           0 :             return false;
     152             :         },
     153         585 :         m_data, rhs.m_data);
     154        1940 : }
     155             : 
     156          35 : bool NetInfoEntry::operator<(const NetInfoEntry& rhs) const
     157             : {
     158          35 :     if (m_type != rhs.m_type) return m_type < rhs.m_type;
     159          34 :     return std::visit(
     160          34 :         [](auto&& lhs, auto&& rhs) -> bool {
     161             :             using T1 = std::decay_t<decltype(lhs)>;
     162             :             using T2 = std::decay_t<decltype(rhs)>;
     163             :             if constexpr (std::is_same_v<T1, T2>) {
     164             :                 // Both the same type, compare as usual
     165          34 :                 return lhs < rhs;
     166             :             } else if constexpr ((std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) &&
     167             :                                  (std::is_same_v<T2, CService> || std::is_same_v<T2, DomainPort>)) {
     168             :                 // Differing types but both implement ToStringAddrPort(), lexicographical compare strings
     169           0 :                 return lhs.ToStringAddrPort() < rhs.ToStringAddrPort();
     170             :             }
     171             :             // If lhs is monostate, it less than rhs; otherwise rhs is greater
     172           0 :             return std::is_same_v<T1, std::monostate>;
     173           0 :         },
     174          34 :         m_data, rhs.m_data);
     175          35 : }
     176             : 
     177        1355 : std::optional<CService> NetInfoEntry::GetAddrPort() const
     178             : {
     179        1355 :     if (const auto* data_ptr{std::get_if<CService>(&m_data)}; m_type == NetInfoType::Service && data_ptr) {
     180             :         ASSERT_IF_DEBUG(data_ptr->IsValid());
     181        1339 :         return *data_ptr;
     182             :     }
     183          16 :     return std::nullopt;
     184        1355 : }
     185             : 
     186          29 : std::optional<DomainPort> NetInfoEntry::GetDomainPort() const
     187             : {
     188          29 :     if (const auto* data_ptr{std::get_if<DomainPort>(&m_data)}; m_type == NetInfoType::Domain && data_ptr) {
     189             :         ASSERT_IF_DEBUG(data_ptr->IsValid());
     190           8 :         return *data_ptr;
     191             :     }
     192          21 :     return std::nullopt;
     193          29 : }
     194             : 
     195           2 : uint16_t NetInfoEntry::GetPort() const
     196             : {
     197           2 :     return std::visit(
     198           2 :         [](auto&& input) -> uint16_t {
     199             :             using T1 = std::decay_t<decltype(input)>;
     200             :             if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
     201           1 :                 return input.GetPort();
     202             :             }
     203           1 :             return 0;
     204             :         },
     205           2 :         m_data);
     206             : }
     207             : 
     208             : // NetInfoEntry is a dumb object that doesn't enforce validation rules, that is the responsibility of
     209             : // types that utilize NetInfoEntry (MnNetInfo and others). IsTriviallyValid() is there to check if a
     210             : // NetInfoEntry object is properly constructed.
     211         254 : bool NetInfoEntry::IsTriviallyValid() const
     212             : {
     213         254 :     if (m_type == NetInfoType::Invalid) return false;
     214         235 :     return std::visit(
     215         470 :         [this](auto&& input) -> bool {
     216             :             using T1 = std::decay_t<decltype(input)>;
     217             :             static_assert(std::is_same_v<T1, std::monostate> || std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>,
     218             :                           "Unexpected type");
     219             :             if constexpr (std::is_same_v<T1, std::monostate>) {
     220             :                 // Empty underlying data isn't a valid entry
     221           0 :                 return false;
     222             :             } else if constexpr (std::is_same_v<T1, CService>) {
     223             :                 // Type code should be truthful as it decides what underlying type is used when (de)serializing
     224         225 :                 if (m_type != NetInfoType::Service) return false;
     225             :                 // Underlying data must meet surface-level validity checks for its type
     226         225 :                 if (!input.IsValid()) return false;
     227             :             } else if constexpr (std::is_same_v<T1, DomainPort>) {
     228             :                 // Type code should be truthful as it decides what underlying type is used when (de)serializing
     229          10 :                 if (m_type != NetInfoType::Domain) return false;
     230             :                 // Underlying data should at least meet surface-level validity checks
     231          10 :                 if (!input.IsValid()) return false;
     232             :             }
     233         235 :             return true;
     234         235 :         },
     235         235 :         m_data);
     236         254 : }
     237             : 
     238          79 : std::string NetInfoEntry::ToString() const
     239             : {
     240          79 :     return std::visit(
     241          79 :         [](auto&& input) -> std::string {
     242             :             using T1 = std::decay_t<decltype(input)>;
     243             :             if constexpr (std::is_same_v<T1, CService>) {
     244          78 :                 return strprintf("CService(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
     245             :             } else if constexpr (std::is_same_v<T1, DomainPort>) {
     246           0 :                 return strprintf("DomainPort(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
     247             :             }
     248           1 :             return "[invalid entry]";
     249           0 :         },
     250          79 :         m_data);
     251             : }
     252             : 
     253          30 : std::string NetInfoEntry::ToStringAddr() const
     254             : {
     255          30 :     return std::visit(
     256          30 :         [](auto&& input) -> std::string {
     257             :             using T1 = std::decay_t<decltype(input)>;
     258             :             if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
     259          29 :                 return input.ToStringAddr();
     260             :             }
     261           1 :             return "[invalid entry]";
     262             :         },
     263          30 :         m_data);
     264             : }
     265             : 
     266           2 : std::string NetInfoEntry::ToStringAddrPort() const
     267             : {
     268           2 :     return std::visit(
     269           2 :         [](auto&& input) -> std::string {
     270             :             using T1 = std::decay_t<decltype(input)>;
     271             :             if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
     272           1 :                 return input.ToStringAddrPort();
     273             :             }
     274           1 :             return "[invalid entry]";
     275             :         },
     276           2 :         m_data);
     277             : }
     278             : 
     279          87 : std::shared_ptr<NetInfoInterface> NetInfoInterface::MakeNetInfo(const uint16_t nVersion)
     280             : {
     281          87 :     assert(nVersion > 0);
     282          87 :     if (nVersion >= ProTxVersion::ExtAddr) {
     283           0 :         return std::make_shared<ExtNetInfo>();
     284             :     }
     285          87 :     return std::make_shared<MnNetInfo>();
     286          87 : }
     287             : 
     288         142 : NetInfoStatus MnNetInfo::ValidateService(const CService& service)
     289             : {
     290         142 :     if (!service.IsValid()) {
     291           1 :         return NetInfoStatus::BadAddress;
     292             :     }
     293         141 :     if (!service.IsIPv4()) {
     294           0 :         return NetInfoStatus::BadType;
     295             :     }
     296         141 :     if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
     297           1 :         return NetInfoStatus::NotRoutable;
     298             :     }
     299             : 
     300         140 :     if (IsNodeOnMainnet() != (service.GetPort() == MainParams().GetDefaultPort())) {
     301             :         // Must use mainnet port on mainnet.
     302             :         // Must NOT use mainnet port on other networks.
     303           2 :         return NetInfoStatus::BadPort;
     304             :     }
     305             : 
     306         138 :     return NetInfoStatus::Success;
     307         142 : }
     308             : 
     309          92 : NetInfoStatus MnNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
     310             : {
     311          92 :     if (purpose != NetInfoPurpose::CORE_P2P || !IsEmpty()) {
     312           9 :         return NetInfoStatus::MaxLimit;
     313             :     }
     314             : 
     315          83 :     std::string addr;
     316          83 :     uint16_t port{Params().GetDefaultPort()};
     317          83 :     SplitHostPort(input, port, addr);
     318             :     // Contains invalid characters, unlikely to pass Lookup(), fast-fail
     319          83 :     if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4)) {
     320           6 :         return NetInfoStatus::BadInput;
     321             :     }
     322             : 
     323         152 :     if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
     324          75 :         const auto ret{ValidateService(*service_opt)};
     325          75 :         if (ret == NetInfoStatus::Success) {
     326          71 :             m_addr = NetInfoEntry{*service_opt};
     327             :             ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
     328          71 :         }
     329          75 :         return ret;
     330             :     }
     331           2 :     return NetInfoStatus::BadInput;
     332          92 : }
     333             : 
     334         955 : NetInfoList MnNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
     335             : {
     336         955 :     if (!IsEmpty() && (!purpose_opt || *purpose_opt == NetInfoPurpose::CORE_P2P)) {
     337             :         ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
     338         914 :         return {m_addr};
     339             :     }
     340             :     // If MnNetInfo is empty or we are given an unexpected purpose code, we don't
     341             :     // expect any entries to show up, so we return a blank set instead.
     342          41 :     return NetInfoList{};
     343         955 : }
     344             : 
     345          71 : CService MnNetInfo::GetPrimary() const
     346             : {
     347         142 :     if (const auto service_opt{m_addr.GetAddrPort()}) {
     348          71 :         return *service_opt;
     349             :     }
     350           0 :     return CService{};
     351          71 : }
     352             : 
     353          83 : NetInfoStatus MnNetInfo::Validate() const
     354             : {
     355          83 :     if (!m_addr.IsTriviallyValid()) {
     356          16 :         return NetInfoStatus::Malformed;
     357             :     }
     358          67 :     return ValidateService(GetPrimary());
     359          83 : }
     360             : 
     361           0 : UniValue MnNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
     362             : {
     363           0 :     if (purpose_opt) {
     364           0 :         UniValue arr(UniValue::VARR);
     365           0 :         for (const auto& entry : GetEntries(purpose_opt)) {
     366           0 :             arr.push_back(entry.ToStringAddrPort());
     367             :         }
     368           0 :         return arr;
     369           0 :     }
     370             : 
     371           0 :     UniValue ret{UniValue::VOBJ};
     372           0 :     if (!IsEmpty()) {
     373           0 :         ret.pushKV(PurposeToString(NetInfoPurpose::CORE_P2P).data(), ArrFromService(GetPrimary()));
     374           0 :     }
     375           0 :     return ret;
     376           0 : }
     377             : 
     378          77 : std::string MnNetInfo::ToString() const
     379             : {
     380         154 :     return IsEmpty() ? "MnNetInfo()"
     381         154 :                      : strprintf("MnNetInfo(NetInfo(purpose=%s, [%s]))", PurposeToString(NetInfoPurpose::CORE_P2P),
     382          77 :                                  m_addr.ToString());
     383           0 : }
     384             : 
     385          12 : bool ExtNetInfo::HasAddrPortDuplicates() const
     386             : {
     387          12 :     std::set<NetInfoEntry> known{};
     388          29 :     for (const auto& entry : m_all_entries) {
     389          17 :         if (auto [_, inserted] = known.insert(entry); !inserted) {
     390           0 :             return true;
     391             :         }
     392             :     }
     393             :     ASSERT_IF_DEBUG(known.size() == m_all_entries.size());
     394          12 :     return false;
     395          12 : }
     396             : 
     397          29 : bool ExtNetInfo::IsAddrPortDuplicate(const NetInfoEntry& candidate) const
     398             : {
     399          58 :     return std::any_of(m_all_entries.begin(), m_all_entries.end(),
     400          47 :                        [&candidate](const auto& entry) { return candidate == entry; });
     401             : }
     402             : 
     403          14 : bool ExtNetInfo::HasAddrDuplicates(const NetInfoList& entries) const
     404             : {
     405          14 :     std::unordered_set<std::string> known{};
     406          31 :     for (const auto& entry : entries) {
     407          17 :         if (auto [_, inserted] = known.insert(entry.ToStringAddr()); !inserted) {
     408           0 :             return true;
     409             :         }
     410             :     }
     411             :     ASSERT_IF_DEBUG(known.size() == entries.size());
     412          14 :     return false;
     413          14 : }
     414             : 
     415           4 : bool ExtNetInfo::IsAddrDuplicate(const NetInfoEntry& candidate, const NetInfoList& entries) const
     416             : {
     417           4 :     const std::string& candidate_str{candidate.ToStringAddr()};
     418           8 :     return std::any_of(entries.begin(), entries.end(),
     419          11 :                        [&candidate_str](const auto& entry) { return candidate_str == entry.ToStringAddr(); });
     420           4 : }
     421             : 
     422          29 : NetInfoStatus ExtNetInfo::ProcessCandidate(const NetInfoPurpose purpose, const NetInfoEntry& candidate)
     423             : {
     424          29 :     assert(candidate.IsTriviallyValid());
     425             : 
     426          29 :     if (IsAddrPortDuplicate(candidate)) {
     427           2 :         return NetInfoStatus::Duplicate;
     428             :     }
     429          27 :     if (candidate.GetDomainPort().has_value() && purpose != NetInfoPurpose::PLATFORM_HTTPS) {
     430             :         // Domains only allowed for Platform HTTPS API
     431           2 :         return NetInfoStatus::BadInput;
     432             :     }
     433          25 :     if (auto it{m_data.find(purpose)}; it != m_data.end()) {
     434             :         // Existing entries list found, check limit
     435           5 :         auto& [_, entries] = *it;
     436           5 :         if (entries.size() >= MAX_ENTRIES_EXTNETINFO) {
     437           1 :             return NetInfoStatus::MaxLimit;
     438             :         }
     439           8 :         if (IsAddrDuplicate(candidate, entries)) {
     440           1 :             return NetInfoStatus::Duplicate;
     441             :         }
     442           3 :         entries.push_back(candidate);
     443           3 :     } else {
     444             :         // First entry for purpose code, create new entries list
     445          20 :         auto [_, status] = m_data.try_emplace(purpose, std::vector<NetInfoEntry>({candidate}));
     446          20 :         assert(status); // We did just check to see if our value already existed, try_emplace shouldn't fail
     447             :     }
     448             : 
     449             :     // Candidate successfully added, update cache
     450          23 :     m_all_entries.push_back(candidate);
     451          23 :     return NetInfoStatus::Success;
     452          29 : }
     453             : 
     454          45 : NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
     455             : {
     456          45 :     if (!service.IsValid()) {
     457           1 :         return NetInfoStatus::BadAddress;
     458             :     }
     459          44 :     if (!service.IsCJDNS() && !service.IsI2P() && !service.IsIPv4() && !service.IsIPv6() && !service.IsTor()) {
     460           0 :         return NetInfoStatus::BadType;
     461             :     }
     462          44 :     if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
     463           1 :         return NetInfoStatus::NotRoutable;
     464             :     }
     465          43 :     const uint16_t service_port{service.GetPort()};
     466          43 :     if (service.IsI2P()) {
     467           2 :         if (service_port != I2P_SAM31_PORT) {
     468             :             // I2P SAM 3.1 and earlier don't support arbitrary ports
     469           1 :             return NetInfoStatus::BadPort;
     470             :         }
     471           1 :     } else {
     472          41 :         if (service_port == 0 || IsBadPort(service_port)) {
     473           4 :             return NetInfoStatus::BadPort;
     474             :         }
     475             :     }
     476             : 
     477          38 :     return NetInfoStatus::Success;
     478          45 : }
     479             : 
     480          11 : NetInfoStatus ExtNetInfo::ValidateDomainPort(const DomainPort& domain)
     481             : {
     482          11 :     if (!domain.IsValid()) {
     483           0 :         return NetInfoStatus::BadInput;
     484             :     }
     485          11 :     const uint16_t domain_port{domain.GetPort()};
     486          11 :     if (domain_port == 0 || (IsBadPort(domain_port) && !IsAllowedPlatformHTTPPort(domain_port))) {
     487           1 :         return NetInfoStatus::BadPort;
     488             :     }
     489          10 :     const std::string& addr{domain.ToStringAddr()};
     490          10 :     if (MatchSuffix(addr, TLDS_BAD) || MatchSuffix(addr, TLDS_PRIVACY)) {
     491           1 :         return NetInfoStatus::BadInput;
     492             :     }
     493          10 :     if (const auto labels{SplitString(addr, '.')}; !MatchCharsFilter(labels.at(labels.size() - 1), SAFE_CHARS_ALPHA)) {
     494           1 :         return NetInfoStatus::BadInput;
     495             :     }
     496             : 
     497           8 :     return NetInfoStatus::Success;
     498          11 : }
     499             : 
     500          54 : NetInfoStatus ExtNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
     501             : {
     502          54 :     if (!IsValidPurpose(purpose)) {
     503           1 :         return NetInfoStatus::MaxLimit;
     504             :     }
     505             : 
     506             :     // We don't allow assuming ports, so we set the default value to 0 so that if no port is specified
     507             :     // it uses a fallback value of 0, which will return a NetInfoStatus::BadPort
     508          53 :     std::string addr;
     509          53 :     uint16_t port{0};
     510          53 :     SplitHostPort(input, port, addr);
     511             : 
     512          53 :     if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4_6)) {
     513          17 :         if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
     514             :             // Neither IP:port safe nor domain-safe, we can safely assume it's bad input
     515           0 :             return NetInfoStatus::BadInput;
     516             :         }
     517             : 
     518             :         // Not IP:port safe but domain safe
     519          17 :         if (MatchSuffix(addr, TLDS_PRIVACY)) {
     520             :             // Special domain, try storing it as CService
     521           7 :             CNetAddr netaddr;
     522           7 :             if (netaddr.SetSpecial(addr)) {
     523           7 :                 const CService service{netaddr, port};
     524           7 :                 const auto ret{ValidateService(service)};
     525           7 :                 if (ret == NetInfoStatus::Success) {
     526           5 :                     return ProcessCandidate(purpose, NetInfoEntry{service});
     527             :                 }
     528           2 :                 return ret; /* ValidateService() failed */
     529           7 :             }
     530          26 :         } else if (DomainPort domain; domain.Set(addr, port) == DomainPort::Status::Success) {
     531             :             // Regular domain
     532           9 :             const auto ret{ValidateDomainPort(domain)};
     533           9 :             if (ret == NetInfoStatus::Success) {
     534           6 :                 return ProcessCandidate(purpose, NetInfoEntry{domain});
     535             :             }
     536           3 :             return ret; /* ValidateDomainPort() failed */
     537             :         }
     538           1 :         return NetInfoStatus::BadInput; /* CNetAddr::SetSpecial() or DomainPort::Set() failed */
     539             :     }
     540             : 
     541             :     // IP:port safe, try to parse it as IP:port
     542          59 :     if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
     543          23 :         const auto service{MaybeFlipIPv6toCJDNS(*service_opt)};
     544          23 :         const auto ret{ValidateService(service)};
     545          23 :         if (ret == NetInfoStatus::Success) {
     546          18 :             return ProcessCandidate(purpose, NetInfoEntry{service});
     547             :         }
     548           5 :         return ret; /* ValidateService() failed */
     549          23 :     }
     550          13 :     return NetInfoStatus::BadInput; /* Lookup() failed */
     551          54 : }
     552             : 
     553          35 : NetInfoList ExtNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
     554             : {
     555          35 :     if (!purpose_opt) {
     556             :         // Without a purpose code specified, we're being requested for a flattened list of all
     557             :         // entries. ExtNetInfo is an append-only structure, so we can avoid re-calculating a
     558             :         // list of all entries by maintaining a small cache.
     559          35 :         return m_all_entries;
     560             :     }
     561           0 :     if (!IsValidPurpose(*purpose_opt)) return NetInfoList{};
     562           0 :     const auto& it{m_data.find(*purpose_opt)};
     563           0 :     return it != m_data.end() ? it->second : NetInfoList{};
     564          35 : }
     565             : 
     566           0 : CService ExtNetInfo::GetPrimary() const
     567             : {
     568           0 :     if (const auto& it{m_data.find(NetInfoPurpose::CORE_P2P)}; it != m_data.end()) {
     569           0 :         const auto& [_, entries] = *it;
     570             :         // If a purpose code is in the map, there should be at least one entry
     571             :         ASSERT_IF_DEBUG(!entries.empty());
     572           0 :         if (entries.size() >= 1) {
     573           0 :             if (const auto& service_opt{entries[0].GetAddrPort()}) {
     574           0 :                 return *service_opt;
     575             :             }
     576           0 :         }
     577           0 :     }
     578           0 :     return CService{};
     579           0 : }
     580             : 
     581          31 : bool ExtNetInfo::HasEntries(NetInfoPurpose purpose) const
     582             : {
     583          31 :     if (!IsValidPurpose(purpose)) return false;
     584          30 :     const auto& it{m_data.find(purpose)};
     585          30 :     return it != m_data.end() && !it->second.empty();
     586          31 : }
     587             : 
     588          27 : NetInfoStatus ExtNetInfo::Validate() const
     589             : {
     590          27 :     if (m_version == 0 || m_version > CURRENT_VERSION || m_data.empty()) {
     591          15 :         return NetInfoStatus::Malformed;
     592             :     }
     593          12 :     if (HasAddrPortDuplicates()) {
     594           0 :         return NetInfoStatus::Duplicate;
     595             :     }
     596          28 :     for (const auto& [purpose, entries] : m_data) {
     597          14 :         if (!IsValidPurpose(purpose)) {
     598           0 :             return NetInfoStatus::Malformed;
     599             :         }
     600          14 :         if (entries.empty()) {
     601             :             // Purpose if present in map must have at least one entry
     602           0 :             return NetInfoStatus::Malformed;
     603             :         }
     604          14 :         if (HasAddrDuplicates(entries)) {
     605           0 :             return NetInfoStatus::Duplicate;
     606             :         }
     607          31 :         for (const auto& entry : entries) {
     608          17 :             if (!entry.IsTriviallyValid()) {
     609             :                 // Trivially invalid NetInfoEntry, no point checking against consensus rules
     610           0 :                 return NetInfoStatus::Malformed;
     611             :             }
     612          34 :             if (const auto& service_opt{entry.GetAddrPort()}) {
     613          15 :                 if (auto ret{ValidateService(*service_opt)}; ret != NetInfoStatus::Success) {
     614             :                     // Stores CService underneath but doesn't pass validation rules
     615           0 :                     return ret;
     616             :                 }
     617          19 :             } else if (const auto domain_opt{entry.GetDomainPort()}) {
     618           2 :                 if (purpose != NetInfoPurpose::PLATFORM_HTTPS) {
     619             :                     // Domains only allowed for Platform HTTPS API
     620           0 :                     return NetInfoStatus::BadInput;
     621             :                 }
     622           2 :                 if (auto ret{ValidateDomainPort(*domain_opt)}; ret != NetInfoStatus::Success) {
     623             :                     // Stores DomainPort underneath but doesn't pass validation rules
     624           0 :                     return ret;
     625             :                 }
     626           2 :             } else {
     627             :                 // Doesn't store valid type underneath
     628           0 :                 return NetInfoStatus::Malformed;
     629             :             }
     630             :         }
     631             :     }
     632          12 :     return NetInfoStatus::Success;
     633          27 : }
     634             : 
     635           0 : UniValue ExtNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
     636             : {
     637             :     // Report only a subset of all addresses if supplied a purpose code
     638           0 :     if (purpose_opt) {
     639           0 :         UniValue arr(UniValue::VARR);
     640           0 :         for (const auto& entry : GetEntries(purpose_opt)) {
     641           0 :             arr.push_back(entry.ToStringAddrPort());
     642             :         }
     643           0 :         return arr;
     644           0 :     }
     645             : 
     646           0 :     UniValue ret(UniValue::VOBJ);
     647           0 :     for (const auto& [purpose, entries] : m_data) {
     648           0 :         UniValue arr(UniValue::VARR);
     649           0 :         for (const auto& entry : entries) {
     650           0 :             arr.push_back(entry.ToStringAddrPort());
     651             :         }
     652           0 :         ret.pushKV(PurposeToString(purpose).data(), arr);
     653           0 :     }
     654           0 :     return ret;
     655           0 : }
     656             : 
     657           0 : std::string ExtNetInfo::ToString() const
     658             : {
     659           0 :     return IsEmpty() ? "ExtNetInfo()" : strprintf("ExtNetInfo(%s)", [&]() -> std::string {
     660           0 :         std::string ret{};
     661           0 :         bool first{true};
     662           0 :         for (const auto& [purpose, entries] : m_data) {
     663           0 :             if (!first) { ret += ", "; } else { first = false; }
     664           0 :             ret += strprintf("NetInfo(purpose=%s, [%s])", PurposeToString(purpose), [&]() -> std::string {
     665           0 :                 if (entries.empty()) {
     666           0 :                     return "invalid list";
     667             :                 }
     668           0 :                 return Join(entries, ", ", [](const auto& entry) { return entry.ToString(); });
     669           0 :             }());
     670             :         }
     671           0 :         return ret;
     672           0 :     }());
     673           0 : }

Generated by: LCOV version 1.16