diff --git a/src/streams.cpp b/src/streams.cpp index 0364c2134f4..e38b9592942 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -57,6 +57,20 @@ int64_t AutoFile::tell() return *m_position; } +int64_t AutoFile::size() +{ + if (IsNull()) { + throw std::ios_base::failure("AutoFile::size: file handle is nullptr"); + } + // Temporarily save the current position + int64_t current_pos = tell(); + seek(0, SEEK_END); + int64_t file_size = tell(); + // Restore the original position + seek(current_pos, SEEK_SET); + return file_size; +} + void AutoFile::read(std::span dst) { if (detail_fread(dst) != dst.size()) { diff --git a/src/streams.h b/src/streams.h index 36af8dd6159..466084e9fa0 100644 --- a/src/streams.h +++ b/src/streams.h @@ -435,6 +435,9 @@ public: /** Find position within the file. Will throw if unknown. */ int64_t tell(); + /** Return the size of the file. Will throw if unknown. */ + int64_t size(); + /** Wrapper around FileCommit(). */ bool Commit(); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index a088bf9df7b..996f25869bd 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -19,6 +19,7 @@ #include using namespace std::literals; +using namespace util::hex_literals; BOOST_FIXTURE_TEST_SUITE(netbase_tests, BasicTestingSetup) @@ -610,4 +611,56 @@ BOOST_AUTO_TEST_CASE(isbadport) BOOST_CHECK_EQUAL(std::ranges::count_if(ports, IsBadPort), 85); } + +BOOST_AUTO_TEST_CASE(asmap_test_vectors) +{ + // Randomly generated encoded ASMap with 128 ranges, up to 20-bit AS numbers. + constexpr auto ASMAP_DATA{ + "fd38d50f7d5d665357f64bba6bfc190d6078a7e68e5d3ac032edf47f8b5755f87881bfd3633d9aa7c1fa279b3" + "6fe26c63bbc9de44e0f04e5a382d8e1cddbe1c26653bc939d4327f287e8b4d1f8aff33176787cb0ff7cb28e3f" + "daef0f8f47357f801c9f7ff7a99f7f9c9f99de7f3156ae00f23eb27a303bc486aa3ccc31ec19394c2f8a53ddd" + "ea3cc56257f3b7e9b1f488be9c1137db823759aa4e071eef2e984aaf97b52d5f88d0f373dd190fe45e06efef1" + "df7278be680a73a74c76db4dd910f1d30752c57fe2bc9f079f1a1e1b036c2a69219f11c5e11980a3fa51f4f82" + "d36373de73b1863a8c27e36ae0e4f705be3d76ecff038a75bc0f92ba7e7f6f4080f1c47c34d095367ecf4406c" + "1e3bbc17ba4d6f79ea3f031b876799ac268b1e0ea9babf0f9a8e5f6c55e363c6363df46afc696d7afceaf49b6" + "e62df9e9dc27e70664cafe5c53df66dd0b8237678ada90e73f05ec60e6f6e96c3cbb1ea2f9dece115d5bdba10" + "33e53662a7d72a29477b5beb35710591d3e23e5f0379baea62ffdee535bcdf879cbf69b88d7ea37c8015381cf" + "63dc33d28f757a4a5e15d6a08"_hex}; + + // Convert to std::vector format that the ASMap interpreter uses. + std::vector asmap_bits; + asmap_bits.reserve(ASMAP_DATA.size() * 8); + for (auto byte : ASMAP_DATA) { + for (int bit = 0; bit < 8; ++bit) { + asmap_bits.push_back((std::to_integer(byte) >> bit) & 1); + } + } + + // Construct NetGroupManager with this data. + NetGroupManager netgroup{std::move(asmap_bits)}; + BOOST_CHECK(netgroup.UsingASMap()); + + // Check some randomly-generated IPv6 addresses in it (biased towards the very beginning and + // very end of the 128-bit range). + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("0:1559:183:3728:224c:65a5:62e6:e991", false)), 961340); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("d0:d493:faa0:8609:e927:8b75:293c:f5a4", false)), 961340); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("2a0:26f:8b2c:2ee7:c7d1:3b24:4705:3f7f", false)), 693761); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("a77:7cd4:4be5:a449:89f2:3212:78c6:ee38", false)), 0); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("1336:1ad6:2f26:4fe3:d809:7321:6e0d:4615", false)), 672176); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("1d56:abd0:a52f:a8d5:d5a7:a610:581d:d792", false)), 499880); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("378e:7290:54e5:bd36:4760:971c:e9b9:570d", false)), 0); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("406c:820b:272a:c045:b74e:fc0a:9ef2:cecc", false)), 248495); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("46c2:ae07:9d08:2d56:d473:2bc7:57e3:20ac", false)), 248495); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("50d2:3db6:52fa:2e7:12ec:5bc4:1bd1:49f9", false)), 124471); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("53e1:1812:ffa:dccf:f9f2:64be:75fa:795", false)), 539993); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("544d:eeba:3990:35d1:ad66:f9a3:576d:8617", false)), 374443); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("6a53:40dc:8f1d:3ffa:efeb:3aa3:df88:b94b", false)), 435070); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("87aa:d1c9:9edb:91e7:aab1:9eb9:baa0:de18", false)), 244121); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("9f00:48fa:88e3:4b67:a6f3:e6d2:5cc1:5be2", false)), 862116); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("c49f:9cc6:86ad:ba08:4580:315e:dbd1:8a62", false)), 969411); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("dff5:8021:61d:b17d:406d:7888:fdac:4a20", false)), 969411); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("e888:6791:2960:d723:bcfd:47e1:2d8c:599f", false)), 824019); + BOOST_CHECK_EQUAL(netgroup.GetMappedAS(*LookupHost("ffff:d499:8c4b:4941:bc81:d5b9:b51e:85a8", false)), 824019); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 43d06a7d0bf..af75ee987ad 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -101,6 +101,7 @@ BOOST_AUTO_TEST_CASE(xor_file) BOOST_CHECK_EXCEPTION(xor_file << std::byte{}, std::ios_base::failure, HasReason{"AutoFile::write: file handle is nullptr"}); BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"AutoFile::read: file handle is nullptr"}); BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: file handle is nullptr"}); + BOOST_CHECK_EXCEPTION(xor_file.size(), std::ios_base::failure, HasReason{"AutoFile::size: file handle is nullptr"}); } { #ifdef __MINGW64__ @@ -111,6 +112,7 @@ BOOST_AUTO_TEST_CASE(xor_file) #endif AutoFile xor_file{raw_file(mode), obfuscation}; xor_file << test1 << test2; + BOOST_CHECK_EQUAL(xor_file.size(), 7); BOOST_REQUIRE_EQUAL(xor_file.fclose(), 0); } { @@ -121,6 +123,7 @@ BOOST_AUTO_TEST_CASE(xor_file) BOOST_CHECK_EQUAL(HexStr(raw), "fc01fd03fd04fa"); // Check that no padding exists BOOST_CHECK_EXCEPTION(non_xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: end of file"}); + BOOST_CHECK_EQUAL(non_xor_file.size(), 7); } { AutoFile xor_file{raw_file("rb"), obfuscation}; @@ -130,6 +133,7 @@ BOOST_AUTO_TEST_CASE(xor_file) BOOST_CHECK_EQUAL(HexStr(read2), HexStr(test2)); // Check that eof was reached BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"AutoFile::read: end of file"}); + BOOST_CHECK_EQUAL(xor_file.size(), 7); } { AutoFile xor_file{raw_file("rb"), obfuscation}; @@ -141,6 +145,7 @@ BOOST_AUTO_TEST_CASE(xor_file) // Check that ignore and read fail now BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: end of file"}); BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"AutoFile::read: end of file"}); + BOOST_CHECK_EQUAL(xor_file.size(), 7); } } @@ -781,4 +786,41 @@ BOOST_AUTO_TEST_CASE(streams_hashed) BOOST_CHECK_EQUAL(hash_writer.GetHash(), hash_verifier.GetHash()); } +BOOST_AUTO_TEST_CASE(size_preserves_position) +{ + const fs::path path = m_args.GetDataDirBase() / "size_pos_test.bin"; + AutoFile f{fsbridge::fopen(path, "w+b")}; + for (uint8_t j = 0; j < 10; ++j) { + f << j; + } + + // Test that usage of size() does not change the current position + // + // Case: Pos at beginning of the file + f.seek(0, SEEK_SET); + (void)f.size(); + uint8_t first{}; + f >> first; + BOOST_CHECK_EQUAL(first, 0); + + // Case: Pos at middle of the file + f.seek(0, SEEK_SET); + // Move pos to middle + f.ignore(4); + (void)f.size(); + uint8_t middle{}; + f >> middle; + // Pos still at 4 + BOOST_CHECK_EQUAL(middle, 4); + + // Case: Pos at EOF + f.seek(0, SEEK_END); + (void)f.size(); + uint8_t end{}; + BOOST_CHECK_EXCEPTION(f >> end, std::ios_base::failure, HasReason{"AutoFile::read: end of file"}); + + BOOST_REQUIRE_EQUAL(f.fclose(), 0); + fs::remove(path); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index bd6852645db..4c20bd819f7 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -200,13 +200,11 @@ std::vector DecodeAsmap(fs::path path) FILE *filestr = fsbridge::fopen(path, "rb"); AutoFile file{filestr}; if (file.IsNull()) { - LogPrintf("Failed to open asmap file from disk\n"); + LogWarning("Failed to open asmap file from disk"); return bits; } - file.seek(0, SEEK_END); - int length = file.tell(); + int64_t length{file.size()}; LogInfo("Opened asmap file %s (%d bytes) from disk", fs::quoted(fs::PathToString(path)), length); - file.seek(0, SEEK_SET); uint8_t cur_byte; for (int i = 0; i < length; ++i) { file >> cur_byte; @@ -215,9 +213,8 @@ std::vector DecodeAsmap(fs::path path) } } if (!SanityCheckASMap(bits, 128)) { - LogPrintf("Sanity check of asmap file %s failed\n", fs::quoted(fs::PathToString(path))); + LogWarning("Sanity check of asmap file %s failed", fs::quoted(fs::PathToString(path))); return {}; } return bits; } - diff --git a/src/wallet/migrate.cpp b/src/wallet/migrate.cpp index 603a0d2bab1..5e202d8df45 100644 --- a/src/wallet/migrate.cpp +++ b/src/wallet/migrate.cpp @@ -544,8 +544,7 @@ void BerkeleyRODatabase::Open() page_size = outer_meta.pagesize; // Verify the size of the file is a multiple of the page size - db_file.seek(0, SEEK_END); - int64_t size = db_file.tell(); + const int64_t size{db_file.size()}; // Since BDB stores everything in a page, the file size should be a multiple of the page size; // However, BDB doesn't actually check that this is the case, and enforcing this check results