mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-07 18:43:26 -08:00
[DeckSync] Match server rows by identity and hash the whole deck
Sync keyed local files to server rows by path, but the server names a row
after the deck, not after the file, so any deck whose file name and deck name
differ uploaded again on every run. A second pass then brought the server copy
back as an extra local file.
- Match by the sidecar's row id, then guid, then content, and only then by the
server path, so renaming either side no longer duplicates a deck.
- Key the server tree by row id: the server allows several rows with the same
name in one folder, and keying by path silently dropped all but one.
- Stop discarding rows whose name contains '/' ("U/W Control"). They stay
matchable, and only the download file name is sanitised.
- Build the download path from the folder plus a sanitised row name, including
every folder segment, so a hostile server name cannot escape the deck folder.
- Download a colliding server row under a unique name and count it, instead of
skipping it without a trace.
- Hash the whole deck instead of the card hash: SHA-1 over the native deck
string covers name, printings, banner, tokens and sideboard plans. The client
and the server hash the exact bytes they exchange, so both agree.
Adds deck_content_hash_test for the client/server hashing invariant and for the
name-only change the card hash cannot see.
This commit is contained in:
parent
e220bb3aa3
commit
543cc16d83
7 changed files with 213 additions and 81 deletions
|
|
@ -28,6 +28,7 @@ add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test)
|
|||
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
||||
set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 15)
|
||||
add_test(NAME deck_diff_test COMMAND deck_diff_test)
|
||||
add_test(NAME deck_content_hash_test COMMAND deck_content_hash_test)
|
||||
add_test(NAME deck_sync_profile_test COMMAND deck_sync_profile_test)
|
||||
add_test(NAME deck_sync_sidecar_test COMMAND deck_sync_sidecar_test)
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ add_executable(password_hash_test password_hash_test.cpp)
|
|||
add_executable(playmat_resolver_test playmat_resolver_test.cpp)
|
||||
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
|
||||
add_executable(deck_diff_test deck_diff_test.cpp)
|
||||
add_executable(deck_content_hash_test deck_content_hash_test.cpp)
|
||||
add_executable(
|
||||
deck_sync_profile_test ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/deck_sync/deck_sync_profile.cpp
|
||||
deck_sync_profile_test.cpp
|
||||
|
|
@ -152,6 +154,7 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(playmat_resolver_test gtest)
|
||||
add_dependencies(deck_hash_performance_test gtest)
|
||||
add_dependencies(deck_diff_test gtest)
|
||||
add_dependencies(deck_content_hash_test gtest)
|
||||
add_dependencies(deck_sync_profile_test gtest)
|
||||
add_dependencies(deck_sync_sidecar_test gtest)
|
||||
add_dependencies(server_card_counter_test gtest)
|
||||
|
|
@ -196,6 +199,10 @@ target_link_libraries(
|
|||
deck_diff_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${TEST_QT_MODULES}
|
||||
)
|
||||
target_link_libraries(
|
||||
deck_content_hash_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${TEST_QT_MODULES}
|
||||
)
|
||||
target_link_libraries(deck_sync_profile_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
||||
target_link_libraries(deck_sync_sidecar_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
||||
target_link_libraries(
|
||||
|
|
|
|||
51
tests/deck_content_hash_test.cpp
Normal file
51
tests/deck_content_hash_test.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include <QString>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
QString deckWithName(const QString &name)
|
||||
{
|
||||
return QStringLiteral(R"(<?xml version="1.0"?><cockatrice_deck version="1">)"
|
||||
R"(<deckname>%1</deckname><comments></comments>)"
|
||||
R"(<zone name="main"><card number="4" name="Lightning Bolt"/></zone>)"
|
||||
R"(<zone name="side"><card number="2" name="Pyroblast"/></zone>)"
|
||||
R"(</cockatrice_deck>)")
|
||||
.arg(name);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(DeckContentHashTest, MatchesNativeSerialisation)
|
||||
{
|
||||
DeckList deck;
|
||||
ASSERT_TRUE(deck.loadFromString_Native(deckWithName(QStringLiteral("Burn"))));
|
||||
EXPECT_EQ(deck.getContentHash(), DeckList::contentHash(deck.writeToString_Native()));
|
||||
}
|
||||
|
||||
TEST(DeckContentHashTest, IsStableAcrossLoads)
|
||||
{
|
||||
DeckList first;
|
||||
DeckList second;
|
||||
ASSERT_TRUE(first.loadFromString_Native(deckWithName(QStringLiteral("Burn"))));
|
||||
ASSERT_TRUE(second.loadFromString_Native(deckWithName(QStringLiteral("Burn"))));
|
||||
EXPECT_EQ(first.getContentHash(), second.getContentHash());
|
||||
}
|
||||
|
||||
TEST(DeckContentHashTest, CoversDeckName)
|
||||
{
|
||||
DeckList burn;
|
||||
DeckList renamed;
|
||||
ASSERT_TRUE(burn.loadFromString_Native(deckWithName(QStringLiteral("Burn"))));
|
||||
ASSERT_TRUE(renamed.loadFromString_Native(deckWithName(QStringLiteral("Burn v2"))));
|
||||
|
||||
// The card hash only looks at card names and counts, so it cannot tell the
|
||||
// two decks apart; the content hash must.
|
||||
EXPECT_EQ(burn.getDeckHash(), renamed.getDeckHash());
|
||||
EXPECT_NE(burn.getContentHash(), renamed.getContentHash());
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue