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()
|