From f93165c0bc5a8ac4f88d80ca49f499dc2faeae84 Mon Sep 17 00:00:00 2001 From: Yat Ho Date: Sat, 3 Jan 2026 01:38:54 +0800 Subject: [PATCH] feat: unify encryption mode serialization format for settings.json and rpc (#8032) * feat: `settings.json` `encryption` is now string * feat: api-compat for encryption in `settings.json` * feat: rpc now uses the same strings as `settings.json` for encryption modes * feat: api-compat for encryption in rpc * code review: use `to_variant()` --- docs/Editing-Configuration-Files.md | 2 +- docs/rpc-spec.md | 4 +- libtransmission/api-compat.cc | 101 ++++++++++++++ libtransmission/rpcimpl.cc | 41 ++---- libtransmission/serializer.cc | 10 ++ libtransmission/session.h | 12 ++ tests/libtransmission/api-compat-test.cc | 165 ++++++++++++++++++++++- tests/libtransmission/settings-test.cc | 9 +- 8 files changed, 303 insertions(+), 41 deletions(-) diff --git a/docs/Editing-Configuration-Files.md b/docs/Editing-Configuration-Files.md index 7028f8e80..4ab327fc4 100644 --- a/docs/Editing-Configuration-Files.md +++ b/docs/Editing-Configuration-Files.md @@ -89,7 +89,7 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri * **cache_size_mib:** Number (default = 4), in MiB, to allocate for Transmission's memory cache. The cache is used to help batch disk IO together, so increasing the cache size can be used to reduce the number of disk reads and writes. The value is the total available to the Transmission instance. Set it to the smallest value tolerable by the random access performance of your storage medium to minimize data loss in case Transmission quit unexpectedly. Setting this to 0 bypasses the cache, which may be useful if your filesystem already has a cache layer that aggregates transactions. Pieces are guaranteed to be written to filesystem if sequential download is enabled. Otherwise, data might still be in cache only. * **default_trackers:** String (default = "") A list of double-newline separated tracker announce URLs. These are used for all torrents in addition to the per torrent trackers specified in the torrent file. If a tracker is only meant to be a backup, it should be separated from its main tracker by a single newline character. If a tracker should be used additionally to another tracker it should be separated by two newlines. (e.g. "udp://tracker.example.invalid:1337/announce\n\nudp://tracker.another-example.invalid:6969/announce\nhttps://backup-tracker.another-example.invalid:443/announce\n\nudp://tracker.yet-another-example.invalid:1337/announce", in this case tracker.example.invalid, tracker.another-example.invalid and tracker.yet-another-example.invalid would be used as trackers and backup-tracker.another-example.invalid as backup in case tracker.another-example.invalid is unreachable. * **dht_enabled:** Boolean (default = true) Enable [Distributed Hash Table (DHT)](https://wiki.theory.org/BitTorrentSpecification#Distributed_Hash_Table). - * **encryption:** Number (0 = Prefer unencrypted connections, 1 = Prefer encrypted connections, 2 = Require encrypted connections; default = 1) [Encryption](https://wiki.vuze.com/w/Message_Stream_Encryption) preference. Encryption may help get around some ISP filtering, but at the cost of slightly higher CPU use. + * **encryption:** String ("allowed" = Prefer unencrypted connections, "preferred" = Prefer encrypted connections, "required" = Require encrypted connections; default = "preferred") [Encryption](https://wiki.vuze.com/w/Message_Stream_Encryption) preference. Encryption may help get around some ISP filtering, but at the cost of slightly higher CPU use. * **lpd_enabled:** Boolean (default = false) Enable [Local Peer Discovery (LPD)](https://en.wikipedia.org/wiki/Local_Peer_Discovery). * **message_level:** Number (0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace; default = 4) Set verbosity of Transmission's log messages. * **pex_enabled:** Boolean (default = true) Enable [Peer Exchange (PEX)](https://en.wikipedia.org/wiki/Peer_exchange). diff --git a/docs/rpc-spec.md b/docs/rpc-spec.md index 195d67800..488d22d57 100644 --- a/docs/rpc-spec.md +++ b/docs/rpc-spec.md @@ -579,7 +579,7 @@ Response parameters: `path`, `name`, and `id`, holding the torrent ID integer | `download_dir_free_space` | number | **DEPRECATED** Use the `free_space` method instead. | `download_queue_enabled` | boolean | if true, limit how many torrents can be downloaded at once | `download_queue_size` | number | max number of torrents to download at once (see `download_queue_enabled`) -| `encryption` | string | `required`, `preferred`, `tolerated` +| `encryption` | string | `required`, `preferred`, `allowed` | `idle_seeding_limit` | number | torrents we're seeding will be stopped if they're idle for this long | `idle_seeding_limit_enabled` | boolean | true if the seeding inactivity limit is honored by default | `incomplete_dir` | string | path for incomplete torrents, when enabled @@ -1093,3 +1093,5 @@ Transmission 4.1.0 (`rpc_version_semver` 6.0.0, `rpc_version`: 18) | `torrent_get` | :bomb: `wanted` is now a boolean array instead of 1/0 | `session_get` | :bomb: renamed `cache_size_mb` to `cache_size_mib` | `session_set` | :bomb: renamed `cache_size_mb` to `cache_size_mib` +| `session_get` | :bomb: renamed `tolerated` to `allowed` in `encryption` +| `session_set` | :bomb: renamed `tolerated` to `allowed` in `encryption` diff --git a/libtransmission/api-compat.cc b/libtransmission/api-compat.cc index 609888628..57571c35c 100644 --- a/libtransmission/api-compat.cc +++ b/libtransmission/api-compat.cc @@ -12,6 +12,8 @@ #include "libtransmission/api-compat.h" #include "libtransmission/quark.h" #include "libtransmission/rpcimpl.h" +#include "libtransmission/serializer.h" +#include "libtransmission/transmission.h" #include "libtransmission/utils.h" #include "libtransmission/variant.h" @@ -406,6 +408,14 @@ auto constexpr SessionKeys = std::array{ { auto constexpr MethodNotFoundLegacyErrmsg = std::string_view{ "no method name" }; +namespace EncryptionModeString +{ +auto constexpr PreferEncryption = std::string_view{ "preferred" }; +auto constexpr RequireEncryption = std::string_view{ "required" }; +auto constexpr PreferClear = std::string_view{ "allowed" }; +auto constexpr PreferClearLegacy = std::string_view{ "tolerated" }; +} // namespace EncryptionModeString + /** * Guess the error code from a legacy RPC response message. * @@ -673,6 +683,39 @@ void convert_keys(tr_variant& var, State& state) }); } +void convert_settings_encryption(tr_variant::Map& top, State const& state) +{ + if (state.is_rpc) + { + return; + } + + if (state.style == Style::Tr4) + { + using namespace EncryptionModeString; + if (auto const encryption = top.value_if(TR_KEY_encryption); encryption == PreferEncryption) + { + top.insert_or_assign(TR_KEY_encryption, TR_ENCRYPTION_PREFERRED); + } + else if (encryption == RequireEncryption) + { + top.insert_or_assign(TR_KEY_encryption, TR_ENCRYPTION_REQUIRED); + } + else if (encryption == PreferClear) + { + top.insert_or_assign(TR_KEY_encryption, TR_CLEAR_PREFERRED); + } + } + + if (state.style == Style::Tr5) + { + if (auto const* const encryption = top.find_if(TR_KEY_encryption)) + { + top.insert_or_assign(TR_KEY_encryption, serializer::to_variant(static_cast(*encryption))); + } + } +} + namespace convert_jsonrpc_helpers { void convert_files_wanted(tr_variant::Vector& wanted, State const& state) @@ -756,6 +799,31 @@ void convert_files_wanted_response(tr_variant::Map& top, State const& state) } } } + +// --- + +void convert_encryption(tr_variant& var, State const& state) +{ + using namespace EncryptionModeString; + if (auto const val = var.value_if()) + { + switch (state.style) + { + case Style::Tr5: + if (val == PreferClearLegacy) + { + var = tr_variant::unmanaged_string(PreferClear); + } + break; + case Style::Tr4: + if (val == PreferClear) + { + var = tr_variant::unmanaged_string(PreferClearLegacy); + } + break; + } + } +} } // namespace convert_jsonrpc_helpers // jsonrpc <-> legacy rpc conversion @@ -801,6 +869,14 @@ void convert_jsonrpc(tr_variant::Map& top, State const& state) top.try_emplace(TR_KEY_result, tr_variant::unmanaged_string("success")); convert_files_wanted_response(top, state); + + if (auto* const args = top.find_if(TR_KEY_arguments)) + { + if (auto const iter = args->find(TR_KEY_encryption); iter != std::end(*args)) + { + convert_encryption(iter->second, state); + } + } } if (state.is_response && is_legacy && !state.is_success) @@ -849,6 +925,14 @@ void convert_jsonrpc(tr_variant::Map& top, State const& state) top.replace_key(TR_KEY_arguments, TR_KEY_result); convert_files_wanted_response(top, state); + + if (auto* const result = top.find_if(TR_KEY_result)) + { + if (auto const iter = result->find(TR_KEY_encryption); iter != std::end(*result)) + { + convert_encryption(iter->second, state); + } + } } if (state.is_response && is_jsonrpc && !state.is_success && state.was_legacy) @@ -892,11 +976,27 @@ void convert_jsonrpc(tr_variant::Map& top, State const& state) if (state.is_request && is_jsonrpc) { top.replace_key(TR_KEY_arguments, TR_KEY_params); + + if (auto* const params = top.find_if(TR_KEY_params)) + { + if (auto const iter = params->find(TR_KEY_encryption); iter != std::end(*params)) + { + convert_encryption(iter->second, state); + } + } } if (state.is_request && is_legacy) { top.replace_key(TR_KEY_params, TR_KEY_arguments); + + if (auto* const args = top.find_if(TR_KEY_arguments)) + { + if (auto const iter = args->find(TR_KEY_encryption); iter != std::end(*args)) + { + convert_encryption(iter->second, state); + } + } } } } // namespace @@ -908,6 +1008,7 @@ void convert(tr_variant& var, Style const tgt_style) auto state = makeState(*top); state.style = tgt_style; convert_keys(var, state); + convert_settings_encryption(*top, state); convert_jsonrpc(*top, state); } } diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 3e5911934..74820538b 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -1999,6 +1999,14 @@ void add_strings_from_var(std::set& strings, tr_variant const& } } + if (auto const iter = args_in.find(TR_KEY_encryption); iter != std::end(args_in)) + { + if (!session->deserialize_encryption_mode(iter->second)) + { + return { Error::INVALID_PARAMS, R"(must be one of "preferred", "required" or "allowed")"s }; + } + } + if (auto const val = args_in.value_if(TR_KEY_cache_size_mib); val) { tr_sessionSetCacheLimit_MB(session, *val); @@ -2217,22 +2225,6 @@ void add_strings_from_var(std::set& strings, tr_variant const& tr_sessionLimitSpeed(session, TR_UP, *val); } - if (auto const val = args_in.value_if(TR_KEY_encryption)) - { - if (*val == "required"sv) - { - tr_sessionSetEncryption(session, TR_ENCRYPTION_REQUIRED); - } - else if (*val == "tolerated"sv) - { - tr_sessionSetEncryption(session, TR_CLEAR_PREFERRED); - } - else - { - tr_sessionSetEncryption(session, TR_ENCRYPTION_PREFERRED); - } - } - if (auto const val = args_in.value_if(TR_KEY_anti_brute_force_threshold); val) { tr_sessionSetAntiBruteForceThreshold(session, static_cast(*val)); @@ -2288,21 +2280,6 @@ void add_strings_from_var(std::set& strings, tr_variant const& return { JsonRpc::Error::SUCCESS, {} }; } -[[nodiscard]] constexpr std::string_view getEncryptionModeString(tr_encryption_mode mode) -{ - switch (mode) - { - case TR_CLEAR_PREFERRED: - return "tolerated"sv; - - case TR_ENCRYPTION_REQUIRED: - return "required"sv; - - default: - return "preferred"sv; - } -} - [[nodiscard]] auto values_get_units() { using namespace libtransmission::Values; @@ -2377,7 +2354,7 @@ void add_strings_from_var(std::set& strings, tr_variant const& case TR_KEY_download_queue_size: return session.queueSize(TR_DOWN); case TR_KEY_encryption: - return tr_variant::unmanaged_string(getEncryptionModeString(tr_sessionGetEncryption(&session))); + return session.serialize_encryption_mode(); case TR_KEY_idle_seeding_limit: return session.idleLimitMinutes(); case TR_KEY_idle_seeding_limit_enabled: diff --git a/libtransmission/serializer.cc b/libtransmission/serializer.cc index dde375b9c..df73fbb44 100644 --- a/libtransmission/serializer.cc +++ b/libtransmission/serializer.cc @@ -137,6 +137,16 @@ bool to_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt) tr_variant from_encryption_mode(tr_encryption_mode const& val) { + static constexpr auto& Keys = EncryptionKeys; + + for (auto const& [key, encryption] : Keys) + { + if (encryption == val) + { + return tr_variant::unmanaged_string(key); + } + } + return static_cast(val); } diff --git a/libtransmission/session.h b/libtransmission/session.h index f215c5d62..95d7c39e7 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -975,6 +975,18 @@ public: return settings().encryption_mode; } + [[nodiscard]] auto serialize_encryption_mode() const noexcept + { + auto var = libtransmission::serializer::to_variant(settings().encryption_mode); + TR_ASSERT(var.has_value()); + return var; + } + + bool deserialize_encryption_mode(tr_variant const& var) noexcept + { + return libtransmission::serializer::Converters::deserialize(var, &settings_.encryption_mode); + } + [[nodiscard]] constexpr auto preallocationMode() const noexcept { return settings().preallocation_mode; diff --git a/tests/libtransmission/api-compat-test.cc b/tests/libtransmission/api-compat-test.cc index c9bf8e514..0b68050c6 100644 --- a/tests/libtransmission/api-compat-test.cc +++ b/tests/libtransmission/api-compat-test.cc @@ -445,6 +445,105 @@ constexpr std::string_view LegacyPortTestErrorResponse = R"json({ "tag": 9 })json"; +constexpr std::string_view CurrentPreferEncryptionResponse = R"json({ + "id": 6, + "jsonrpc": "2.0", + "result": { + "encryption": "preferred" + } +})json"; + +constexpr std::string_view LegacyPreferEncryptionResponse = R"json({ + "arguments": { + "encryption": "preferred" + }, + "result": "success", + "tag": 6 +})json"; + +constexpr std::string_view CurrentRequireEncryptionResponse = R"json({ + "id": 6, + "jsonrpc": "2.0", + "result": { + "encryption": "required" + } +})json"; + +constexpr std::string_view LegacyRequireEncryptionResponse = R"json({ + "arguments": { + "encryption": "required" + }, + "result": "success", + "tag": 6 +})json"; + +constexpr std::string_view CurrentPreferClearResponse = R"json({ + "id": 6, + "jsonrpc": "2.0", + "result": { + "encryption": "allowed" + } +})json"; + +constexpr std::string_view LegacyPreferClearResponse = R"json({ + "arguments": { + "encryption": "tolerated" + }, + "result": "success", + "tag": 6 +})json"; + +constexpr std::string_view CurrentPreferEncryptionRequest = R"json({ + "id": 6, + "jsonrpc": "2.0", + "method": "session_set", + "params": { + "encryption": "preferred" + } +})json"; + +constexpr std::string_view LegacyPreferEncryptionRequest = R"json({ + "arguments": { + "encryption": "preferred" + }, + "method": "session-set", + "tag": 6 +})json"; + +constexpr std::string_view CurrentRequireEncryptionRequest = R"json({ + "id": 6, + "jsonrpc": "2.0", + "method": "session_set", + "params": { + "encryption": "required" + } +})json"; + +constexpr std::string_view LegacyRequireEncryptionRequest = R"json({ + "arguments": { + "encryption": "required" + }, + "method": "session-set", + "tag": 6 +})json"; + +constexpr std::string_view CurrentPreferClearRequest = R"json({ + "id": 6, + "jsonrpc": "2.0", + "method": "session_set", + "params": { + "encryption": "allowed" + } +})json"; + +constexpr std::string_view LegacyPreferClearRequest = R"json({ + "arguments": { + "encryption": "tolerated" + }, + "method": "session-set", + "tag": 6 +})json"; + constexpr std::string_view LegacyStatsJson = R"json({ "downloaded-bytes": 12, "files-added": 34, @@ -581,7 +680,7 @@ constexpr std::string_view CurrentSettingsJson = R"json({ "download_dir": "/home/user/Downloads", "download_queue_enabled": true, "download_queue_size": 5, - "encryption": 1, + "encryption": "preferred", "filter_mode": "show_all", "filter_trackers": "", "idle_seeding_limit": 30, @@ -665,6 +764,30 @@ constexpr std::string_view CurrentSettingsJson = R"json({ "watch_dir_enabled": false })json"; +constexpr std::string_view LegacyPreferClearJson = R"json({ + "encryption": 0 +})json"; + +constexpr std::string_view CurrentPreferClearJson = R"json({ + "encryption": "allowed" +})json"; + +constexpr std::string_view LegacyPreferEncryptionJson = R"json({ + "encryption": 1 +})json"; + +constexpr std::string_view CurrentPreferEncryptionJson = R"json({ + "encryption": "preferred" +})json"; + +constexpr std::string_view LegacyRequireEncryptionJson = R"json({ + "encryption": 2 +})json"; + +constexpr std::string_view CurrentRequireEncryptionJson = R"json({ + "encryption": "required" +})json"; + constexpr std::string_view BadFreeSpaceRequest = R"json({ "id": 39693, "jsonrpc": "2.0", @@ -1085,7 +1208,7 @@ TEST_F(ApiCompatTest, canConvertRpc) using TestCase = std::tuple; // clang-format off - static auto constexpr TestCases = std::array{ { + static auto constexpr TestCases = std::array{ { { "free_space tr5 -> tr5", BadFreeSpaceRequest, Style::Tr5, BadFreeSpaceRequest }, { "free_space tr5 -> tr4", BadFreeSpaceRequest, Style::Tr4, BadFreeSpaceRequestLegacy }, { "free_space tr4 -> tr5", BadFreeSpaceRequestLegacy, Style::Tr5, BadFreeSpaceRequest }, @@ -1136,6 +1259,30 @@ TEST_F(ApiCompatTest, canConvertRpc) { "files wanted response array tr5 -> tr4", CurrentFilesWantedResponseArrayJson, Style::Tr4, LegacyFilesWantedResponseArrayJson }, { "files wanted response array tr4 -> tr5", LegacyFilesWantedResponseArrayJson, Style::Tr5, CurrentFilesWantedResponseArrayJson }, { "files wanted response array tr5 -> tr4", LegacyFilesWantedResponseArrayJson, Style::Tr4, LegacyFilesWantedResponseArrayJson }, + { "prefer encryption response tr5 -> tr5", CurrentPreferEncryptionResponse, Style::Tr5, CurrentPreferEncryptionResponse }, + { "prefer encryption response tr5 -> tr4", CurrentPreferEncryptionResponse, Style::Tr4, LegacyPreferEncryptionResponse }, + { "prefer encryption response tr4 -> tr5", LegacyPreferEncryptionResponse, Style::Tr5, CurrentPreferEncryptionResponse }, + { "prefer encryption response tr5 -> tr4", LegacyPreferEncryptionResponse, Style::Tr4, LegacyPreferEncryptionResponse }, + { "require encryption response tr5 -> tr5", CurrentRequireEncryptionResponse, Style::Tr5, CurrentRequireEncryptionResponse }, + { "require encryption response tr5 -> tr4", CurrentRequireEncryptionResponse, Style::Tr4, LegacyRequireEncryptionResponse }, + { "require encryption response tr4 -> tr5", LegacyRequireEncryptionResponse, Style::Tr5, CurrentRequireEncryptionResponse }, + { "require encryption response tr5 -> tr4", LegacyRequireEncryptionResponse, Style::Tr4, LegacyRequireEncryptionResponse }, + { "prefer clear response tr5 -> tr5", CurrentPreferClearResponse, Style::Tr5, CurrentPreferClearResponse }, + { "prefer clear response tr5 -> tr4", CurrentPreferClearResponse, Style::Tr4, LegacyPreferClearResponse }, + { "prefer clear response tr4 -> tr5", LegacyPreferClearResponse, Style::Tr5, CurrentPreferClearResponse }, + { "prefer clear response tr5 -> tr4", LegacyPreferClearResponse, Style::Tr4, LegacyPreferClearResponse }, + { "prefer encryption request tr5 -> tr5", CurrentPreferEncryptionRequest, Style::Tr5, CurrentPreferEncryptionRequest }, + { "prefer encryption request tr5 -> tr4", CurrentPreferEncryptionRequest, Style::Tr4, LegacyPreferEncryptionRequest }, + { "prefer encryption request tr4 -> tr5", LegacyPreferEncryptionRequest, Style::Tr5, CurrentPreferEncryptionRequest }, + { "prefer encryption request tr5 -> tr4", LegacyPreferEncryptionRequest, Style::Tr4, LegacyPreferEncryptionRequest }, + { "require encryption request tr5 -> tr5", CurrentRequireEncryptionRequest, Style::Tr5, CurrentRequireEncryptionRequest }, + { "require encryption request tr5 -> tr4", CurrentRequireEncryptionRequest, Style::Tr4, LegacyRequireEncryptionRequest }, + { "require encryption request tr4 -> tr5", LegacyRequireEncryptionRequest, Style::Tr5, CurrentRequireEncryptionRequest }, + { "require encryption request tr5 -> tr4", LegacyRequireEncryptionRequest, Style::Tr4, LegacyRequireEncryptionRequest }, + { "prefer clear request tr5 -> tr5", CurrentPreferClearRequest, Style::Tr5, CurrentPreferClearRequest }, + { "prefer clear request tr5 -> tr4", CurrentPreferClearRequest, Style::Tr4, LegacyPreferClearRequest }, + { "prefer clear request tr4 -> tr5", LegacyPreferClearRequest, Style::Tr5, CurrentPreferClearRequest }, + { "prefer clear request tr5 -> tr4", LegacyPreferClearRequest, Style::Tr4, LegacyPreferClearRequest }, // TODO(ckerr): torrent-get with 'table' } }; @@ -1156,11 +1303,23 @@ TEST_F(ApiCompatTest, canConvertJsonDataFiles) using Style = libtransmission::api_compat::Style; using TestCase = std::tuple; - static auto constexpr TestCases = std::array{ { + static auto constexpr TestCases = std::array{ { { "settings tr5 -> tr5", CurrentSettingsJson, Style::Tr5, CurrentSettingsJson }, { "settings tr5 -> tr4", CurrentSettingsJson, Style::Tr4, LegacySettingsJson }, { "settings tr4 -> tr5", LegacySettingsJson, Style::Tr5, CurrentSettingsJson }, { "settings tr4 -> tr4", LegacySettingsJson, Style::Tr4, LegacySettingsJson }, + { "prefer clear tr5 -> tr5", CurrentPreferClearJson, Style::Tr5, CurrentPreferClearJson }, + { "prefer clear tr5 -> tr4", CurrentPreferClearJson, Style::Tr4, LegacyPreferClearJson }, + { "prefer clear tr4 -> tr5", LegacyPreferClearJson, Style::Tr5, CurrentPreferClearJson }, + { "prefer clear tr4 -> tr4", LegacyPreferClearJson, Style::Tr4, LegacyPreferClearJson }, + { "prefer encryption tr5 -> tr5", CurrentPreferEncryptionJson, Style::Tr5, CurrentPreferEncryptionJson }, + { "prefer encryption tr5 -> tr4", CurrentPreferEncryptionJson, Style::Tr4, LegacyPreferEncryptionJson }, + { "prefer encryption tr4 -> tr5", LegacyPreferEncryptionJson, Style::Tr5, CurrentPreferEncryptionJson }, + { "prefer encryption tr4 -> tr4", LegacyPreferEncryptionJson, Style::Tr4, LegacyPreferEncryptionJson }, + { "require encryption tr5 -> tr5", CurrentRequireEncryptionJson, Style::Tr5, CurrentRequireEncryptionJson }, + { "require encryption tr5 -> tr4", CurrentRequireEncryptionJson, Style::Tr4, LegacyRequireEncryptionJson }, + { "require encryption tr4 -> tr5", LegacyRequireEncryptionJson, Style::Tr5, CurrentRequireEncryptionJson }, + { "require encryption tr4 -> tr4", LegacyRequireEncryptionJson, Style::Tr4, LegacyRequireEncryptionJson }, { "stats tr5 -> tr5", CurrentStatsJson, Style::Tr5, CurrentStatsJson }, { "stats tr5 -> tr4", CurrentStatsJson, Style::Tr4, LegacyStatsJson }, diff --git a/tests/libtransmission/settings-test.cc b/tests/libtransmission/settings-test.cc index 93e6f3d87..e27b7a7db 100644 --- a/tests/libtransmission/settings-test.cc +++ b/tests/libtransmission/settings-test.cc @@ -112,14 +112,15 @@ TEST_F(SettingsTest, canLoadEncryptionMode) TEST_F(SettingsTest, canSaveEncryptionMode) { static auto constexpr Key = TR_KEY_encryption; - static auto constexpr ExpectedValue = TR_ENCRYPTION_REQUIRED; + static auto constexpr SourceValue = TR_ENCRYPTION_REQUIRED; + static auto constexpr ExpectedValue = "required"sv; auto settings = tr_session::Settings{}; - EXPECT_NE(ExpectedValue, settings.seed_queue_enabled); - settings.encryption_mode = ExpectedValue; + EXPECT_NE(SourceValue, settings.seed_queue_enabled); + settings.encryption_mode = SourceValue; auto const map = settings.save(); - auto const val = map.value_if(Key); + auto const val = map.value_if(Key); ASSERT_TRUE(val); EXPECT_EQ(ExpectedValue, *val); }