From 5ca82b771b12c1690058a06c96a5deae457a10f0 Mon Sep 17 00:00:00 2001 From: Alan King Date: Fri, 15 Sep 2023 18:49:40 +0000 Subject: [PATCH] [ 7098] Add utility to generate random alphanumeric strings Adds a function which randomly generates an alphanumeric character and concatenates a string which is returned to the user. Unit tests included. --- lib/core/include/irods/irods_random.hpp | 11 ++++ lib/core/src/irods_random.cpp | 52 ++++++++++++++++++- unit_tests/CMakeLists.txt | 1 + .../cmake/test_config/irods_random.cmake | 9 ++++ unit_tests/src/test_random.cpp | 34 ++++++++++++ unit_tests/unit_tests_list.json | 1 + 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 unit_tests/cmake/test_config/irods_random.cmake create mode 100644 unit_tests/src/test_random.cpp diff --git a/lib/core/include/irods/irods_random.hpp b/lib/core/include/irods/irods_random.hpp index 78cda4d4f1..226d0c02b9 100644 --- a/lib/core/include/irods/irods_random.hpp +++ b/lib/core/include/irods/irods_random.hpp @@ -1,9 +1,20 @@ #ifndef IRODS_RANDOM_HPP #define IRODS_RANDOM_HPP +#include + namespace irods { void getRandomBytes( void * buf, int bytes ); + /// \brief Generate a string of size \p _length consisting of randomly generated alphanumeric characters. + /// + /// \param[in] _length Desired length of the generated string. + /// + /// \throws irods::exception If \p _length exceeds max_size or on allocation failure. + /// + /// \since 4.3.1 + auto generate_random_alphanumeric_string(std::size_t _length) -> std::string; + template T getRandom() { diff --git a/lib/core/src/irods_random.cpp b/lib/core/src/irods_random.cpp index 663d95eab2..7e9907c8bf 100644 --- a/lib/core/src/irods_random.cpp +++ b/lib/core/src/irods_random.cpp @@ -1,7 +1,34 @@ +#include "irods/irods_random.hpp" + +#include "irods/irods_exception.hpp" +#include "irods/rodsErrorTable.h" + #include +#include #include -#include "irods/irods_random.hpp" + #include +#include +#include + +namespace +{ + auto generate_random_alphanumeric_character() -> char + { + constexpr char min_char = '0'; + constexpr char max_char = 'z'; + + static std::random_device rd; + static std::default_random_engine e{rd()}; + static std::uniform_int_distribution<> d{min_char, max_char}; + + char c{}; + while (!std::isalnum(c)) { + c = d(e); + } + return c; + } // generate_random_alphanumeric_character +} // anonymous namespace void irods::getRandomBytes( void * buf, int bytes ) { if ( RAND_bytes( ( unsigned char * )buf, bytes ) != 1 ) { @@ -13,3 +40,26 @@ void irods::getRandomBytes( void * buf, int bytes ) { } } } + +auto irods::generate_random_alphanumeric_string(std::size_t _length) -> std::string +{ + try { + std::string s; + s.reserve(_length); + + for (std::size_t i = 0; i < _length; ++i) { + s += generate_random_alphanumeric_character(); + } + + return s; + } + catch (const std::length_error& e) { + THROW(SYS_INVALID_INPUT_PARAM, fmt::format("{}: Invalid length. [{}]", __func__, e.what())); + } + catch (const std::exception& e) { + THROW(SYS_LIBRARY_ERROR, fmt::format("{}: Failed to generate string. [{}]", __func__, e.what())); + } + catch (...) { + THROW(SYS_UNKNOWN_ERROR, "Failed to generate string: [Unknown error]"); + } +} // generate_random_alphanumeric_string diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 188c0c209b..d3ae4d1451 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -63,6 +63,7 @@ set( parallel_transfer_engine process_stash query_builder + random rcConnect rcTicketAdmin rc_check_auth_credentials diff --git a/unit_tests/cmake/test_config/irods_random.cmake b/unit_tests/cmake/test_config/irods_random.cmake new file mode 100644 index 0000000000..64b9b0edf5 --- /dev/null +++ b/unit_tests/cmake/test_config/irods_random.cmake @@ -0,0 +1,9 @@ +set(IRODS_TEST_TARGET irods_random) + +set(IRODS_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test_random.cpp) + +set(IRODS_TEST_INCLUDE_PATH ${IRODS_EXTERNALS_FULLPATH_FMT}/include) + +set(IRODS_TEST_LINK_LIBRARIES irods_common + ${IRODS_EXTERNALS_FULLPATH_FMT}/lib/libfmt.so) diff --git a/unit_tests/src/test_random.cpp b/unit_tests/src/test_random.cpp new file mode 100644 index 0000000000..0171f68793 --- /dev/null +++ b/unit_tests/src/test_random.cpp @@ -0,0 +1,34 @@ +#include + +#include "irods/irods_exception.hpp" +#include "irods/irods_random.hpp" + +#include +#include +#include + +TEST_CASE("test_generate_random_alphanumeric_string") +{ + SECTION("zero length") + { + CHECK(irods::generate_random_alphanumeric_string(0).empty()); + } + + SECTION("one character") + { + constexpr std::size_t character_count = 1; + const auto s = irods::generate_random_alphanumeric_string(character_count); + REQUIRE(!s.empty()); + CHECK(s.size() == character_count); + CHECK(std::all_of(std::begin(s), std::end(s), [](const auto& c) { return std::isalnum(c); })); + } + + SECTION("a lot of characters") + { + constexpr std::size_t character_count = 1'000'000; + const auto s = irods::generate_random_alphanumeric_string(character_count); + REQUIRE(!s.empty()); + CHECK(s.size() == character_count); + CHECK(std::all_of(std::begin(s), std::end(s), [](const auto& c) { return std::isalnum(c); })); + } +} diff --git a/unit_tests/unit_tests_list.json b/unit_tests/unit_tests_list.json index 5355a6ba68..822657d4f8 100644 --- a/unit_tests/unit_tests_list.json +++ b/unit_tests/unit_tests_list.json @@ -32,6 +32,7 @@ "irods_parallel_transfer_engine", "irods_process_stash", "irods_query_builder", + "irods_random", "irods_rcConnect", "irods_rcTicketAdmin", "irods_rc_check_auth_credentials",