diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 4d26ba7cf86..4207ba1c9aa 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -72,11 +72,11 @@ class Permission isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const; // for tx level permission, permission value is equal to tx type plus one - static uint32_t + [[nodiscard]] static uint32_t txToPermissionType(TxType const& type); // tx type value is permission value minus one - static TxType + [[nodiscard]] static std::optional permissionToTxType(uint32_t const& value); }; diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index 21efe7a8cf1..922cce89913 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -104,9 +105,11 @@ Permission::getPermissionName(std::uint32_t const value) const return granular; // not a granular permission, check if it maps to a transaction type - auto const txType = permissionToTxType(value); - if (auto const* item = TxFormats::getInstance().findByType(txType); item != nullptr) - return item->getName(); + if (auto const txType = permissionToTxType(value)) + { + if (auto const* item = TxFormats::getInstance().findByType(*txType); item != nullptr) + return item->getName(); + } return std::nullopt; } @@ -166,12 +169,15 @@ Permission::isDelegable(std::uint32_t const& permissionValue, Rules const& rules } auto const txType = permissionToTxType(permissionValue); - auto const it = delegableTx_.find(txType); + if (!txType) + return false; + + auto const it = delegableTx_.find(*txType); if (it == delegableTx_.end()) return false; - auto const txFeaturesIt = txFeatureMap_.find(txType); + auto const txFeaturesIt = txFeatureMap_.find(*txType); XRPL_ASSERT( txFeaturesIt != txFeatureMap_.end(), "xrpl::Permissions::isDelegable : tx exists in txFeatureMap_"); @@ -194,9 +200,14 @@ Permission::txToPermissionType(TxType const& type) return static_cast(type) + 1; } -TxType +std::optional Permission::permissionToTxType(uint32_t const& value) { + // Defensive check: values outside this range would silently truncate when cast to + // uint16_t, for example, 65537 would become 1, mapping to the Payment transaction. + if (value == 0 || value > std::numeric_limits::max() + 1u) + return std::nullopt; + return static_cast(value - 1); } diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 5ed8c791468..1753a3db99f 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include #include @@ -1915,6 +1916,38 @@ class Delegate_test : public beast::unit_test::suite BEAST_EXPECT(granularPermissions.empty()); } + void + testPermissionToTxType() + { + testcase("test Permission to Tx type"); + + // 0 is not a valid permission value + BEAST_EXPECT(!Permission::permissionToTxType(0)); + + // 1 maps to Payment transaction + BEAST_EXPECT(Permission::permissionToTxType(1) == ttPAYMENT); + + // UINT16_MAX+1 is the maximum possible tx-level permission value + constexpr uint32_t maxTxPermission = std::numeric_limits::max() + 1u; + BEAST_EXPECT(Permission::permissionToTxType(maxTxPermission).has_value()); + + // exceeding maximum value should return nullopt + BEAST_EXPECT(!Permission::permissionToTxType(maxTxPermission + 1)); + + // All granular permission values should return nullopt since they do not map to a TxType. + for (auto const gp : { +#pragma push_macro("PERMISSION") +#undef PERMISSION +#define PERMISSION(type, txType, value) GranularPermissionType::type, +#include +#undef PERMISSION +#pragma pop_macro("PERMISSION") + }) + { + BEAST_EXPECT(!Permission::permissionToTxType(static_cast(gp))); + } + } + void run() override { @@ -1941,6 +1974,7 @@ class Delegate_test : public beast::unit_test::suite testTxRequireFeatures(all); testTxDelegableCount(); testDelegateUtilsNullptrCheck(); + testPermissionToTxType(); } }; BEAST_DEFINE_TESTSUITE(Delegate, app, xrpl);