LCOV - code coverage report
Current view: top level - src/test - evo_netinfo_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 286 286 100.0 %
Date: 2026-06-25 07:23:43 Functions: 77 77 100.0 %

          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 <test/util/setup_common.h>
       6             : 
       7             : #include <evo/netinfo.h>
       8             : #include <util/helpers.h>
       9             : 
      10             : #include <chainparams.h>
      11             : #include <clientversion.h>
      12             : #include <netbase.h>
      13             : #include <streams.h>
      14             : #include <util/strencodings.h>
      15             : 
      16             : #include <boost/test/unit_test.hpp>
      17             : 
      18         146 : BOOST_FIXTURE_TEST_SUITE(evo_netinfo_tests, BasicTestingSetup)
      19             : 
      20             : struct TestEntry {
      21             :     std::pair</*purpose=*/NetInfoPurpose, /*addr=*/std::string> input;
      22             :     NetInfoStatus expected_ret_mn;
      23             :     NetInfoStatus expected_ret_ext;
      24             : };
      25             : 
      26         146 : static const std::vector<TestEntry> addr_vals_main{
      27             :     // Address and port specified
      28         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"}, NetInfoStatus::Success, NetInfoStatus::Success},
      29             :     // - Port should default to default P2P core with MnNetInfo
      30             :     // - Ports are no longer implied with ExtNetInfo
      31         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
      32             :     // - Non-mainnet port on mainnet causes failure in MnNetInfo
      33             :     // - ExtNetInfo is indifferent to choice of port unless it's a bad port which 9998 isn't
      34         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:9998"}, NetInfoStatus::BadPort, NetInfoStatus::Success},
      35             :     // Internal addresses not allowed on mainnet
      36         146 :     {{NetInfoPurpose::CORE_P2P, "127.0.0.1:9999"}, NetInfoStatus::NotRoutable, NetInfoStatus::NotRoutable},
      37             :     // Valid IPv4 formatting but invalid IPv4 address
      38         146 :     {{NetInfoPurpose::CORE_P2P, "0.0.0.0:9999"}, NetInfoStatus::BadAddress, NetInfoStatus::BadAddress},
      39             :     // Port greater than uint16_t max
      40         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:99999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
      41             :     // - Non-IPv4 addresses are prohibited in MnNetInfo
      42             :     // - Any valid BIP155 address is allowed in ExtNetInfo
      43         146 :     {{NetInfoPurpose::CORE_P2P, "[2606:4700:4700::1111]:9999"}, NetInfoStatus::BadInput, NetInfoStatus::Success},
      44             :     // - MnNetInfo doesn't allow storing anything except a Core P2P address
      45             :     // - Privacy network domains are allowed in ExtNetInfo but internet domains are not
      46         146 :     {{NetInfoPurpose::CORE_P2P, "example.com:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
      47         146 :     {{NetInfoPurpose::CORE_P2P, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:9999"}, NetInfoStatus::BadInput, NetInfoStatus::Success},
      48         146 :     {{NetInfoPurpose::PLATFORM_P2P, "example.com:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadInput},
      49         146 :     {{NetInfoPurpose::PLATFORM_P2P, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
      50             :     // - MnNetInfo doesn't allow storing anything except a Core P2P address
      51             :     // - ExtNetInfo can store Platform HTTPS addresses *as domains* alongside privacy network domains
      52         146 :     {{NetInfoPurpose::PLATFORM_HTTPS, "example.com:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
      53         146 :     {{NetInfoPurpose::PLATFORM_HTTPS, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
      54             :     // Incorrect IPv4 address
      55         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.256:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
      56             :     // Missing address
      57         146 :     {{NetInfoPurpose::CORE_P2P, ":9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
      58             :     // Bad purpose code
      59         146 :     {{static_cast<NetInfoPurpose>(64), "1.1.1.1:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::MaxLimit},
      60             :     // - MnNetInfo doesn't allow storing anything except a Core P2P address
      61             :     // - ExtNetInfo allows storing Platform P2P addresses
      62         146 :     {{NetInfoPurpose::PLATFORM_P2P, "1.1.1.1:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
      63             : };
      64             : 
      65          22 : void ValidateGetEntries(const NetInfoList& entries, const size_t expected_size)
      66             : {
      67          22 :     BOOST_CHECK_EQUAL(entries.size(), expected_size);
      68          47 :     for (const NetInfoEntry& entry : entries) {
      69          25 :         BOOST_CHECK(entry.IsTriviallyValid());
      70             :     }
      71          22 : }
      72             : 
      73           2 : void TestMnNetInfo(const std::vector<TestEntry>& vals)
      74             : {
      75          62 :     for (const auto& [input, expected_ret, _] : vals) {
      76          80 :         const auto& [purpose, addr] = input;
      77          20 :         MnNetInfo netInfo;
      78          20 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(purpose, addr), expected_ret);
      79          20 :         if (expected_ret != NetInfoStatus::Success) {
      80             :             // An empty MnNetInfo is considered malformed
      81          16 :             BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Malformed);
      82          16 :             BOOST_CHECK(!netInfo.HasEntries(purpose));
      83          16 :             BOOST_CHECK(netInfo.GetEntries().empty());
      84          16 :         } else {
      85           4 :             BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Success);
      86           4 :             BOOST_CHECK(netInfo.HasEntries(purpose));
      87           4 :             ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1);
      88             :         }
      89          20 :     }
      90           2 : }
      91             : 
      92           3 : void TestExtNetInfo(const std::vector<TestEntry>& vals)
      93             : {
      94          78 :     for (const auto& [input, _, expected_ret] : vals) {
      95         100 :         const auto& [purpose, addr] = input;
      96          25 :         ExtNetInfo netInfo;
      97          25 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(purpose, addr), expected_ret);
      98          25 :         if (expected_ret != NetInfoStatus::Success) {
      99             :             // An empty ExtNetInfo is considered malformed
     100          15 :             BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Malformed);
     101          15 :             BOOST_CHECK(!netInfo.HasEntries(purpose));
     102          15 :             BOOST_CHECK(netInfo.GetEntries().empty());
     103          15 :         } else {
     104          10 :             BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Success);
     105          10 :             BOOST_CHECK(netInfo.HasEntries(purpose));
     106          10 :             ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1);
     107             :         }
     108          25 :     }
     109           3 : }
     110             : 
     111         149 : BOOST_AUTO_TEST_CASE(mnnetinfo_rules_main)
     112             : {
     113           1 :     TestMnNetInfo(addr_vals_main);
     114             : 
     115             :     {
     116             :         // MnNetInfo only stores one value, overwriting prohibited
     117           1 :         MnNetInfo netInfo;
     118           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"), NetInfoStatus::Success);
     119           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.2:9999"), NetInfoStatus::MaxLimit);
     120           1 :         BOOST_CHECK(netInfo.HasEntries(NetInfoPurpose::CORE_P2P));
     121           1 :         ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1);
     122           1 :     }
     123             : 
     124             :     {
     125             :         // MnNetInfo only allows storing a Core P2P address
     126           1 :         MnNetInfo netInfo;
     127           3 :         for (const auto purpose : {NetInfoPurpose::PLATFORM_HTTPS, NetInfoPurpose::PLATFORM_P2P}) {
     128           2 :             BOOST_CHECK_EQUAL(netInfo.AddEntry(purpose, "1.1.1.1:9999"), NetInfoStatus::MaxLimit);
     129           2 :             BOOST_CHECK(!netInfo.HasEntries(purpose));
     130             :         }
     131           1 :         BOOST_CHECK(netInfo.GetEntries().empty());
     132           1 :     }
     133           1 : }
     134             : 
     135         149 : BOOST_AUTO_TEST_CASE(extnetinfo_rules_main) { TestExtNetInfo(addr_vals_main); }
     136             : 
     137         146 : static const std::vector<TestEntry> addr_vals_reg{
     138             :     // - MnNetInfo doesn't mind using port 0
     139             :     // - ExtNetInfo requires non-zero ports
     140         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:0"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
     141             :     // - Mainnet P2P port on non-mainnet cause failure in MnNetInfo
     142             :     // - ExtNetInfo is indifferent to choice of port unless it's a bad port which 9999 isn't
     143         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"}, NetInfoStatus::BadPort, NetInfoStatus::Success},
     144             :     // - Non-mainnet P2P port is allowed in MnNetInfo regardless of bad port status
     145             :     // - Port 22 (SSH) is below the privileged ports threshold (1023) and is therefore a bad port, disallowed in ExtNetInfo
     146         146 :     {{NetInfoPurpose::CORE_P2P, "1.1.1.1:22"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
     147             : };
     148             : 
     149             : enum class ExpectedType : uint8_t {
     150             :     CJDNS,
     151             :     I2P,
     152             :     Tor,
     153             : };
     154             : 
     155         146 : static const std::vector<std::tuple</*type=*/ExpectedType, /*input=*/std::string, /*expected_ret=*/NetInfoStatus>> privacy_addr_vals{
     156         146 :     {ExpectedType::CJDNS, "[fc00:3344:5566:7788:9900:aabb:ccdd:eeff]:9998", NetInfoStatus::Success},
     157             :     // ExtNetInfo can store I2P addresses as long as it uses port 0
     158         146 :     {ExpectedType::I2P, "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p:0", NetInfoStatus::Success},
     159             :     // ExtNetInfo can store onion addresses
     160         146 :     {ExpectedType::Tor, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:9998", NetInfoStatus::Success},
     161             :     // ExtNetInfo can store I2P addresses but non-zero ports are not allowed
     162         146 :     {ExpectedType::I2P, "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p:9998", NetInfoStatus::BadPort},
     163             :     // ExtNetInfo can store onion addresses but zero ports are not allowed
     164         146 :     {ExpectedType::Tor, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:0", NetInfoStatus::BadPort},
     165             : };
     166             : 
     167         149 : BOOST_FIXTURE_TEST_CASE(mnnetinfo_rules_reg, RegTestingSetup) { TestMnNetInfo(addr_vals_reg); }
     168             : 
     169         149 : BOOST_FIXTURE_TEST_CASE(extnetinfo_rules_reg, RegTestingSetup)
     170             : {
     171           1 :     TestExtNetInfo(addr_vals_reg);
     172             : 
     173             :     {
     174             :         // ExtNetInfo can store up to 4 entries per purpose code, check limit enforcement
     175           1 :         ExtNetInfo netInfo;
     176           5 :         for (size_t idx{1}; idx <= MAX_ENTRIES_EXTNETINFO; idx++) {
     177           4 :             BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.%d:9998", idx)),
     178             :                               NetInfoStatus::Success);
     179           4 :         }
     180           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.5:9998"), NetInfoStatus::MaxLimit);
     181           1 :         BOOST_CHECK(netInfo.HasEntries(NetInfoPurpose::CORE_P2P));
     182             :         // The limit applies *per purpose code* and therefore wouldn't error if the address was for a different purpose
     183           1 :         BOOST_CHECK(!netInfo.HasEntries(NetInfoPurpose::PLATFORM_P2P));
     184           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::PLATFORM_P2P, "1.1.1.5:9998"), NetInfoStatus::Success);
     185           1 :         BOOST_CHECK(netInfo.HasEntries(NetInfoPurpose::PLATFORM_P2P));
     186           1 :         BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Success);
     187             :         // GetEntries() is a tally of all entries across all purpose codes
     188           1 :         ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/MAX_ENTRIES_EXTNETINFO + 1);
     189           1 :     }
     190             : 
     191             :     {
     192             :         // ExtNetInfo has restrictions on duplicates
     193           1 :         ExtNetInfo netInfo;
     194           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9998"), NetInfoStatus::Success);
     195             : 
     196             :         // Exact (i.e. addr:port) duplicates are prohibited *within* a list
     197           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9998"), NetInfoStatus::Duplicate);
     198             :         // Partial (i.e. different port) duplicates are prohibited *within* a list
     199           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9997"), NetInfoStatus::Duplicate);
     200             : 
     201             :         // Exact (i.e. addr:port) duplicates are prohibited *across* lists
     202           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::PLATFORM_P2P, "1.1.1.1:9998"), NetInfoStatus::Duplicate);
     203             :         // Partial (i.e. different port) duplicates are allowed *across* a list
     204           1 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::PLATFORM_P2P, "1.1.1.1:9997"), NetInfoStatus::Success);
     205             : 
     206           1 :         BOOST_CHECK_EQUAL(netInfo.Validate(), NetInfoStatus::Success);
     207           1 :         BOOST_CHECK(netInfo.HasEntries(NetInfoPurpose::CORE_P2P));
     208           1 :         BOOST_CHECK(netInfo.HasEntries(NetInfoPurpose::PLATFORM_P2P));
     209           1 :         BOOST_CHECK(!netInfo.HasEntries(NetInfoPurpose::PLATFORM_HTTPS));
     210           1 :         ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/2);
     211           1 :     }
     212             : 
     213             :     {
     214             :         // ExtNetInfo has additional rules for domains
     215           1 :         const std::vector<TestEntry> domain_vals{
     216             :             // Port 80 (HTTP) is below the privileged ports threshold (1023), not allowed
     217           1 :             {{NetInfoPurpose::PLATFORM_HTTPS, "example.com:80"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadPort},
     218             :             // Port 443 (HTTPS) is below the privileged ports threshold (1023) but still allowed
     219           1 :             {{NetInfoPurpose::PLATFORM_HTTPS, "example.com:443"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
     220             :             // TLDs must be alphabetic to avoid ambiguation with IP addresses (per ICANN guidelines)
     221           1 :             {{NetInfoPurpose::PLATFORM_HTTPS, "example.123:443"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadInput},
     222             :             // .local is a prohibited TLD
     223           1 :             {{NetInfoPurpose::PLATFORM_HTTPS, "somebodys-macbook-pro.local:9998"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadInput},
     224             :             // DomainPort isn't used for storing privacy network TLDs like .onion
     225           1 :             {{NetInfoPurpose::PLATFORM_HTTPS, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd:9998"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadInput},
     226             :         };
     227           1 :         TestExtNetInfo(domain_vals);
     228           1 :     }
     229             : 
     230             :     // Privacy network entry checks
     231          14 :     for (const auto& [type, input, expected_ret] : privacy_addr_vals) {
     232           5 :         const bool expected_success{expected_ret == NetInfoStatus::Success};
     233             : 
     234           5 :         ExtNetInfo netInfo{};
     235           5 :         BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, input), expected_ret);
     236           5 :         ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/expected_success ? 1 : 0);
     237           5 :         if (!expected_success) continue;
     238             : 
     239             :         // Type registration check
     240           3 :         const CService service{netInfo.GetEntries().at(0).GetAddrPort().value()};
     241           3 :         BOOST_CHECK(service.IsValid());
     242           3 :         switch (type) {
     243             :         case ExpectedType::CJDNS:
     244           1 :             BOOST_CHECK(service.IsCJDNS());
     245           1 :             break;
     246             :         case ExpectedType::I2P:
     247           1 :             BOOST_CHECK(service.IsI2P());
     248           1 :             break;
     249             :         case ExpectedType::Tor:
     250           1 :             BOOST_CHECK(service.IsTor());
     251           1 :             break;
     252             :         } // no default case, so the compiler can warn about missing cases
     253           5 :     }
     254           1 : }
     255             : 
     256         149 : BOOST_AUTO_TEST_CASE(netinfo_ser)
     257             : {
     258             :     {
     259             :         // An empty object should only store one byte to denote it is invalid
     260           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION);
     261           1 :         NetInfoEntry entry{};
     262           1 :         ds << entry;
     263           1 :         BOOST_CHECK_EQUAL(ds.size(), sizeof(uint8_t));
     264           1 :     }
     265             : 
     266             :     {
     267             :         // Reading a nonsense byte should return an empty object
     268           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION);
     269           1 :         NetInfoEntry entry{};
     270           1 :         ds << 0xfe;
     271           1 :         ds >> entry;
     272           1 :         BOOST_CHECK(entry.IsEmpty() && !entry.IsTriviallyValid());
     273           1 :     }
     274             : 
     275             :     {
     276             :         // Reading an invalid CService should fail trivial validation and return an empty object
     277           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION);
     278           1 :         NetInfoEntry entry{};
     279           1 :         ds << NetInfoEntry::NetInfoType::Service << CService{};
     280           1 :         ds >> entry;
     281           1 :         BOOST_CHECK(entry.IsEmpty() && !entry.IsTriviallyValid());
     282           1 :     }
     283             : 
     284             :     {
     285             :         // Reading an unrecognized type should fail trivial validation and return an empty object
     286           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION);
     287           1 :         NetInfoEntry entry{};
     288           1 :         ds << NetInfoEntry::NetInfoType::Service << uint256{};
     289           1 :         ds >> entry;
     290           1 :         BOOST_CHECK(entry.IsEmpty() && !entry.IsTriviallyValid());
     291           1 :     }
     292             : 
     293             :     {
     294             :         // A valid CService should be constructable, readable and pass validation
     295           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION | ADDRV2_FORMAT);
     296           1 :         CService service{LookupNumeric("1.1.1.1", Params().GetDefaultPort())};
     297           1 :         BOOST_CHECK(service.IsValid());
     298           1 :         NetInfoEntry entry{service}, entry2{};
     299           1 :         ds << NetInfoEntry::NetInfoType::Service << service;
     300           1 :         ds >> entry2;
     301           1 :         BOOST_CHECK(entry == entry2);
     302           2 :         BOOST_CHECK(!entry.IsEmpty() && entry.IsTriviallyValid());
     303           1 :         BOOST_CHECK(entry.GetAddrPort().value() == service);
     304           1 :     }
     305             : 
     306             :     {
     307             :         // NetInfoEntry should be able to read and write ADDRV2 addresses
     308           1 :         CService service{};
     309           1 :         service.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion");
     310           2 :         BOOST_CHECK(service.IsValid() && service.IsTor());
     311             : 
     312           1 :         CDataStream ds(SER_DISK, CLIENT_VERSION | ADDRV2_FORMAT);
     313           1 :         ds << NetInfoEntry::NetInfoType::Service << service;
     314           1 :         ds.SetVersion(CLIENT_VERSION); // Drop the explicit format flag
     315             : 
     316           1 :         NetInfoEntry entry{};
     317           1 :         ds >> entry;
     318           2 :         BOOST_CHECK(!entry.IsEmpty() && entry.IsTriviallyValid());
     319           1 :         BOOST_CHECK(entry.GetAddrPort().value() == service);
     320           1 :         ds.clear();
     321             : 
     322           1 :         NetInfoEntry entry2{};
     323           1 :         ds << entry;
     324           1 :         ds >> entry2;
     325           2 :         BOOST_CHECK(entry == entry2 && entry2.GetAddrPort().value() == service);
     326           1 :     }
     327           1 : }
     328             : 
     329         149 : BOOST_AUTO_TEST_CASE(netinfo_retvals)
     330             : {
     331           1 :     uint16_t p2p_port{Params().GetDefaultPort()};
     332           1 :     CService service{LookupNumeric("1.1.1.1", p2p_port)}, service2{LookupNumeric("1.1.1.2", p2p_port)};
     333           1 :     NetInfoEntry entry{service}, entry2{service2}, entry_empty{};
     334             : 
     335             :     // Check that values are correctly recorded and pass trivial validation
     336           1 :     BOOST_CHECK(service.IsValid());
     337           2 :     BOOST_CHECK(!entry.IsEmpty() && entry.IsTriviallyValid());
     338           1 :     BOOST_CHECK(entry.GetAddrPort().value() == service);
     339           2 :     BOOST_CHECK(!entry2.IsEmpty() && entry2.IsTriviallyValid());
     340           1 :     BOOST_CHECK(entry2.GetAddrPort().value() == service2);
     341             : 
     342             :     // Check that dispatch returns the expected values
     343           1 :     BOOST_CHECK_EQUAL(entry.GetPort(), service.GetPort());
     344           1 :     BOOST_CHECK_EQUAL(entry.ToString(), strprintf("CService(addr=%s, port=%u)", service.ToStringAddr(), service.GetPort()));
     345           1 :     BOOST_CHECK_EQUAL(entry.ToStringAddr(), service.ToStringAddr());
     346           1 :     BOOST_CHECK_EQUAL(entry.ToStringAddrPort(), service.ToStringAddrPort());
     347           1 :     BOOST_CHECK_EQUAL(service < service2, entry < entry2);
     348             : 
     349             :     // Check that empty/invalid entries return error messages
     350           1 :     BOOST_CHECK_EQUAL(entry_empty.GetPort(), 0);
     351           1 :     BOOST_CHECK_EQUAL(entry_empty.ToString(), "[invalid entry]");
     352           1 :     BOOST_CHECK_EQUAL(entry_empty.ToStringAddr(), "[invalid entry]");
     353           1 :     BOOST_CHECK_EQUAL(entry_empty.ToStringAddrPort(), "[invalid entry]");
     354             : 
     355             :     // The invalid entry type code is 0xff (highest possible value) and therefore will return as greater
     356             :     // in comparison to any valid entry
     357           1 :     BOOST_CHECK(entry < entry_empty);
     358           1 : }
     359             : 
     360           5 : bool CheckIfSerSame(const CService& lhs, const MnNetInfo& rhs)
     361             : {
     362           5 :     CHashWriter ss_lhs(SER_GETHASH, 0), ss_rhs(SER_GETHASH, 0);
     363           5 :     ss_lhs << lhs;
     364           5 :     ss_rhs << rhs;
     365           5 :     return ss_lhs.GetSHA256() == ss_rhs.GetSHA256();
     366             : }
     367             : 
     368         149 : BOOST_AUTO_TEST_CASE(cservice_compatible)
     369             : {
     370             :     // Empty values should be the same
     371           1 :     CService service;
     372           1 :     MnNetInfo netInfo;
     373           1 :     BOOST_CHECK(CheckIfSerSame(service, netInfo));
     374             : 
     375             :     // Valid IPv4 address, valid port
     376           1 :     service = LookupNumeric("1.1.1.1", 9999);
     377           1 :     netInfo.Clear();
     378           1 :     BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"), NetInfoStatus::Success);
     379           1 :     BOOST_CHECK(CheckIfSerSame(service, netInfo));
     380             : 
     381             :     // Valid IPv4 address, default P2P port implied
     382           1 :     service = LookupNumeric("1.1.1.1", Params().GetDefaultPort());
     383           1 :     netInfo.Clear();
     384           1 :     BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1"), NetInfoStatus::Success);
     385           1 :     BOOST_CHECK(CheckIfSerSame(service, netInfo));
     386             : 
     387             :     // Lookup() failure (domains not allowed), MnNetInfo should remain empty if Lookup() failed
     388           1 :     service = CService();
     389           1 :     netInfo.Clear();
     390           1 :     BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "example.com"), NetInfoStatus::BadInput);
     391           1 :     BOOST_CHECK(CheckIfSerSame(service, netInfo));
     392             : 
     393             :     // Validation failure (non-IPv4 not allowed), MnNetInfo should remain empty if ValidateService() failed
     394           1 :     service = CService();
     395           1 :     netInfo.Clear();
     396           1 :     BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::CORE_P2P, "[2606:4700:4700::1111]:9999"), NetInfoStatus::BadInput);
     397           1 :     BOOST_CHECK(CheckIfSerSame(service, netInfo));
     398           1 : }
     399             : 
     400         149 : BOOST_AUTO_TEST_CASE(interface_equality)
     401             : {
     402             :     // We also check for symmetry as NetInfoInterface, ExtNetInfo, MnNetInfo and NetInfoEntry
     403             :     // define their operator!= as the inverse of operator==
     404           1 :     std::shared_ptr<NetInfoInterface> ptr_lhs{nullptr}, ptr_rhs{nullptr};
     405             : 
     406             :     // Equal initialization state (uninitialized)
     407           1 :     BOOST_CHECK(util::shared_ptr_equal(ptr_lhs, ptr_rhs) && !util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     408             : 
     409             :     // Unequal initialization state (lhs initialized, rhs unchanged)
     410           1 :     ptr_lhs = std::make_shared<MnNetInfo>();
     411           2 :     BOOST_CHECK(!util::shared_ptr_equal(ptr_lhs, ptr_rhs) && util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     412             : 
     413             :     // Equal initialization state (lhs unchanged, rhs initialized), same values
     414           1 :     ptr_rhs = std::make_shared<MnNetInfo>();
     415           2 :     BOOST_CHECK(ptr_lhs->IsEmpty() && ptr_rhs->IsEmpty());
     416           1 :     BOOST_CHECK(util::shared_ptr_equal(ptr_lhs, ptr_rhs) && !util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     417             : 
     418             :     // Equal initialization state, same type, differing values
     419           1 :     BOOST_CHECK_EQUAL(ptr_rhs->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"), NetInfoStatus::Success);
     420           2 :     BOOST_CHECK(!util::shared_ptr_equal(ptr_lhs, ptr_rhs) && util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     421             : 
     422             :     // Equal initialization state, different type, same values
     423           1 :     ptr_rhs = std::make_shared<ExtNetInfo>();
     424           2 :     BOOST_CHECK(ptr_lhs->IsEmpty() && ptr_rhs->IsEmpty());
     425           2 :     BOOST_CHECK(!util::shared_ptr_equal(ptr_lhs, ptr_rhs) && util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     426             : 
     427             :     // Equal initialization state, same type, same values
     428           1 :     ptr_lhs = std::make_shared<ExtNetInfo>();
     429           2 :     BOOST_CHECK(ptr_lhs->IsEmpty() && ptr_rhs->IsEmpty());
     430           1 :     BOOST_CHECK(util::shared_ptr_equal(ptr_lhs, ptr_rhs) && !util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     431             : 
     432             :     // Equal initialization state, same type, differing values
     433           1 :     BOOST_CHECK_EQUAL(ptr_rhs->AddEntry(NetInfoPurpose::CORE_P2P, "1.1.1.1:9999"), NetInfoStatus::Success);
     434           2 :     BOOST_CHECK(!util::shared_ptr_equal(ptr_lhs, ptr_rhs) && util::shared_ptr_not_equal(ptr_lhs, ptr_rhs));
     435           1 : }
     436             : 
     437         149 : BOOST_AUTO_TEST_CASE(domainport_rules)
     438             : {
     439           1 :     static const std::vector<std::pair</*addr=*/std::string, /*retval=*/DomainPort::Status>> domain_vals{
     440             :         // Domain name labels can be as small as one character long and remain valid
     441           1 :         {"r.server-1.ab.cd", DomainPort::Status::Success},
     442             :         // Domain names labels can trail with numbers or consist entirely of numbers due to RFC 1123
     443           1 :         {"9998.9example7.ab", DomainPort::Status::Success},
     444             :         // dotless domains prohibited
     445           1 :         {"abcd", DomainPort::Status::BadDotless},
     446             :         // no empty label (trailing delimiter)
     447           1 :         {"abc.", DomainPort::Status::BadCharPos},
     448             :         // no empty label (leading delimiter)
     449           1 :         {".abc", DomainPort::Status::BadCharPos},
     450             :         // no empty label (extra delimiters)
     451           1 :         {"a..dot..b", DomainPort::Status::BadLabelLen},
     452             :         // ' is not a valid character in domains
     453           1 :         {"somebody's macbook pro.local", DomainPort::Status::BadChar},
     454             :         // spaces are not a valid character in domains
     455           1 :         {"somebodys macbook pro.local", DomainPort::Status::BadChar},
     456             :         // trailing hyphens are not allowed
     457           1 :         {"-a-.bc.de", DomainPort::Status::BadLabelCharPos},
     458             :         // 2 (characters in domain) < 3 (minimum length)
     459           1 :         {"ac", DomainPort::Status::BadLen},
     460             :         // 278 (characters in domain) > 253 (maximum limit)
     461           1 :         {"Loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtempor"
     462             :          "incididuntutlaboreetdoloremagnaaliquaUtenimadminimveniamquisnostrud"
     463             :          "exercitationullamcolaborisnisiutaliquipexeacommodoconsequatDuisaute"
     464           1 :          "iruredolorinreprehenderitinvoluptatevelitessecillumdoloreeufugiatnullapariat.ur", DomainPort::Status::BadLen},
     465             :         // 64 (characters in label) > 63 (maximum limit)
     466           1 :         {"loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtempo.ri.nc", DomainPort::Status::BadLabelLen},
     467             :     };
     468             : 
     469          37 :     for (const auto& [addr, retval] : domain_vals) {
     470          12 :         DomainPort domain;
     471          12 :         ExtNetInfo netInfo;
     472          12 :         BOOST_CHECK_EQUAL(domain.Set(addr, 443), retval);
     473          12 :         if (retval != DomainPort::Status::Success) {
     474          10 :             BOOST_CHECK_EQUAL(domain.Validate(), DomainPort::Status::Malformed); // Empty values report as Malformed
     475          10 :             BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::PLATFORM_HTTPS, domain.ToStringAddrPort()),
     476             :                               NetInfoStatus::BadInput);
     477          10 :         } else {
     478           2 :             BOOST_CHECK_EQUAL(domain.Validate(), DomainPort::Status::Success);
     479           2 :             BOOST_CHECK_EQUAL(netInfo.AddEntry(NetInfoPurpose::PLATFORM_HTTPS, domain.ToStringAddrPort()), NetInfoStatus::Success);
     480             :         }
     481          12 :     }
     482             : 
     483             :     {
     484             :         // DomainPort requires non-zero ports
     485           1 :         DomainPort domain;
     486           1 :         BOOST_CHECK_EQUAL(domain.Set("example.com", 0), DomainPort::Status::BadPort);
     487           1 :         BOOST_CHECK_EQUAL(domain.Validate(), DomainPort::Status::Malformed);
     488           1 :     }
     489             : 
     490             :     {
     491             :         // DomainPort stores the domain in lower-case
     492           1 :         DomainPort lhs, rhs;
     493           1 :         BOOST_CHECK_EQUAL(lhs.Set("example.com", 9999), DomainPort::Status::Success);
     494           1 :         BOOST_CHECK_EQUAL(rhs.Set(ToUpper("example.com"), 9999), DomainPort::Status::Success);
     495           1 :         BOOST_CHECK_EQUAL(lhs.ToStringAddr(), rhs.ToStringAddr());
     496           1 :         BOOST_CHECK(lhs == rhs);
     497           1 :     }
     498           1 : }
     499             : 
     500         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16