diff --git a/include/boost/crypt/hash/md5.hpp b/include/boost/crypt/hash/md5.hpp index cad1710c..3458e127 100644 --- a/include/boost/crypt/hash/md5.hpp +++ b/include/boost/crypt/hash/md5.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #ifndef BOOST_CRYPT_BUILD_MODULE #include @@ -550,28 +551,91 @@ inline auto md5(const std::wstring& str) noexcept -> boost::crypt::array boost::crypt::array +inline auto md5(std::string_view str) -> boost::crypt::array { return detail::md5(str.begin(), str.end()); } -inline auto md5(const std::u16string_view& str) -> boost::crypt::array +inline auto md5(std::u16string_view str) -> boost::crypt::array { return detail::md5(str.begin(), str.end()); } -inline auto md5(const std::u32string_view& str) -> boost::crypt::array +inline auto md5(std::u32string_view str) -> boost::crypt::array { return detail::md5(str.begin(), str.end()); } -inline auto md5(const std::wstring_view& str) -> boost::crypt::array +inline auto md5(std::wstring_view str) -> boost::crypt::array { return detail::md5(str.begin(), str.end()); } #endif // BOOST_CRYPT_HAS_STRING_VIEW +// ---- CUDA also does not have the ability to consume files ----- + +namespace detail { + +template +auto md5_file_impl(utility::file_reader& reader) noexcept -> boost::crypt::array +{ + md5_hasher hasher; + while (!reader.eof()) + { + const auto buffer_iter {reader.read_next_block()}; + const auto len {reader.get_bytes_read()}; + hasher.process_bytes(buffer_iter, len); + } + + return hasher.get_digest(); +} + +} // namespace detail + +inline auto md5_file(const std::string& filepath) noexcept -> boost::crypt::array +{ + try + { + utility::file_reader<64U> reader(filepath); + return detail::md5_file_impl(reader); + } + catch (const std::runtime_error&) + { + return boost::crypt::array{}; + } +} + +inline auto md5_file(const char* filepath) noexcept -> boost::crypt::array +{ + try + { + utility::file_reader<64U> reader(filepath); + return detail::md5_file_impl(reader); + } + catch (const std::runtime_error&) + { + return boost::crypt::array{}; + } +} + +#ifdef BOOST_CRYPT_HAS_STRING_VIEW + +inline auto md5_file(std::string_view filepath) noexcept -> boost::crypt::array +{ + try + { + utility::file_reader<64U> reader(filepath); + return detail::md5_file_impl(reader); + } + catch (const std::runtime_error&) + { + return boost::crypt::array{}; + } +} + +#endif // BOOST_CRYPT_HAS_STRING_VIEW + #endif // BOOST_CRYPT_HAS_CUDA } // namespace crypt diff --git a/include/boost/crypt/utility/file.hpp b/include/boost/crypt/utility/file.hpp new file mode 100644 index 00000000..fdf7f27a --- /dev/null +++ b/include/boost/crypt/utility/file.hpp @@ -0,0 +1,86 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CRYPT_UTILITY_FILE_HPP +#define BOOST_CRYPT_UTILITY_FILE_HPP + +#include +#include + +#ifndef BOOST_CRYPT_BUILD_MODULE +#include +#include +#include +#include +#include +#endif + +namespace boost { +namespace crypt { +namespace utility { + +template +class file_reader +{ +private: + std::ifstream fd; + std::array buffer_ {}; + +public: + explicit file_reader(const std::string& filename) : fd(filename, std::ios::binary | std::ios::in) + { + if (!fd.is_open()) + { + throw std::runtime_error("Error opening file: " + filename); + } + } + + explicit file_reader(const char* filename) : fd(filename, std::ios::binary | std::ios::in) + { + if (!fd.is_open()) + { + throw std::runtime_error("Error opening file"); + } + } + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + explicit file_reader(std::string_view filename) : fd(filename.data(), std::ios::binary | std::ios::in) + { + if (!fd.is_open()) + { + throw std::runtime_error("Error opening file"); + } + } + #endif + + auto read_next_block() + { + fd.read(reinterpret_cast(buffer_.data()), block_size); + return buffer_.begin(); + } + + auto get_bytes_read() const -> std::size_t + { + return static_cast(fd.gcount()); + } + + auto eof() const -> bool + { + return fd.eof(); + } + + ~file_reader() + { + if (fd.is_open()) + { + fd.close(); + } + } +}; + +} // namespace utility +} // namespace crypt +} // namespace boost + +#endif //BOOST_CRYPT_UTILITY_FILE_HPP diff --git a/test/test_file_1.txt b/test/test_file_1.txt new file mode 100644 index 00000000..2fe6575e --- /dev/null +++ b/test/test_file_1.txt @@ -0,0 +1 @@ +The quick brown fox jumps over the lazy dog. diff --git a/test/test_file_2.txt b/test/test_file_2.txt new file mode 100644 index 00000000..45bb78df --- /dev/null +++ b/test/test_file_2.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras leo purus, faucibus id risus pulvinar, condimentum molestie justo. Nulla sit amet pulvinar magna. In at quam id leo scelerisque posuere. Nullam porttitor auctor vestibulum. Nullam ac velit orci. Quisque semper hendrerit tortor, eu vulputate mauris vestibulum eget. Quisque nunc neque, posuere ut tortor a, suscipit tristique diam. Maecenas nec elit turpis. Nullam sapien enim, rhoncus et aliquet id, accumsan ac justo. Maecenas eu diam eget lorem vehicula lacinia eget sit amet orci. Ut lobortis magna arcu, pharetra lacinia est lobortis ut. Nam sagittis ex et magna maximus volutpat. Praesent lacinia felis neque. Vestibulum velit nisl, ullamcorper eu hendrerit eget, aliquet vitae ante. Mauris scelerisque blandit felis sed pharetra. + +Etiam a sapien at arcu cursus malesuada. Mauris ut quam velit. Praesent rutrum, neque ut vehicula hendrerit, lorem libero malesuada neque, sed sodales turpis lorem fermentum erat. Vestibulum in eleifend erat, nec blandit libero. Etiam aliquam lacus sit amet nisl cursus, ut consequat orci dignissim. Proin varius lectus augue, a euismod quam euismod at. Nullam augue sapien, finibus viverra ante ac, ultricies eleifend ipsum. Mauris ullamcorper eros nulla, sed porta nulla imperdiet sed. Sed ac massa dui. Pellentesque tellus ligula, posuere quis enim hendrerit, suscipit eleifend felis. Vivamus consectetur feugiat orci a faucibus. Morbi tristique, ex sit amet mollis laoreet, ligula ligula tempor lorem, sed posuere nisl sem at nunc. Morbi a sodales justo. Sed efficitur nibh vitae turpis aliquam semper nec in urna. + +In neque nisl, malesuada eu tristique non, euismod a diam. Sed mattis scelerisque consectetur. Sed in molestie libero, quis porta felis. Curabitur vel augue mauris. Aliquam dignissim facilisis bibendum. Vestibulum ut dignissim metus, ut fermentum elit. Etiam eu arcu id massa tristique semper. Ut lobortis neque eget hendrerit pretium. Nulla congue justo nec nibh cursus mattis. Morbi libero urna, sagittis ac risus vel, rutrum finibus purus. + +Nam mattis fringilla justo eget pretium. Vivamus quis facilisis tortor. Aenean blandit elit eu mollis lacinia. Cras orci odio, aliquet eget lacus ac, feugiat lacinia magna. Pellentesque vel urna congue metus faucibus accumsan. Donec ac tortor feugiat nibh maximus imperdiet non id lectus. Duis commodo, purus eu suscipit porttitor, lorem purus faucibus enim, ac ultricies nulla massa vitae magna. Vestibulum maximus enim ante, quis lacinia magna molestie nec. Donec eleifend sapien at risus iaculis rhoncus. Phasellus sit amet urna pulvinar, commodo mi ut, feugiat sapien. Nullam id felis nec turpis commodo laoreet. + +Vivamus quis felis pretium, bibendum turpis vitae, maximus nulla. Phasellus a elit id erat lacinia lacinia. Nunc in feugiat libero. Morbi sodales quam eget sem egestas varius ut quis orci. In hac habitasse platea dictumst. Sed sollicitudin vestibulum faucibus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam rhoncus nulla elit, id egestas arcu posuere vulputate. Nullam sit amet leo iaculis, viverra nisi nec, efficitur nisi. Fusce nec dui ultricies, ultricies est ac, pretium est. diff --git a/test/test_md5.cpp b/test/test_md5.cpp index 20091ab3..ab4429e5 100644 --- a/test/test_md5.cpp +++ b/test/test_md5.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -295,6 +296,125 @@ void test_random_piecewise_values() delete[] str_2; } +template +void test_file(T filename, const std::array& res) +{ + const auto crypt_res {boost::crypt::md5_file(filename)}; + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with file: " << filename << std::endl; + break; + // LCOV_EXCL_STOP + } + } +} + +template +void test_invalid_file(T filename) +{ + constexpr std::array res{}; + + const auto crypt_res {boost::crypt::md5_file(filename)}; + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with file: " << filename << std::endl; + break; + // LCOV_EXCL_STOP + } + } +} + +void files_test() +{ + // Based off where we are testing from (test vs boost_root) we need to adjust our filepath + const char* filename; + const char* filename_2; + + // Boost-root + std::ifstream fd("libs/crypt/test/test_file_1.txt", std::ios::binary | std::ios::in); + filename = "libs/crypt/test/test_file_1.txt"; + filename_2 = "libs/crypt/test/test_file_2.txt"; + + // LCOV_EXCL_START + if (!fd.is_open()) + { + // Local test directory or IDE + std::ifstream fd2("test_file_1.txt", std::ios::binary | std::ios::in); + filename = "test_file_1.txt"; + filename_2 = "test_file_2.txt"; + + if (!fd2.is_open()) + { + // test/cover + std::ifstream fd3("../test_file_1.txt", std::ios::binary | std::ios::in); + filename = "../test_file_1.txt"; + filename_2 = "../test_file_2.txt"; + + if (!fd3.is_open()) + { + std::cerr << "Test not run due to file system issues" << std::endl; + return; + } + else + { + fd3.close(); + } + } + else + { + fd2.close(); + } + } + else + { + fd.close(); + } + // LCOV_EXCL_STOP + + // On macOS 15 + // md5 test_file_1.txt + // MD5 (test_file_1.txt) = 0d7006cd055e94cf614587e1d2ae0c8e + constexpr std::array res{0x0d, 0x70, 0x06, 0xcd, 0x05, 0x5e, 0x94, 0xcf, + 0x61, 0x45, 0x87, 0xe1, 0xd2, 0xae, 0x0c, 0x8e}; + + test_file(filename, res); + + const std::string str_filename {filename}; + test_file(str_filename, res); + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + const std::string_view str_view_filename {str_filename}; + test_file(str_view_filename, res); + #endif + + const auto invalid_filename = "broken.bin"; + test_invalid_file(invalid_filename); + + const std::string str_invalid_filename {invalid_filename}; + test_invalid_file(str_invalid_filename); + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + const std::string_view str_view_invalid_filename {str_invalid_filename}; + test_invalid_file(str_view_invalid_filename); + #endif + + // On macOS 15 + // md5 test_file_2.txt + // MD5 (test_file_2.txt) = 530e67fa4b01e3ccaee8eca9916a814c + constexpr std::array res_2{0x53, 0x0e, 0x67, 0xfa, 0x4b, 0x01, 0xe3, 0xcc, + 0xae, 0xe8, 0xec, 0xa9, 0x91, 0x6a, 0x81, 0x4c}; + + test_file(filename_2, res_2); +} + int main() { basic_tests(); @@ -318,5 +438,10 @@ int main() test_random_values(); test_random_piecewise_values(); + // The Windows file system returns a different result than on UNIX platforms + #if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) + files_test(); + #endif + return boost::report_errors(); }