From 7891c689fbe636b72aba73e1d25b57b185214102 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 2 Jul 2026 16:20:53 -0500 Subject: [PATCH] =?UTF-8?q?refactor(audit):=20batch=202=20=E2=80=94=20remo?= =?UTF-8?q?ve=20dead=20scaffolding=20from=20the=20shipping=20binary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two dead-code removals surfaced by the audit; no runtime behavior change. 1. Delete the lite-lifecycle "readiness" scaffold (~1,586 lines). The pure dry-run evaluateLiteWalletServerLifecycleReadiness (whose own status text says "wallet lifecycle execution is still disabled in this scaffold") had zero callers; executeLiteWalletServerSelectionUi had zero callers; and executeLiteWalletLifecycleUiRequest's only caller was a fallback in settings_page reached only when app->liteWallet() is null. Deleted lite_wallet_server_lifecycle_readiness.{h,cpp} and the readiness / UI-execution machinery + enums + translation tables from both adapters, keeping the genuinely-live helpers (liteConnectionSettingsFromAppSettings, applyLiteConnectionSettingsToAppSettings, redactLiteServerSelectionValue, and the LiteWalletLifecycleUiExecutionInput DTO the live create/open/ restore path still uses). The null-backend fallback now sets a plain "Lite wallet backend unavailable" status. This is exactly the readiness/scaffold pattern CLAUDE.md forbids regrowing. 2. Split the HushChat fixture/capture-manifest/seed-projection tooling (~2,050 lines, incl. the libsodium seed-projection) out of chat_protocol.cpp (1854 -> 284) and chat_protocol.h (586 -> 105) into a new chat_fixture_tooling.{h,cpp} compiled ONLY into the HushChatFixtureCheck dev tool — never into the app, lite, or test binaries (verified via nm). The app keeps only the runtime slice (memo parsing + tx metadata extraction) that network_refresh_service actually calls. Also moved the decrypt-preflight/hex helpers, which likewise had no runtime caller. Note: the HushChat split is on gated-off experimental code and should be coordinated with the pending dormant-HushChat-content handling (commit af06b8b) before the lite PR — done here as a pure mechanical split, no redesign. Full-node + Lite + HushChatFixtureCheck build clean; ctest 1/1; hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- CMakeLists.txt | 4 +- src/chat/chat_fixture_tooling.cpp | 1594 +++++++++++++++++ src/chat/chat_fixture_tooling.h | 505 ++++++ src/chat/chat_protocol.cpp | 1573 +--------------- src/chat/chat_protocol.h | 483 +---- src/ui/pages/settings_page.cpp | 16 +- .../lite_wallet_lifecycle_ui_adapter.cpp | 431 +---- src/wallet/lite_wallet_lifecycle_ui_adapter.h | 106 +- ...lite_wallet_server_lifecycle_readiness.cpp | 386 ---- .../lite_wallet_server_lifecycle_readiness.h | 181 -- .../lite_wallet_server_selection_adapter.cpp | 353 +--- .../lite_wallet_server_selection_adapter.h | 133 +- tools/hushchat_fixture_check.cpp | 2 +- 13 files changed, 2126 insertions(+), 3641 deletions(-) create mode 100644 src/chat/chat_fixture_tooling.cpp create mode 100644 src/chat/chat_fixture_tooling.h delete mode 100644 src/wallet/lite_wallet_server_lifecycle_readiness.cpp delete mode 100644 src/wallet/lite_wallet_server_lifecycle_readiness.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0922b..9e581e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,7 +423,6 @@ set(APP_SOURCES src/wallet/lite_wallet_state_mapper.cpp src/wallet/lite_wallet_lifecycle_ui_adapter.cpp src/wallet/lite_wallet_server_selection_adapter.cpp - src/wallet/lite_wallet_server_lifecycle_readiness.cpp src/wallet/lite_wallet_lifecycle_service.cpp src/data/wallet_state.cpp src/data/transaction_history_cache.cpp @@ -557,7 +556,6 @@ set(APP_HEADERS src/wallet/lite_wallet_state_mapper.h src/wallet/lite_wallet_lifecycle_ui_adapter.h src/wallet/lite_wallet_server_selection_adapter.h - src/wallet/lite_wallet_server_lifecycle_readiness.h src/wallet/lite_wallet_lifecycle_service.h src/chat/chat_protocol.h src/config/version.h @@ -825,6 +823,7 @@ endif() add_executable(HushChatFixtureCheck tools/hushchat_fixture_check.cpp src/chat/chat_protocol.cpp + src/chat/chat_fixture_tooling.cpp ) target_include_directories(HushChatFixtureCheck PRIVATE @@ -1020,7 +1019,6 @@ if(BUILD_TESTING) src/wallet/lite_wallet_state_mapper.cpp src/wallet/lite_wallet_lifecycle_ui_adapter.cpp src/wallet/lite_wallet_server_selection_adapter.cpp - src/wallet/lite_wallet_server_lifecycle_readiness.cpp src/wallet/lite_wallet_lifecycle_service.cpp src/ui/explorer/explorer_block_cache.cpp src/ui/windows/balance_address_list.cpp diff --git a/src/chat/chat_fixture_tooling.cpp b/src/chat/chat_fixture_tooling.cpp new file mode 100644 index 0000000..7487213 --- /dev/null +++ b/src/chat/chat_fixture_tooling.cpp @@ -0,0 +1,1594 @@ +#include "chat_fixture_tooling.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace dragonx::chat { +namespace { + +bool isHexString(const std::string& value) +{ + for (unsigned char ch : value) { + if (!std::isxdigit(ch)) return false; + } + return true; +} + +std::optional hexNibble(unsigned char character) +{ + if (character >= '0' && character <= '9') return static_cast(character - '0'); + if (character >= 'a' && character <= 'f') return static_cast(character - 'a' + 10); + if (character >= 'A' && character <= 'F') return static_cast(character - 'A' + 10); + return std::nullopt; +} + +std::optional readJsonString(const nlohmann::json& object, const char* key) +{ + auto it = object.find(key); + if (it == object.end() || !it->is_string()) return std::nullopt; + return it->get(); +} + +std::optional readJsonSize(const nlohmann::json& object, const char* key) +{ + auto it = object.find(key); + if (it == object.end() || (!it->is_number_integer() && !it->is_number_unsigned())) return std::nullopt; + if (it->is_number_unsigned()) return it->get(); + auto value = it->get(); + if (value < 0) return std::nullopt; + return static_cast(value); +} + +std::optional parseFixtureDirection(const std::string& value) +{ + if (value == "Incoming") return HushChatDecryptDirection::Incoming; + if (value == "Outgoing") return HushChatDecryptDirection::Outgoing; + return std::nullopt; +} + +std::optional parseFixtureSessionKeySelection(const std::string& value) +{ + if (value == "ClientRx") return HushChatSessionKeySelection::ClientRx; + if (value == "ServerTx") return HushChatSessionKeySelection::ServerTx; + return std::nullopt; +} + +std::optional parseFixtureKind(const std::string& value) +{ + if (value == "incoming_memo") return HushChatCompatibilityFixtureKind::IncomingMemo; + if (value == "outgoing_memo") return HushChatCompatibilityFixtureKind::OutgoingMemo; + if (value == "seed_public_key_projection") return HushChatCompatibilityFixtureKind::SeedPublicKeyProjection; + if (value == "corrupted_auth_failure") return HushChatCompatibilityFixtureKind::CorruptedAuthFailure; + if (value == "cont_exclusion") return HushChatCompatibilityFixtureKind::ContactExclusion; + return std::nullopt; +} + +std::optional parseFixtureFileStatus(const std::string& value) +{ + if (value == "pending") return HushChatCompatibilityFixtureFileStatus::Pending; + if (value == "ready") return HushChatCompatibilityFixtureFileStatus::Ready; + return std::nullopt; +} + +std::string lowerCopy(const std::string& value) +{ + std::string lowered; + lowered.reserve(value.size()); + for (unsigned char ch : value) lowered.push_back(static_cast(std::tolower(ch))); + return lowered; +} + +bool isProhibitedCaptureManifestKey(const std::string& key) +{ + const std::string lowered = lowerCopy(key); + if (lowered.rfind("no_", 0) == 0) return false; + + static const std::vector prohibitedKeys = { + "fixture", + "fixtures", + "passphrase", + "password", + "plaintext", + "plaintext_hash_hex", + "memo", + "memostr", + "memo_contents", + "header_memo", + "ciphertext", + "ciphertext_memo", + "ciphertext_bytes", + "private_key", + "viewing_key", + "spending_key", + "wallet_seed", + "wallet_file", + "wallet_files", + "stored_chat_key_hex", + "local_public_key_hex", + "peer_public_key_hex", + "public_key_hex", + "secretstream_header", + "derived_key", + "session_key", + "secret_key", + "seed_bytes" + }; + + return std::find(prohibitedKeys.begin(), prohibitedKeys.end(), lowered) != prohibitedKeys.end(); +} + +std::size_t countProhibitedCaptureManifestFields(const nlohmann::json& value) +{ + std::size_t count = 0; + if (value.is_object()) { + for (auto it = value.begin(); it != value.end(); ++it) { + if (isProhibitedCaptureManifestKey(it.key())) ++count; + count += countProhibitedCaptureManifestFields(it.value()); + } + } else if (value.is_array()) { + for (const auto& item : value) count += countProhibitedCaptureManifestFields(item); + } + return count; +} + +std::vector requiredCaptureHandlingFlags() +{ + return { + "disposable_wallets_only", + "non_sensitive_vectors_only", + "no_passphrases", + "no_plaintext", + "no_memo_contents", + "no_private_keys", + "no_wallet_files", + "no_ciphertext_byte_dumps", + "no_derived_keys", + "no_session_keys", + "redacted_report_only" + }; +} + +} // namespace + +const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error) +{ + switch (error) { + case HushChatDecryptPreflightError::None: + return "None"; + case HushChatDecryptPreflightError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatDecryptPreflightError::NonMessageHeader: + return "NonMessageHeader"; + case HushChatDecryptPreflightError::InvalidHeaderNumber: + return "InvalidHeaderNumber"; + case HushChatDecryptPreflightError::UnsupportedVersion: + return "UnsupportedVersion"; + case HushChatDecryptPreflightError::MissingReplyAddress: + return "MissingReplyAddress"; + case HushChatDecryptPreflightError::MissingConversationId: + return "MissingConversationId"; + case HushChatDecryptPreflightError::InvalidSecretstreamHeader: + return "InvalidSecretstreamHeader"; + case HushChatDecryptPreflightError::InvalidPublicKey: + return "InvalidPublicKey"; + case HushChatDecryptPreflightError::EmptyCiphertext: + return "EmptyCiphertext"; + case HushChatDecryptPreflightError::OversizedCiphertext: + return "OversizedCiphertext"; + case HushChatDecryptPreflightError::OddLengthCiphertext: + return "OddLengthCiphertext"; + case HushChatDecryptPreflightError::InvalidCiphertextHex: + return "InvalidCiphertextHex"; + case HushChatDecryptPreflightError::TruncatedCiphertext: + return "TruncatedCiphertext"; + } + return "Unknown"; +} + +const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error) +{ + switch (error) { + case HushChatHexDecodeError::None: + return "None"; + case HushChatHexDecodeError::Empty: + return "Empty"; + case HushChatHexDecodeError::OddLength: + return "OddLength"; + case HushChatHexDecodeError::InvalidHex: + return "InvalidHex"; + case HushChatHexDecodeError::UnexpectedByteLength: + return "UnexpectedByteLength"; + } + return "Unknown"; +} + +const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction) +{ + switch (direction) { + case HushChatDecryptDirection::Incoming: + return "Incoming"; + case HushChatDecryptDirection::Outgoing: + return "Outgoing"; + } + return "Unknown"; +} + +const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection) +{ + switch (selection) { + case HushChatSessionKeySelection::ClientRx: + return "ClientRx"; + case HushChatSessionKeySelection::ServerTx: + return "ServerTx"; + } + return "Unknown"; +} + +const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error) +{ + switch (error) { + case HushChatDecryptInputError::None: + return "None"; + case HushChatDecryptInputError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatDecryptInputError::InvalidStoredChatKey: + return "InvalidStoredChatKey"; + case HushChatDecryptInputError::DecryptPreflightFailed: + return "DecryptPreflightFailed"; + case HushChatDecryptInputError::InvalidPeerPublicKey: + return "InvalidPeerPublicKey"; + case HushChatDecryptInputError::InvalidStreamHeader: + return "InvalidStreamHeader"; + case HushChatDecryptInputError::InvalidCiphertext: + return "InvalidCiphertext"; + } + return "Unknown"; +} + +const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error) +{ + switch (error) { + case HushChatCompatibilityFixtureError::None: + return "None"; + case HushChatCompatibilityFixtureError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatCompatibilityFixtureError::MissingFixtureId: + return "MissingFixtureId"; + case HushChatCompatibilityFixtureError::InvalidLocalPublicKey: + return "InvalidLocalPublicKey"; + case HushChatCompatibilityFixtureError::InvalidPeerPublicKey: + return "InvalidPeerPublicKey"; + case HushChatCompatibilityFixtureError::InvalidHeaderMemo: + return "InvalidHeaderMemo"; + case HushChatCompatibilityFixtureError::InvalidMemoPair: + return "InvalidMemoPair"; + case HushChatCompatibilityFixtureError::NonMemoHeader: + return "NonMemoHeader"; + case HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch: + return "HeaderPublicKeyMismatch"; + case HushChatCompatibilityFixtureError::DecryptInputFailed: + return "DecryptInputFailed"; + case HushChatCompatibilityFixtureError::NotFixtureReady: + return "NotFixtureReady"; + case HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch: + return "ExpectedStoredChatKeyLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch: + return "ExpectedSeedLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch: + return "ExpectedLocalPublicKeyLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch: + return "ExpectedPeerPublicKeyLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch: + return "ExpectedStreamHeaderLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch: + return "ExpectedCiphertextLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch: + return "ExpectedPlaintextLengthMismatch"; + case HushChatCompatibilityFixtureError::ExpectedRoleMismatch: + return "ExpectedRoleMismatch"; + case HushChatCompatibilityFixtureError::InvalidPlaintextHash: + return "InvalidPlaintextHash"; + } + return "Unknown"; +} + +const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind) +{ + switch (kind) { + case HushChatCompatibilityFixtureKind::IncomingMemo: + return "incoming_memo"; + case HushChatCompatibilityFixtureKind::OutgoingMemo: + return "outgoing_memo"; + case HushChatCompatibilityFixtureKind::SeedPublicKeyProjection: + return "seed_public_key_projection"; + case HushChatCompatibilityFixtureKind::CorruptedAuthFailure: + return "corrupted_auth_failure"; + case HushChatCompatibilityFixtureKind::ContactExclusion: + return "cont_exclusion"; + } + return "unknown"; +} + +const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status) +{ + switch (status) { + case HushChatCompatibilityFixtureFileStatus::Pending: + return "pending"; + case HushChatCompatibilityFixtureFileStatus::Ready: + return "ready"; + } + return "unknown"; +} + +const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error) +{ + switch (error) { + case HushChatCompatibilityFixtureFileError::None: + return "None"; + case HushChatCompatibilityFixtureFileError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatCompatibilityFixtureFileError::InvalidJson: + return "InvalidJson"; + case HushChatCompatibilityFixtureFileError::JsonNotObject: + return "JsonNotObject"; + case HushChatCompatibilityFixtureFileError::InvalidSchema: + return "InvalidSchema"; + case HushChatCompatibilityFixtureFileError::MissingKind: + return "MissingKind"; + case HushChatCompatibilityFixtureFileError::UnknownKind: + return "UnknownKind"; + case HushChatCompatibilityFixtureFileError::MissingStatus: + return "MissingStatus"; + case HushChatCompatibilityFixtureFileError::UnknownStatus: + return "UnknownStatus"; + case HushChatCompatibilityFixtureFileError::MissingFixtureId: + return "MissingFixtureId"; + case HushChatCompatibilityFixtureFileError::MissingPendingReason: + return "MissingPendingReason"; + case HushChatCompatibilityFixtureFileError::MissingFixtureObject: + return "MissingFixtureObject"; + case HushChatCompatibilityFixtureFileError::InvalidFixtureField: + return "InvalidFixtureField"; + case HushChatCompatibilityFixtureFileError::FixtureVerificationFailed: + return "FixtureVerificationFailed"; + case HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded: + return "ContactFixtureNotExcluded"; + case HushChatCompatibilityFixtureFileError::FileReadFailed: + return "FileReadFailed"; + } + return "Unknown"; +} + +const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error) +{ + switch (error) { + case HushChatSeedPublicKeyProjectionError::None: + return "None"; + case HushChatSeedPublicKeyProjectionError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatSeedPublicKeyProjectionError::MissingFixtureId: + return "MissingFixtureId"; + case HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey: + return "InvalidStoredChatKey"; + case HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey: + return "InvalidLocalPublicKey"; + case HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch: + return "ExpectedStoredChatKeyLengthMismatch"; + case HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch: + return "ExpectedSeedLengthMismatch"; + case HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch: + return "ExpectedLocalPublicKeyLengthMismatch"; + case HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed: + return "SodiumInitializationFailed"; + case HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed: + return "KeypairProjectionFailed"; + case HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch: + return "ProjectedPublicKeyMismatch"; + } + return "Unknown"; +} + +const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error) +{ + switch (error) { + case HushChatCorruptedAuthFailureReadinessError::None: + return "None"; + case HushChatCorruptedAuthFailureReadinessError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatCorruptedAuthFailureReadinessError::FixturePending: + return "FixturePending"; + case HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind: + return "WrongFixtureKind"; + case HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified: + return "FixtureNotVerified"; + case HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified: + return "SeedProjectionNotVerified"; + } + return "Unknown"; +} + +const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error) +{ + switch (error) { + case HushChatCompatibilityFixtureImportError::None: + return "None"; + case HushChatCompatibilityFixtureImportError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatCompatibilityFixtureImportError::MissingRequiredKind: + return "MissingRequiredKind"; + case HushChatCompatibilityFixtureImportError::DuplicateKind: + return "DuplicateKind"; + case HushChatCompatibilityFixtureImportError::FixtureLoadFailed: + return "FixtureLoadFailed"; + case HushChatCompatibilityFixtureImportError::FixtureKindMismatch: + return "FixtureKindMismatch"; + case HushChatCompatibilityFixtureImportError::FixturePending: + return "FixturePending"; + case HushChatCompatibilityFixtureImportError::FixtureInvalid: + return "FixtureInvalid"; + case HushChatCompatibilityFixtureImportError::FixtureNotVerified: + return "FixtureNotVerified"; + case HushChatCompatibilityFixtureImportError::SeedProjectionFailed: + return "SeedProjectionFailed"; + case HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed: + return "AuthFailureScaffoldFailed"; + case HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded: + return "ContactFixtureNotExcluded"; + } + return "Unknown"; +} + +const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error) +{ + switch (error) { + case HushChatCaptureManifestError::None: + return "None"; + case HushChatCaptureManifestError::FeatureDisabled: + return "FeatureDisabled"; + case HushChatCaptureManifestError::FileReadFailed: + return "FileReadFailed"; + case HushChatCaptureManifestError::InvalidJson: + return "InvalidJson"; + case HushChatCaptureManifestError::JsonNotObject: + return "JsonNotObject"; + case HushChatCaptureManifestError::InvalidSchema: + return "InvalidSchema"; + case HushChatCaptureManifestError::MissingManifestId: + return "MissingManifestId"; + case HushChatCaptureManifestError::MissingStatus: + return "MissingStatus"; + case HushChatCaptureManifestError::UnknownStatus: + return "UnknownStatus"; + case HushChatCaptureManifestError::MissingFixtureDirectory: + return "MissingFixtureDirectory"; + case HushChatCaptureManifestError::MissingDryRunCommand: + return "MissingDryRunCommand"; + case HushChatCaptureManifestError::InvalidDryRunCommand: + return "InvalidDryRunCommand"; + case HushChatCaptureManifestError::MissingProvenance: + return "MissingProvenance"; + case HushChatCaptureManifestError::MissingSourceClient: + return "MissingSourceClient"; + case HushChatCaptureManifestError::InvalidSourceClient: + return "InvalidSourceClient"; + case HushChatCaptureManifestError::MissingSourceClientVersion: + return "MissingSourceClientVersion"; + case HushChatCaptureManifestError::MissingCaptureDate: + return "MissingCaptureDate"; + case HushChatCaptureManifestError::MissingNetwork: + return "MissingNetwork"; + case HushChatCaptureManifestError::MissingCaptureMethod: + return "MissingCaptureMethod"; + case HushChatCaptureManifestError::MissingHandling: + return "MissingHandling"; + case HushChatCaptureManifestError::MissingHandlingFlag: + return "MissingHandlingFlag"; + case HushChatCaptureManifestError::HandlingFlagNotTrue: + return "HandlingFlagNotTrue"; + case HushChatCaptureManifestError::MissingCategories: + return "MissingCategories"; + case HushChatCaptureManifestError::InvalidCategoryEntry: + return "InvalidCategoryEntry"; + case HushChatCaptureManifestError::UnknownCategory: + return "UnknownCategory"; + case HushChatCaptureManifestError::DuplicateCategory: + return "DuplicateCategory"; + case HushChatCaptureManifestError::MissingRequiredCategory: + return "MissingRequiredCategory"; + case HushChatCaptureManifestError::ProhibitedFieldPresent: + return "ProhibitedFieldPresent"; + } + return "Unknown"; +} + +HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction) +{ + switch (direction) { + case HushChatDecryptDirection::Incoming: + return HushChatSessionKeySelection::ClientRx; + case HushChatDecryptDirection::Outgoing: + return HushChatSessionKeySelection::ServerTx; + } + return HushChatSessionKeySelection::ClientRx; +} + +HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight( + const HushChatDecryptPreflightInput& input, + bool featureEnabled) +{ + auto failPreflight = [&](HushChatDecryptPreflightError error) { + HushChatDecryptPreflightResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatDecryptPreflightErrorName(error); + return result; + }; + + if (!featureEnabled) return failPreflight(HushChatDecryptPreflightError::FeatureDisabled); + + const auto& header = input.header; + if (header.type != HushChatHeaderType::Message) { + return failPreflight(HushChatDecryptPreflightError::NonMessageHeader); + } + if (header.header_number < 1) return failPreflight(HushChatDecryptPreflightError::InvalidHeaderNumber); + if (header.version != kHushChatSupportedVersion) { + return failPreflight(HushChatDecryptPreflightError::UnsupportedVersion); + } + if (header.reply_zaddr.empty()) return failPreflight(HushChatDecryptPreflightError::MissingReplyAddress); + if (header.conversation_id.empty()) return failPreflight(HushChatDecryptPreflightError::MissingConversationId); + if (header.secretstream_header_hex.size() != kHushChatSecretstreamHeaderHexLength || + !isHexString(header.secretstream_header_hex)) { + return failPreflight(HushChatDecryptPreflightError::InvalidSecretstreamHeader); + } + if (header.public_key_hex.size() != kHushChatPublicKeyHexLength || !isHexString(header.public_key_hex)) { + return failPreflight(HushChatDecryptPreflightError::InvalidPublicKey); + } + + if (input.ciphertext_hex.empty()) return failPreflight(HushChatDecryptPreflightError::EmptyCiphertext); + if (input.ciphertext_hex.size() > kHushChatMemoByteLimit) { + return failPreflight(HushChatDecryptPreflightError::OversizedCiphertext); + } + if (input.ciphertext_hex.size() % 2 != 0) return failPreflight(HushChatDecryptPreflightError::OddLengthCiphertext); + if (!isHexString(input.ciphertext_hex)) return failPreflight(HushChatDecryptPreflightError::InvalidCiphertextHex); + + const std::size_t ciphertextSize = input.ciphertext_hex.size() / 2; + if (ciphertextSize <= kHushChatSecretstreamABytes) { + return failPreflight(HushChatDecryptPreflightError::TruncatedCiphertext); + } + + HushChatDecryptPreflightResult result; + result.ok = true; + result.feature_enabled = true; + result.error = HushChatDecryptPreflightError::None; + result.error_name = hushChatDecryptPreflightErrorName(result.error); + result.ciphertext_size = ciphertextSize; + return result; +} + +HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex, + std::size_t expectedByteLength) +{ + auto failDecode = [](HushChatHexDecodeError error) { + HushChatHexDecodeResult result; + result.error = error; + result.error_name = hushChatHexDecodeErrorName(error); + return result; + }; + + if (hex.empty()) { + if (expectedByteLength == 0) { + HushChatHexDecodeResult result; + result.ok = true; + return result; + } + return failDecode(HushChatHexDecodeError::Empty); + } + if (hex.size() % 2 != 0) return failDecode(HushChatHexDecodeError::OddLength); + if (!isHexString(hex)) return failDecode(HushChatHexDecodeError::InvalidHex); + if (hex.size() / 2 != expectedByteLength) { + return failDecode(HushChatHexDecodeError::UnexpectedByteLength); + } + + HushChatHexDecodeResult result; + result.ok = true; + result.bytes.reserve(expectedByteLength); + for (std::size_t index = 0; index < hex.size(); index += 2) { + auto high = hexNibble(static_cast(hex[index])); + auto low = hexNibble(static_cast(hex[index + 1])); + if (!high || !low) return failDecode(HushChatHexDecodeError::InvalidHex); + result.bytes.push_back(static_cast((*high << 4) | *low)); + } + return result; +} + +HushChatDecryptInputPreparationResult prepareHushChatDecryptInput( + const HushChatDecryptInputMaterial& material, + bool featureEnabled) +{ + auto failPreparation = [&](HushChatDecryptInputError error, + HushChatHexDecodeError hexError = HushChatHexDecodeError::None, + HushChatDecryptPreflightError preflightError = HushChatDecryptPreflightError::None) { + HushChatDecryptInputPreparationResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatDecryptInputErrorName(error); + result.hex_error = hexError; + result.preflight_error = preflightError; + return result; + }; + + if (!featureEnabled) return failPreparation(HushChatDecryptInputError::FeatureDisabled); + + auto storedChatKey = decodeHushChatHexBytes(material.stored_chat_key_hex, + kHushChatStoredChatKeyByteLength); + if (!storedChatKey.ok) { + return failPreparation(HushChatDecryptInputError::InvalidStoredChatKey, storedChatKey.error); + } + + auto preflight = validateHushChatMemoDecryptPreflight( + HushChatDecryptPreflightInput{material.header, material.ciphertext_hex}, + true); + if (!preflight.ok) { + return failPreparation(HushChatDecryptInputError::DecryptPreflightFailed, + HushChatHexDecodeError::None, + preflight.error); + } + + const std::string& peerPublicKeyHex = material.peer_public_key_hex.empty() + ? material.header.public_key_hex + : material.peer_public_key_hex; + auto peerPublicKey = decodeHushChatHexBytes(peerPublicKeyHex, + kHushChatPublicKeyByteLength); + if (!peerPublicKey.ok) { + return failPreparation(HushChatDecryptInputError::InvalidPeerPublicKey, peerPublicKey.error); + } + + auto streamHeader = decodeHushChatHexBytes(material.header.secretstream_header_hex, + kHushChatSecretstreamHeaderByteLength); + if (!streamHeader.ok) { + return failPreparation(HushChatDecryptInputError::InvalidStreamHeader, streamHeader.error); + } + + auto ciphertext = decodeHushChatHexBytes(material.ciphertext_hex, preflight.ciphertext_size); + if (!ciphertext.ok) { + return failPreparation(HushChatDecryptInputError::InvalidCiphertext, ciphertext.error); + } + + HushChatDecryptInputPreparationResult result; + result.ok = true; + result.feature_enabled = true; + result.error = HushChatDecryptInputError::None; + result.error_name = hushChatDecryptInputErrorName(result.error); + result.prepared.stored_chat_key_bytes = std::move(storedChatKey.bytes); + result.prepared.seed_bytes.reserve(kHushChatSeedByteLength); + for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) { + result.prepared.seed_bytes.push_back(static_cast(material.stored_chat_key_hex[index])); + } + result.prepared.peer_public_key_bytes = std::move(peerPublicKey.bytes); + result.prepared.stream_header_bytes = std::move(streamHeader.bytes); + result.prepared.ciphertext_bytes = std::move(ciphertext.bytes); + result.prepared.direction = material.direction; + result.prepared.session_key_selection = hushChatSessionKeySelectionForDirection(material.direction); + result.prepared.plaintext_capacity = result.prepared.ciphertext_bytes.size() - kHushChatSecretstreamABytes; + return result; +} + +HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness( + const HushChatPreparedDecryptInput& prepared) +{ + HushChatDecryptFixtureReadinessResult result; + result.stored_chat_key_size = prepared.stored_chat_key_bytes.size(); + result.seed_size = prepared.seed_bytes.size(); + result.peer_public_key_size = prepared.peer_public_key_bytes.size(); + result.stream_header_size = prepared.stream_header_bytes.size(); + result.ciphertext_size = prepared.ciphertext_bytes.size(); + result.plaintext_capacity = prepared.plaintext_capacity; + result.session_key_selection = prepared.session_key_selection; + result.ready = result.stored_chat_key_size == kHushChatStoredChatKeyByteLength && + result.seed_size == kHushChatSeedByteLength && + result.peer_public_key_size == kHushChatPublicKeyByteLength && + result.stream_header_size == kHushChatSecretstreamHeaderByteLength && + result.ciphertext_size > kHushChatSecretstreamABytes && + result.plaintext_capacity == result.ciphertext_size - kHushChatSecretstreamABytes; + return result; +} + +HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture( + const HushChatCompatibilityFixture& fixture, + bool featureEnabled) +{ + auto failFixture = [&](HushChatCompatibilityFixtureError error, + HushChatHexDecodeError hexError = HushChatHexDecodeError::None) { + HushChatCompatibilityFixtureVerificationResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatCompatibilityFixtureErrorName(error); + result.hex_error = hexError; + return result; + }; + + if (!featureEnabled) return failFixture(HushChatCompatibilityFixtureError::FeatureDisabled); + if (fixture.fixture_id.empty()) return failFixture(HushChatCompatibilityFixtureError::MissingFixtureId); + + auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex, + kHushChatPublicKeyByteLength); + if (!localPublicKey.ok) { + return failFixture(HushChatCompatibilityFixtureError::InvalidLocalPublicKey, localPublicKey.error); + } + + auto peerPublicKey = decodeHushChatHexBytes(fixture.peer_public_key_hex, + kHushChatPublicKeyByteLength); + if (!peerPublicKey.ok) { + return failFixture(HushChatCompatibilityFixtureError::InvalidPeerPublicKey, peerPublicKey.error); + } + + auto parsedHeader = parseHushChatHeaderMemo(fixture.header_memo); + if (!parsedHeader.ok) return failFixture(HushChatCompatibilityFixtureError::InvalidHeaderMemo); + + auto grouped = groupHushChatMemoOutputs({ + HushChatMemoOutput{0, fixture.header_memo}, + HushChatMemoOutput{1, fixture.ciphertext_memo} + }); + if (grouped.pairs.size() != 1 || !grouped.issues.empty()) { + return failFixture(HushChatCompatibilityFixtureError::InvalidMemoPair); + } + + const auto& header = grouped.pairs[0].header; + if (header.type != HushChatHeaderType::Message) { + return failFixture(HushChatCompatibilityFixtureError::NonMemoHeader); + } + + const std::string& expectedHeaderPublicKey = fixture.direction == HushChatDecryptDirection::Incoming + ? fixture.peer_public_key_hex + : fixture.local_public_key_hex; + if (header.public_key_hex != expectedHeaderPublicKey) { + return failFixture(HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch); + } + + auto preparation = prepareHushChatDecryptInput(HushChatDecryptInputMaterial{ + fixture.stored_chat_key_hex, + header, + fixture.ciphertext_memo, + fixture.direction, + fixture.peer_public_key_hex + }, true); + + if (!preparation.ok) { + HushChatCompatibilityFixtureVerificationResult result = failFixture( + HushChatCompatibilityFixtureError::DecryptInputFailed, + preparation.hex_error); + result.decrypt_input_error = preparation.error; + result.preflight_error = preparation.preflight_error; + result.preparation = std::move(preparation); + return result; + } + + auto readiness = inspectHushChatDecryptFixtureReadiness(preparation.prepared); + if (!readiness.ready) { + HushChatCompatibilityFixtureVerificationResult result = failFixture( + HushChatCompatibilityFixtureError::NotFixtureReady); + result.preparation = std::move(preparation); + result.readiness = readiness; + return result; + } + + auto failExpectation = [&](HushChatCompatibilityFixtureError error, + HushChatDecryptInputPreparationResult&& prepared, + const HushChatDecryptFixtureReadinessResult& fixtureReadiness) { + HushChatCompatibilityFixtureVerificationResult result = failFixture(error); + result.header = header; + result.preparation = std::move(prepared); + result.readiness = fixtureReadiness; + result.local_public_key_size = localPublicKey.bytes.size(); + result.peer_public_key_size = peerPublicKey.bytes.size(); + return result; + }; + + if (readiness.stored_chat_key_size != fixture.expected_stored_chat_key_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.seed_size != fixture.expected_seed_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch, + std::move(preparation), + readiness); + } + if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.peer_public_key_size != fixture.expected_peer_public_key_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.stream_header_size != fixture.expected_stream_header_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.ciphertext_size != fixture.expected_ciphertext_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.plaintext_capacity != fixture.expected_plaintext_size) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch, + std::move(preparation), + readiness); + } + if (readiness.session_key_selection != fixture.expected_session_key_selection) { + return failExpectation(HushChatCompatibilityFixtureError::ExpectedRoleMismatch, + std::move(preparation), + readiness); + } + + std::size_t plaintextHashSize = 0; + if (!fixture.expected_plaintext_hash_hex.empty()) { + if (fixture.expected_plaintext_hash_hex.size() % 2 != 0 || + !isHexString(fixture.expected_plaintext_hash_hex)) { + return failExpectation(HushChatCompatibilityFixtureError::InvalidPlaintextHash, + std::move(preparation), + readiness); + } + plaintextHashSize = fixture.expected_plaintext_hash_hex.size() / 2; + } + + HushChatCompatibilityFixtureVerificationResult result; + result.ok = true; + result.feature_enabled = true; + result.error = HushChatCompatibilityFixtureError::None; + result.error_name = hushChatCompatibilityFixtureErrorName(result.error); + result.header = header; + result.preparation = std::move(preparation); + result.readiness = readiness; + result.local_public_key_size = localPublicKey.bytes.size(); + result.peer_public_key_size = peerPublicKey.bytes.size(); + result.plaintext_hash_size = plaintextHashSize; + return result; +} + +HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile( + const std::string& jsonText, + bool featureEnabled) +{ + auto failFile = [&](HushChatCompatibilityFixtureFileError error) { + HushChatCompatibilityFixtureFileParseResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatCompatibilityFixtureFileErrorName(error); + return result; + }; + + if (!featureEnabled) return failFile(HushChatCompatibilityFixtureFileError::FeatureDisabled); + + nlohmann::json object; + try { + object = nlohmann::json::parse(jsonText); + } catch (const nlohmann::json::parse_error&) { + return failFile(HushChatCompatibilityFixtureFileError::InvalidJson); + } + if (!object.is_object()) return failFile(HushChatCompatibilityFixtureFileError::JsonNotObject); + + auto schema = readJsonString(object, "schema"); + if (!schema || *schema != kHushChatCompatibilityFixtureSchema) { + return failFile(HushChatCompatibilityFixtureFileError::InvalidSchema); + } + + auto kindText = readJsonString(object, "kind"); + if (!kindText) return failFile(HushChatCompatibilityFixtureFileError::MissingKind); + auto kind = parseFixtureKind(*kindText); + if (!kind) return failFile(HushChatCompatibilityFixtureFileError::UnknownKind); + + auto statusText = readJsonString(object, "status"); + if (!statusText) return failFile(HushChatCompatibilityFixtureFileError::MissingStatus); + auto status = parseFixtureFileStatus(*statusText); + if (!status) return failFile(HushChatCompatibilityFixtureFileError::UnknownStatus); + + HushChatCompatibilityFixtureFile parsed; + parsed.schema = *schema; + parsed.kind = *kind; + parsed.status = *status; + if (auto fixtureId = readJsonString(object, "id")) parsed.fixture_id = *fixtureId; + + if (parsed.status == HushChatCompatibilityFixtureFileStatus::Pending) { + if (parsed.fixture_id.empty()) return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureId); + + auto pendingReason = readJsonString(object, "pending_reason"); + if (!pendingReason || pendingReason->empty()) { + return failFile(HushChatCompatibilityFixtureFileError::MissingPendingReason); + } + parsed.pending_reason = *pendingReason; + + HushChatCompatibilityFixtureFileParseResult result; + result.ok = true; + result.feature_enabled = true; + result.pending = true; + result.error = HushChatCompatibilityFixtureFileError::None; + result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); + result.file = std::move(parsed); + return result; + } + + if (!object.contains("fixture") || !object["fixture"].is_object()) { + return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureObject); + } + + const auto& fixtureObject = object["fixture"]; + const nlohmann::json expected = fixtureObject.contains("expected") ? fixtureObject["expected"] : nlohmann::json(); + if (!expected.is_object()) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); + + auto readRequiredFixtureString = [&](const nlohmann::json& source, const char* key) -> std::optional { + auto value = readJsonString(source, key); + if (!value || value->empty()) return std::nullopt; + return value; + }; + + HushChatCompatibilityFixture fixture; + auto id = readRequiredFixtureString(fixtureObject, "id"); + auto storedChatKey = readRequiredFixtureString(fixtureObject, "stored_chat_key_hex"); + auto localPublicKey = readRequiredFixtureString(fixtureObject, "local_public_key_hex"); + auto peerPublicKey = readRequiredFixtureString(fixtureObject, "peer_public_key_hex"); + auto headerMemo = readRequiredFixtureString(fixtureObject, "header_memo"); + auto ciphertextMemo = readRequiredFixtureString(fixtureObject, "ciphertext_memo"); + auto directionText = readRequiredFixtureString(fixtureObject, "direction"); + auto roleText = readRequiredFixtureString(expected, "session_key_selection"); + auto storedKeySize = readJsonSize(expected, "stored_chat_key_bytes"); + auto seedSize = readJsonSize(expected, "seed_bytes"); + auto localKeySize = readJsonSize(expected, "local_public_key_bytes"); + auto peerKeySize = readJsonSize(expected, "peer_public_key_bytes"); + auto streamHeaderSize = readJsonSize(expected, "stream_header_bytes"); + auto ciphertextSize = readJsonSize(expected, "ciphertext_bytes"); + auto plaintextSize = readJsonSize(expected, "plaintext_bytes"); + if (!id || !storedChatKey || !localPublicKey || !peerPublicKey || !headerMemo || !ciphertextMemo || + !directionText || !roleText || !storedKeySize || !seedSize || !localKeySize || !peerKeySize || + !streamHeaderSize || !ciphertextSize || !plaintextSize) { + return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); + } + + auto direction = parseFixtureDirection(*directionText); + auto role = parseFixtureSessionKeySelection(*roleText); + if (!direction || !role) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); + + fixture.fixture_id = *id; + fixture.stored_chat_key_hex = *storedChatKey; + fixture.local_public_key_hex = *localPublicKey; + fixture.peer_public_key_hex = *peerPublicKey; + fixture.header_memo = *headerMemo; + fixture.ciphertext_memo = *ciphertextMemo; + fixture.direction = *direction; + fixture.expected_session_key_selection = *role; + fixture.expected_stored_chat_key_size = *storedKeySize; + fixture.expected_seed_size = *seedSize; + fixture.expected_local_public_key_size = *localKeySize; + fixture.expected_peer_public_key_size = *peerKeySize; + fixture.expected_stream_header_size = *streamHeaderSize; + fixture.expected_ciphertext_size = *ciphertextSize; + fixture.expected_plaintext_size = *plaintextSize; + if (auto plaintextHash = readJsonString(expected, "plaintext_hash_hex")) { + fixture.expected_plaintext_hash_hex = *plaintextHash; + } + + parsed.fixture_id = fixture.fixture_id; + parsed.fixture = fixture; + + auto verification = verifyHushChatCompatibilityFixture(fixture, true); + if (parsed.kind == HushChatCompatibilityFixtureKind::ContactExclusion) { + if (verification.error != HushChatCompatibilityFixtureError::NonMemoHeader) { + HushChatCompatibilityFixtureFileParseResult result = failFile( + HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded); + result.file = std::move(parsed); + result.verification = std::move(verification); + return result; + } + + HushChatCompatibilityFixtureFileParseResult result; + result.ok = true; + result.feature_enabled = true; + result.excluded_from_decrypt = true; + result.error = HushChatCompatibilityFixtureFileError::None; + result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); + result.file = std::move(parsed); + result.verification = std::move(verification); + return result; + } + + if (!verification.ok) { + HushChatCompatibilityFixtureFileParseResult result = failFile( + HushChatCompatibilityFixtureFileError::FixtureVerificationFailed); + result.file = std::move(parsed); + result.verification = std::move(verification); + return result; + } + + HushChatCompatibilityFixtureFileParseResult result; + result.ok = true; + result.feature_enabled = true; + result.verified = true; + result.error = HushChatCompatibilityFixtureFileError::None; + result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); + result.file = std::move(parsed); + result.verification = std::move(verification); + return result; +} + +HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile( + const std::string& path, + bool featureEnabled) +{ + if (!featureEnabled) { + HushChatCompatibilityFixtureFileParseResult result; + result.error = HushChatCompatibilityFixtureFileError::FeatureDisabled; + result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); + return result; + } + + std::ifstream input(path); + if (!input.good()) { + HushChatCompatibilityFixtureFileParseResult result; + result.feature_enabled = true; + result.error = HushChatCompatibilityFixtureFileError::FileReadFailed; + result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); + return result; + } + + std::ostringstream buffer; + buffer << input.rdbuf(); + return parseHushChatCompatibilityFixtureFile(buffer.str(), true); +} + +HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection( + const HushChatCompatibilityFixture& fixture, + bool featureEnabled) +{ + auto failProjection = [&](HushChatSeedPublicKeyProjectionError error, + HushChatHexDecodeError hexError = HushChatHexDecodeError::None) { + HushChatSeedPublicKeyProjectionResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatSeedPublicKeyProjectionErrorName(error); + result.hex_error = hexError; + return result; + }; + + if (!featureEnabled) return failProjection(HushChatSeedPublicKeyProjectionError::FeatureDisabled); + if (fixture.fixture_id.empty()) return failProjection(HushChatSeedPublicKeyProjectionError::MissingFixtureId); + + auto storedChatKey = decodeHushChatHexBytes(fixture.stored_chat_key_hex, + kHushChatStoredChatKeyByteLength); + if (!storedChatKey.ok) { + return failProjection(HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey, storedChatKey.error); + } + + auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex, + kHushChatPublicKeyByteLength); + if (!localPublicKey.ok) { + return failProjection(HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey, localPublicKey.error); + } + + const std::size_t seedSize = std::min(fixture.stored_chat_key_hex.size(), kHushChatSeedByteLength); + if (storedChatKey.bytes.size() != fixture.expected_stored_chat_key_size) { + auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch); + result.stored_chat_key_size = storedChatKey.bytes.size(); + result.seed_size = seedSize; + result.local_public_key_size = localPublicKey.bytes.size(); + return result; + } + if (seedSize != fixture.expected_seed_size) { + auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch); + result.stored_chat_key_size = storedChatKey.bytes.size(); + result.seed_size = seedSize; + result.local_public_key_size = localPublicKey.bytes.size(); + return result; + } + if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) { + auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch); + result.stored_chat_key_size = storedChatKey.bytes.size(); + result.seed_size = seedSize; + result.local_public_key_size = localPublicKey.bytes.size(); + return result; + } + + if (sodium_init() < 0) { + return failProjection(HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed); + } + + unsigned char seed[kHushChatSeedByteLength]; + for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) { + seed[index] = static_cast(fixture.stored_chat_key_hex[index]); + } + + unsigned char projectedPublicKey[crypto_kx_PUBLICKEYBYTES]; + unsigned char projectedSecretKey[crypto_kx_SECRETKEYBYTES]; + const int projectionStatus = crypto_kx_seed_keypair(projectedPublicKey, projectedSecretKey, seed); + sodium_memzero(projectedSecretKey, sizeof(projectedSecretKey)); + sodium_memzero(seed, sizeof(seed)); + if (projectionStatus != 0) { + return failProjection(HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed); + } + + HushChatSeedPublicKeyProjectionResult result; + result.feature_enabled = true; + result.stored_chat_key_size = storedChatKey.bytes.size(); + result.seed_size = seedSize; + result.local_public_key_size = localPublicKey.bytes.size(); + result.projected_public_key_size = sizeof(projectedPublicKey); + + if (result.projected_public_key_size != localPublicKey.bytes.size() || + std::memcmp(projectedPublicKey, localPublicKey.bytes.data(), localPublicKey.bytes.size()) != 0) { + sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey)); + result.error = HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch; + result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error); + return result; + } + + sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey)); + result.ok = true; + result.error = HushChatSeedPublicKeyProjectionError::None; + result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error); + return result; +} + +HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness( + const HushChatCompatibilityFixtureFileParseResult& parsed, + const HushChatSeedPublicKeyProjectionResult& seedProjection, + bool featureEnabled) +{ + auto failReadiness = [&](HushChatCorruptedAuthFailureReadinessError error) { + HushChatCorruptedAuthFailureReadinessResult result; + result.feature_enabled = featureEnabled; + result.error = error; + result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(error); + return result; + }; + + if (!featureEnabled) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FeatureDisabled); + if (parsed.pending) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixturePending); + if (parsed.file.kind != HushChatCompatibilityFixtureKind::CorruptedAuthFailure) { + return failReadiness(HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind); + } + if (!parsed.verified || !parsed.verification.ok) { + return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified); + } + if (!seedProjection.ok) { + return failReadiness(HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified); + } + + HushChatCorruptedAuthFailureReadinessResult result; + result.ok = true; + result.feature_enabled = true; + result.structurally_ready_for_future_auth_check = true; + result.requires_future_secretstream_auth_failure = true; + result.decrypted = false; + result.authenticated = false; + result.error = HushChatCorruptedAuthFailureReadinessError::None; + result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(result.error); + return result; +} + +std::vector hushChatRequiredCompatibilityFixtureKinds() +{ + return { + HushChatCompatibilityFixtureKind::IncomingMemo, + HushChatCompatibilityFixtureKind::OutgoingMemo, + HushChatCompatibilityFixtureKind::SeedPublicKeyProjection, + HushChatCompatibilityFixtureKind::CorruptedAuthFailure, + HushChatCompatibilityFixtureKind::ContactExclusion + }; +} + +HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist( + const std::vector& candidates, + bool featureEnabled) +{ + HushChatCompatibilityFixtureImportChecklistResult result; + result.feature_enabled = featureEnabled; + + auto setFirstError = [&](HushChatCompatibilityFixtureImportError error) { + if (result.error == HushChatCompatibilityFixtureImportError::None) { + result.error = error; + result.error_name = hushChatCompatibilityFixtureImportErrorName(error); + } + }; + + if (!featureEnabled) { + setFirstError(HushChatCompatibilityFixtureImportError::FeatureDisabled); + return result; + } + + const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds(); + result.required_count = requiredKinds.size(); + result.items.reserve(requiredKinds.size()); + + for (const auto expectedKind : requiredKinds) { + HushChatCompatibilityFixtureImportItem item; + item.expected_kind = expectedKind; + + const HushChatCompatibilityFixtureImportCandidate* selected = nullptr; + std::size_t matchCount = 0; + for (const auto& candidate : candidates) { + if (candidate.expected_kind != expectedKind) continue; + ++matchCount; + if (!selected) selected = &candidate; + } + + if (matchCount == 0) { + item.error = HushChatCompatibilityFixtureImportError::MissingRequiredKind; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.missing_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.supplied = true; + ++result.supplied_count; + + if (matchCount > 1 || !selected) { + item.error = HushChatCompatibilityFixtureImportError::DuplicateKind; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.path = selected->path; + item.parsed = loadHushChatCompatibilityFixtureFile(selected->path, true); + if (!item.parsed.ok) { + if (item.parsed.error == HushChatCompatibilityFixtureFileError::FileReadFailed) { + item.error = HushChatCompatibilityFixtureImportError::FixtureLoadFailed; + } else if (item.parsed.error == HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded) { + item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded; + } else { + item.error = HushChatCompatibilityFixtureImportError::FixtureInvalid; + } + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.loaded_kind = item.parsed.file.kind; + if (item.loaded_kind != expectedKind) { + item.error = HushChatCompatibilityFixtureImportError::FixtureKindMismatch; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + if (item.parsed.pending) { + item.pending = true; + item.error = HushChatCompatibilityFixtureImportError::FixturePending; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.pending_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + if (expectedKind == HushChatCompatibilityFixtureKind::ContactExclusion) { + if (!item.parsed.excluded_from_decrypt) { + item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.replacement_eligible = true; + item.error = HushChatCompatibilityFixtureImportError::None; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.excluded_count; + result.items.push_back(std::move(item)); + continue; + } + + if (!item.parsed.verified) { + item.error = HushChatCompatibilityFixtureImportError::FixtureNotVerified; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.seed_projection = verifyHushChatSeedPublicKeyProjection(item.parsed.file.fixture, true); + if (!item.seed_projection.ok) { + item.error = HushChatCompatibilityFixtureImportError::SeedProjectionFailed; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + if (expectedKind == HushChatCompatibilityFixtureKind::CorruptedAuthFailure) { + item.auth_failure_readiness = inspectHushChatCorruptedAuthFailureReadiness( + item.parsed, + item.seed_projection, + true); + if (!item.auth_failure_readiness.ok) { + item.error = HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.rejected_count; + setFirstError(item.error); + result.items.push_back(std::move(item)); + continue; + } + + item.future_auth_failure_required = item.auth_failure_readiness.requires_future_secretstream_auth_failure; + item.structurally_ready_for_future_auth_check = + item.auth_failure_readiness.structurally_ready_for_future_auth_check; + if (item.future_auth_failure_required) ++result.future_auth_failure_required_count; + if (item.structurally_ready_for_future_auth_check) ++result.auth_failure_structural_ready_count; + } + + item.replacement_eligible = true; + item.seed_projection_verified = true; + item.error = HushChatCompatibilityFixtureImportError::None; + item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); + ++result.verified_count; + ++result.seed_projection_verified_count; + result.items.push_back(std::move(item)); + } + + result.replacement_ready = result.supplied_count == result.required_count && + result.missing_count == 0 && + result.pending_count == 0 && + result.rejected_count == 0 && + result.verified_count == result.required_count - 1 && + result.seed_projection_verified_count == result.required_count - 1 && + result.future_auth_failure_required_count == 1 && + result.auth_failure_structural_ready_count == 1 && + result.excluded_count == 1; + result.ok = result.replacement_ready; + if (result.ok) { + result.error = HushChatCompatibilityFixtureImportError::None; + result.error_name = hushChatCompatibilityFixtureImportErrorName(result.error); + } + return result; +} + +HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun( + const std::vector& candidates, + bool featureEnabled) +{ + const auto checklist = inspectHushChatCompatibilityFixtureImportChecklist(candidates, featureEnabled); + + HushChatCompatibilityFixtureReplacementDryRunResult result; + result.ok = checklist.replacement_ready; + result.feature_enabled = checklist.feature_enabled; + result.would_replace = checklist.replacement_ready; + result.replacement_refused = !checklist.replacement_ready; + result.error = checklist.error; + result.error_name = checklist.error_name; + result.required_count = checklist.required_count; + result.supplied_count = checklist.supplied_count; + result.missing_count = checklist.missing_count; + result.pending_count = checklist.pending_count; + result.verified_count = checklist.verified_count; + result.seed_projection_verified_count = checklist.seed_projection_verified_count; + result.future_auth_failure_required_count = checklist.future_auth_failure_required_count; + result.auth_failure_structural_ready_count = checklist.auth_failure_structural_ready_count; + result.excluded_count = checklist.excluded_count; + result.rejected_count = checklist.rejected_count; + result.report_items.reserve(checklist.items.size()); + + for (const auto& item : checklist.items) { + HushChatCompatibilityFixtureReplacementReportItem reportItem; + reportItem.expected_kind = item.expected_kind; + reportItem.loaded_kind = item.loaded_kind; + reportItem.path = item.path; + reportItem.supplied = item.supplied; + reportItem.pending = item.pending; + reportItem.replacement_eligible = item.replacement_eligible; + reportItem.refused = !item.replacement_eligible; + reportItem.seed_projection_verified = item.seed_projection_verified; + reportItem.future_auth_failure_required = item.future_auth_failure_required; + reportItem.structurally_ready_for_future_auth_check = item.structurally_ready_for_future_auth_check; + reportItem.cont_excluded = item.parsed.excluded_from_decrypt; + reportItem.decrypted = item.auth_failure_readiness.decrypted; + reportItem.authenticated = item.auth_failure_readiness.authenticated; + reportItem.error = item.error; + reportItem.error_name = item.error_name; + result.report_items.push_back(std::move(reportItem)); + } + + return result; +} + +HushChatCaptureManifestValidationResult validateHushChatCaptureManifest( + const std::string& jsonText, + bool featureEnabled) +{ + HushChatCaptureManifestValidationResult result; + result.feature_enabled = featureEnabled; + const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds(); + result.required_count = requiredKinds.size(); + result.categories.reserve(requiredKinds.size()); + for (const auto kind : requiredKinds) { + HushChatCaptureManifestCategoryReport report; + report.kind = kind; + result.categories.push_back(std::move(report)); + } + + auto failManifest = [&](HushChatCaptureManifestError error) { + result.error = error; + result.error_name = hushChatCaptureManifestErrorName(error); + return result; + }; + + if (!featureEnabled) return failManifest(HushChatCaptureManifestError::FeatureDisabled); + + nlohmann::json object; + try { + object = nlohmann::json::parse(jsonText); + } catch (const nlohmann::json::parse_error&) { + return failManifest(HushChatCaptureManifestError::InvalidJson); + } + if (!object.is_object()) return failManifest(HushChatCaptureManifestError::JsonNotObject); + + result.prohibited_field_count = countProhibitedCaptureManifestFields(object); + if (result.prohibited_field_count > 0) { + return failManifest(HushChatCaptureManifestError::ProhibitedFieldPresent); + } + + auto schema = readJsonString(object, "schema"); + if (!schema || *schema != kHushChatCaptureManifestSchema) { + return failManifest(HushChatCaptureManifestError::InvalidSchema); + } + + auto id = readJsonString(object, "id"); + if (!id || id->empty()) return failManifest(HushChatCaptureManifestError::MissingManifestId); + + auto status = readJsonString(object, "status"); + if (!status) return failManifest(HushChatCaptureManifestError::MissingStatus); + if (*status != "staged") return failManifest(HushChatCaptureManifestError::UnknownStatus); + + auto fixtureDirectory = readJsonString(object, "fixture_directory"); + if (!fixtureDirectory || fixtureDirectory->empty()) { + return failManifest(HushChatCaptureManifestError::MissingFixtureDirectory); + } + result.fixture_directory = *fixtureDirectory; + + auto dryRunCommand = readJsonString(object, "dry_run_command"); + if (!dryRunCommand || dryRunCommand->empty()) { + return failManifest(HushChatCaptureManifestError::MissingDryRunCommand); + } + result.has_dry_run_command = true; + if (dryRunCommand->find("HushChatFixtureCheck") == std::string::npos || + dryRunCommand->find("--replacement-dry-run") == std::string::npos) { + return failManifest(HushChatCaptureManifestError::InvalidDryRunCommand); + } + + auto provenance = object.find("provenance"); + if (provenance == object.end() || !provenance->is_object()) { + return failManifest(HushChatCaptureManifestError::MissingProvenance); + } + auto sourceClient = readJsonString(*provenance, "source_client"); + if (!sourceClient || sourceClient->empty()) { + return failManifest(HushChatCaptureManifestError::MissingSourceClient); + } + if (*sourceClient != "SilentDragonXLite") { + return failManifest(HushChatCaptureManifestError::InvalidSourceClient); + } + auto sourceClientVersion = readJsonString(*provenance, "source_client_version"); + if (!sourceClientVersion || sourceClientVersion->empty()) { + return failManifest(HushChatCaptureManifestError::MissingSourceClientVersion); + } + auto captureDate = readJsonString(*provenance, "capture_date"); + if (!captureDate || captureDate->empty()) return failManifest(HushChatCaptureManifestError::MissingCaptureDate); + auto network = readJsonString(*provenance, "network"); + if (!network || network->empty()) return failManifest(HushChatCaptureManifestError::MissingNetwork); + auto captureMethod = readJsonString(*provenance, "capture_method"); + if (!captureMethod || captureMethod->empty()) { + return failManifest(HushChatCaptureManifestError::MissingCaptureMethod); + } + + auto handling = object.find("handling"); + if (handling == object.end() || !handling->is_object()) { + return failManifest(HushChatCaptureManifestError::MissingHandling); + } + for (const char* flag : requiredCaptureHandlingFlags()) { + auto flagValue = handling->find(flag); + if (flagValue == handling->end()) return failManifest(HushChatCaptureManifestError::MissingHandlingFlag); + if (!flagValue->is_boolean() || !flagValue->get()) { + return failManifest(HushChatCaptureManifestError::HandlingFlagNotTrue); + } + ++result.handling_flag_count; + } + result.no_sensitive_material_declared = true; + + auto categories = object.find("categories"); + if (categories == object.end() || !categories->is_array()) { + return failManifest(HushChatCaptureManifestError::MissingCategories); + } + + auto findCategoryReport = [&](HushChatCompatibilityFixtureKind kind) -> HushChatCaptureManifestCategoryReport* { + for (auto& report : result.categories) { + if (report.kind == kind) return &report; + } + return nullptr; + }; + + for (const auto& category : *categories) { + if (!category.is_object()) return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry); + + auto kindText = readJsonString(category, "kind"); + auto stagedFilename = readJsonString(category, "staged_filename"); + auto categoryStatus = readJsonString(category, "status"); + if (!kindText || kindText->empty() || !stagedFilename || stagedFilename->empty() || + !categoryStatus || *categoryStatus != "ready") { + return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry); + } + + auto kind = parseFixtureKind(*kindText); + if (!kind) return failManifest(HushChatCaptureManifestError::UnknownCategory); + + auto* report = findCategoryReport(*kind); + if (!report) return failManifest(HushChatCaptureManifestError::UnknownCategory); + if (report->declared) { + ++result.duplicate_count; + return failManifest(HushChatCaptureManifestError::DuplicateCategory); + } + + report->declared = true; + report->staged_filename = *stagedFilename; + ++result.declared_count; + } + + for (const auto& report : result.categories) { + if (!report.declared) ++result.missing_count; + } + if (result.missing_count > 0) return failManifest(HushChatCaptureManifestError::MissingRequiredCategory); + + result.ok = true; + result.error = HushChatCaptureManifestError::None; + result.error_name = hushChatCaptureManifestErrorName(result.error); + return result; +} + +HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile( + const std::string& path, + bool featureEnabled) +{ + if (!featureEnabled) { + HushChatCaptureManifestValidationResult result; + result.error = HushChatCaptureManifestError::FeatureDisabled; + result.error_name = hushChatCaptureManifestErrorName(result.error); + return result; + } + + std::ifstream input(path); + if (!input.good()) { + HushChatCaptureManifestValidationResult result; + result.feature_enabled = true; + result.manifest_path = path; + result.error = HushChatCaptureManifestError::FileReadFailed; + result.error_name = hushChatCaptureManifestErrorName(result.error); + return result; + } + + std::ostringstream buffer; + buffer << input.rdbuf(); + auto result = validateHushChatCaptureManifest(buffer.str(), true); + result.manifest_path = path; + return result; +} + +} // namespace dragonx::chat diff --git a/src/chat/chat_fixture_tooling.h b/src/chat/chat_fixture_tooling.h new file mode 100644 index 0000000..eab4a0e --- /dev/null +++ b/src/chat/chat_fixture_tooling.h @@ -0,0 +1,505 @@ +#pragma once + +#include "chat_protocol.h" + +#include +#include +#include + +// HushChat compatibility-fixture / capture-manifest / seed-projection tooling. +// +// Everything in this header is DEAD AT RUNTIME in the shipping app and test +// binaries — its only caller is the standalone dev CLI +// tools/hushchat_fixture_check.cpp. It is deliberately compiled ONLY into the +// HushChatFixtureCheck target so this validation scaffolding (and its libsodium +// seed-projection path) never reaches the wallet binary. Keep it that way: do +// not add chat_fixture_tooling.cpp to APP_SOURCES or the test target. +// +// It builds on the runtime types declared in chat_protocol.h. + +namespace dragonx::chat { + +enum class HushChatDecryptPreflightError { + None, + FeatureDisabled, + NonMessageHeader, + InvalidHeaderNumber, + UnsupportedVersion, + MissingReplyAddress, + MissingConversationId, + InvalidSecretstreamHeader, + InvalidPublicKey, + EmptyCiphertext, + OversizedCiphertext, + OddLengthCiphertext, + InvalidCiphertextHex, + TruncatedCiphertext +}; + +struct HushChatDecryptPreflightInput { + HushChatHeader header; + std::string ciphertext_hex; +}; + +struct HushChatDecryptPreflightResult { + bool ok = false; + bool feature_enabled = false; + HushChatDecryptPreflightError error = HushChatDecryptPreflightError::None; + const char* error_name = "None"; + std::size_t ciphertext_size = 0; +}; + +enum class HushChatHexDecodeError { + None, + Empty, + OddLength, + InvalidHex, + UnexpectedByteLength +}; + +struct HushChatHexDecodeResult { + bool ok = false; + HushChatHexDecodeError error = HushChatHexDecodeError::None; + const char* error_name = "None"; + std::vector bytes; +}; + +enum class HushChatDecryptDirection { + Incoming, + Outgoing +}; + +enum class HushChatSessionKeySelection { + ClientRx, + ServerTx +}; + +enum class HushChatDecryptInputError { + None, + FeatureDisabled, + InvalidStoredChatKey, + DecryptPreflightFailed, + InvalidPeerPublicKey, + InvalidStreamHeader, + InvalidCiphertext +}; + +struct HushChatDecryptInputMaterial { + std::string stored_chat_key_hex; + HushChatHeader header; + std::string ciphertext_hex; + HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; + std::string peer_public_key_hex; +}; + +struct HushChatPreparedDecryptInput { + std::vector stored_chat_key_bytes; + std::vector seed_bytes; + std::vector peer_public_key_bytes; + std::vector stream_header_bytes; + std::vector ciphertext_bytes; + HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; + HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx; + std::size_t plaintext_capacity = 0; +}; + +struct HushChatDecryptInputPreparationResult { + bool ok = false; + bool feature_enabled = false; + HushChatDecryptInputError error = HushChatDecryptInputError::None; + const char* error_name = "None"; + HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; + HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None; + HushChatPreparedDecryptInput prepared; +}; + +struct HushChatDecryptFixtureReadinessResult { + bool ready = false; + std::size_t stored_chat_key_size = 0; + std::size_t seed_size = 0; + std::size_t peer_public_key_size = 0; + std::size_t stream_header_size = 0; + std::size_t ciphertext_size = 0; + std::size_t plaintext_capacity = 0; + HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx; +}; + +enum class HushChatCompatibilityFixtureError { + None, + FeatureDisabled, + MissingFixtureId, + InvalidLocalPublicKey, + InvalidPeerPublicKey, + InvalidHeaderMemo, + InvalidMemoPair, + NonMemoHeader, + HeaderPublicKeyMismatch, + DecryptInputFailed, + NotFixtureReady, + ExpectedStoredChatKeyLengthMismatch, + ExpectedSeedLengthMismatch, + ExpectedLocalPublicKeyLengthMismatch, + ExpectedPeerPublicKeyLengthMismatch, + ExpectedStreamHeaderLengthMismatch, + ExpectedCiphertextLengthMismatch, + ExpectedPlaintextLengthMismatch, + ExpectedRoleMismatch, + InvalidPlaintextHash +}; + +struct HushChatCompatibilityFixture { + std::string fixture_id; + std::string stored_chat_key_hex; + std::string local_public_key_hex; + std::string peer_public_key_hex; + std::string header_memo; + std::string ciphertext_memo; + HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; + HushChatSessionKeySelection expected_session_key_selection = HushChatSessionKeySelection::ClientRx; + std::size_t expected_stored_chat_key_size = 32; + std::size_t expected_seed_size = 32; + std::size_t expected_local_public_key_size = 32; + std::size_t expected_peer_public_key_size = 32; + std::size_t expected_stream_header_size = 24; + std::size_t expected_ciphertext_size = 0; + std::size_t expected_plaintext_size = 0; + std::string expected_plaintext_hash_hex; +}; + +struct HushChatCompatibilityFixtureVerificationResult { + bool ok = false; + bool feature_enabled = false; + HushChatCompatibilityFixtureError error = HushChatCompatibilityFixtureError::None; + const char* error_name = "None"; + HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; + HushChatDecryptInputError decrypt_input_error = HushChatDecryptInputError::None; + HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None; + HushChatHeader header; + HushChatDecryptInputPreparationResult preparation; + HushChatDecryptFixtureReadinessResult readiness; + std::size_t local_public_key_size = 0; + std::size_t peer_public_key_size = 0; + std::size_t plaintext_hash_size = 0; +}; + +enum class HushChatCompatibilityFixtureKind { + IncomingMemo, + OutgoingMemo, + SeedPublicKeyProjection, + CorruptedAuthFailure, + ContactExclusion +}; + +enum class HushChatCompatibilityFixtureFileStatus { + Pending, + Ready +}; + +enum class HushChatCompatibilityFixtureFileError { + None, + FeatureDisabled, + InvalidJson, + JsonNotObject, + InvalidSchema, + MissingKind, + UnknownKind, + MissingStatus, + UnknownStatus, + MissingFixtureId, + MissingPendingReason, + MissingFixtureObject, + InvalidFixtureField, + FixtureVerificationFailed, + ContactFixtureNotExcluded, + FileReadFailed +}; + +struct HushChatCompatibilityFixtureFile { + std::string schema; + HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo; + HushChatCompatibilityFixtureFileStatus status = HushChatCompatibilityFixtureFileStatus::Pending; + std::string fixture_id; + std::string pending_reason; + HushChatCompatibilityFixture fixture; +}; + +struct HushChatCompatibilityFixtureFileParseResult { + bool ok = false; + bool feature_enabled = false; + bool pending = false; + bool verified = false; + bool excluded_from_decrypt = false; + HushChatCompatibilityFixtureFileError error = HushChatCompatibilityFixtureFileError::None; + const char* error_name = "None"; + HushChatCompatibilityFixtureFile file; + HushChatCompatibilityFixtureVerificationResult verification; +}; + +enum class HushChatSeedPublicKeyProjectionError { + None, + FeatureDisabled, + MissingFixtureId, + InvalidStoredChatKey, + InvalidLocalPublicKey, + ExpectedStoredChatKeyLengthMismatch, + ExpectedSeedLengthMismatch, + ExpectedLocalPublicKeyLengthMismatch, + SodiumInitializationFailed, + KeypairProjectionFailed, + ProjectedPublicKeyMismatch +}; + +struct HushChatSeedPublicKeyProjectionResult { + bool ok = false; + bool feature_enabled = false; + HushChatSeedPublicKeyProjectionError error = HushChatSeedPublicKeyProjectionError::None; + const char* error_name = "None"; + HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; + std::size_t stored_chat_key_size = 0; + std::size_t seed_size = 0; + std::size_t local_public_key_size = 0; + std::size_t projected_public_key_size = 0; +}; + +enum class HushChatCorruptedAuthFailureReadinessError { + None, + FeatureDisabled, + FixturePending, + WrongFixtureKind, + FixtureNotVerified, + SeedProjectionNotVerified +}; + +struct HushChatCorruptedAuthFailureReadinessResult { + bool ok = false; + bool feature_enabled = false; + bool structurally_ready_for_future_auth_check = false; + bool requires_future_secretstream_auth_failure = false; + bool decrypted = false; + bool authenticated = false; + HushChatCorruptedAuthFailureReadinessError error = HushChatCorruptedAuthFailureReadinessError::None; + const char* error_name = "None"; +}; + +enum class HushChatCompatibilityFixtureImportError { + None, + FeatureDisabled, + MissingRequiredKind, + DuplicateKind, + FixtureLoadFailed, + FixtureKindMismatch, + FixturePending, + FixtureInvalid, + FixtureNotVerified, + SeedProjectionFailed, + AuthFailureScaffoldFailed, + ContactFixtureNotExcluded +}; + +struct HushChatCompatibilityFixtureImportCandidate { + HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; + std::string path; +}; + +struct HushChatCompatibilityFixtureImportItem { + HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; + HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo; + std::string path; + bool supplied = false; + bool pending = false; + bool replacement_eligible = false; + bool seed_projection_verified = false; + bool future_auth_failure_required = false; + bool structurally_ready_for_future_auth_check = false; + HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; + const char* error_name = "None"; + HushChatCompatibilityFixtureFileParseResult parsed; + HushChatSeedPublicKeyProjectionResult seed_projection; + HushChatCorruptedAuthFailureReadinessResult auth_failure_readiness; +}; + +struct HushChatCompatibilityFixtureImportChecklistResult { + bool ok = false; + bool feature_enabled = false; + bool replacement_ready = false; + HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; + const char* error_name = "None"; + std::size_t required_count = 0; + std::size_t supplied_count = 0; + std::size_t missing_count = 0; + std::size_t pending_count = 0; + std::size_t verified_count = 0; + std::size_t seed_projection_verified_count = 0; + std::size_t future_auth_failure_required_count = 0; + std::size_t auth_failure_structural_ready_count = 0; + std::size_t excluded_count = 0; + std::size_t rejected_count = 0; + std::vector items; +}; + +struct HushChatCompatibilityFixtureReplacementReportItem { + HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; + HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo; + std::string path; + bool supplied = false; + bool pending = false; + bool replacement_eligible = false; + bool refused = true; + bool seed_projection_verified = false; + bool future_auth_failure_required = false; + bool structurally_ready_for_future_auth_check = false; + bool cont_excluded = false; + bool decrypted = false; + bool authenticated = false; + HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; + const char* error_name = "None"; +}; + +struct HushChatCompatibilityFixtureReplacementDryRunResult { + bool ok = false; + bool feature_enabled = false; + bool dry_run_only = true; + bool redacted_report = true; + bool would_replace = false; + bool replacement_refused = true; + HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; + const char* error_name = "None"; + std::size_t required_count = 0; + std::size_t supplied_count = 0; + std::size_t missing_count = 0; + std::size_t pending_count = 0; + std::size_t verified_count = 0; + std::size_t seed_projection_verified_count = 0; + std::size_t future_auth_failure_required_count = 0; + std::size_t auth_failure_structural_ready_count = 0; + std::size_t excluded_count = 0; + std::size_t rejected_count = 0; + std::vector report_items; +}; + +enum class HushChatCaptureManifestError { + None, + FeatureDisabled, + FileReadFailed, + InvalidJson, + JsonNotObject, + InvalidSchema, + MissingManifestId, + MissingStatus, + UnknownStatus, + MissingFixtureDirectory, + MissingDryRunCommand, + InvalidDryRunCommand, + MissingProvenance, + MissingSourceClient, + InvalidSourceClient, + MissingSourceClientVersion, + MissingCaptureDate, + MissingNetwork, + MissingCaptureMethod, + MissingHandling, + MissingHandlingFlag, + HandlingFlagNotTrue, + MissingCategories, + InvalidCategoryEntry, + UnknownCategory, + DuplicateCategory, + MissingRequiredCategory, + ProhibitedFieldPresent +}; + +enum class HushChatCaptureManifestStatus { + Staged +}; + +struct HushChatCaptureManifestCategoryReport { + HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo; + std::string staged_filename; + bool declared = false; +}; + +struct HushChatCaptureManifestValidationResult { + bool ok = false; + bool feature_enabled = false; + bool redacted_report = true; + bool validates_provenance_only = true; + bool no_sensitive_material_declared = false; + bool has_dry_run_command = false; + HushChatCaptureManifestError error = HushChatCaptureManifestError::None; + const char* error_name = "None"; + HushChatCaptureManifestStatus status = HushChatCaptureManifestStatus::Staged; + std::string manifest_path; + std::string fixture_directory; + std::size_t required_count = 0; + std::size_t declared_count = 0; + std::size_t missing_count = 0; + std::size_t duplicate_count = 0; + std::size_t prohibited_field_count = 0; + std::size_t handling_flag_count = 0; + std::vector categories; +}; + +constexpr std::size_t kHushChatSecretstreamABytes = 17; +constexpr std::size_t kHushChatStoredChatKeyByteLength = 32; +constexpr std::size_t kHushChatStoredChatKeyHexLength = kHushChatStoredChatKeyByteLength * 2; +constexpr std::size_t kHushChatSeedByteLength = 32; +constexpr std::size_t kHushChatPublicKeyByteLength = kHushChatPublicKeyHexLength / 2; +constexpr std::size_t kHushChatSecretstreamHeaderByteLength = kHushChatSecretstreamHeaderHexLength / 2; +constexpr const char* kHushChatCompatibilityFixtureSchema = "dragonx.hushchat.compat-fixture.v1"; +constexpr const char* kHushChatCaptureManifestSchema = "dragonx.hushchat.capture-manifest.v1"; + +HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight( + const HushChatDecryptPreflightInput& input, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex, + std::size_t expectedByteLength); +HushChatDecryptInputPreparationResult prepareHushChatDecryptInput( + const HushChatDecryptInputMaterial& material, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness( + const HushChatPreparedDecryptInput& prepared); +HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction); +HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture( + const HushChatCompatibilityFixture& fixture, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile( + const std::string& jsonText, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile( + const std::string& path, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection( + const HushChatCompatibilityFixture& fixture, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness( + const HushChatCompatibilityFixtureFileParseResult& parsed, + const HushChatSeedPublicKeyProjectionResult& seedProjection, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +std::vector hushChatRequiredCompatibilityFixtureKinds(); +HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist( + const std::vector& candidates, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun( + const std::vector& candidates, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCaptureManifestValidationResult validateHushChatCaptureManifest( + const std::string& jsonText, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile( + const std::string& path, + bool featureEnabled = hushChatFeatureEnabledAtBuild()); +const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error); +const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error); +const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction); +const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection); +const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error); +const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error); +const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind); +const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status); +const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error); +const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error); +const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error); +const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error); +const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error); + +} // namespace dragonx::chat diff --git a/src/chat/chat_protocol.cpp b/src/chat/chat_protocol.cpp index 2e348fd..f9db76f 100644 --- a/src/chat/chat_protocol.cpp +++ b/src/chat/chat_protocol.cpp @@ -1,14 +1,10 @@ #include "chat_protocol.h" #include -#include #include #include -#include -#include #include -#include #include namespace dragonx::chat { @@ -22,14 +18,6 @@ bool isHexString(const std::string& value) return true; } -std::optional hexNibble(unsigned char character) -{ - if (character >= '0' && character <= '9') return static_cast(character - '0'); - if (character >= 'a' && character <= 'f') return static_cast(character - 'a' + 10); - if (character >= 'A' && character <= 'F') return static_cast(character - 'A' + 10); - return std::nullopt; -} - bool isCiphertextPayloadCandidate(const std::string& value) { return !value.empty() && value.size() <= kHushChatMemoByteLimit && @@ -84,132 +72,6 @@ HushChatHeaderParseResult fail(std::string error) return result; } -std::optional readJsonString(const nlohmann::json& object, const char* key) -{ - auto it = object.find(key); - if (it == object.end() || !it->is_string()) return std::nullopt; - return it->get(); -} - -std::optional readJsonSize(const nlohmann::json& object, const char* key) -{ - auto it = object.find(key); - if (it == object.end() || (!it->is_number_integer() && !it->is_number_unsigned())) return std::nullopt; - if (it->is_number_unsigned()) return it->get(); - auto value = it->get(); - if (value < 0) return std::nullopt; - return static_cast(value); -} - -std::optional parseFixtureDirection(const std::string& value) -{ - if (value == "Incoming") return HushChatDecryptDirection::Incoming; - if (value == "Outgoing") return HushChatDecryptDirection::Outgoing; - return std::nullopt; -} - -std::optional parseFixtureSessionKeySelection(const std::string& value) -{ - if (value == "ClientRx") return HushChatSessionKeySelection::ClientRx; - if (value == "ServerTx") return HushChatSessionKeySelection::ServerTx; - return std::nullopt; -} - -std::optional parseFixtureKind(const std::string& value) -{ - if (value == "incoming_memo") return HushChatCompatibilityFixtureKind::IncomingMemo; - if (value == "outgoing_memo") return HushChatCompatibilityFixtureKind::OutgoingMemo; - if (value == "seed_public_key_projection") return HushChatCompatibilityFixtureKind::SeedPublicKeyProjection; - if (value == "corrupted_auth_failure") return HushChatCompatibilityFixtureKind::CorruptedAuthFailure; - if (value == "cont_exclusion") return HushChatCompatibilityFixtureKind::ContactExclusion; - return std::nullopt; -} - -std::optional parseFixtureFileStatus(const std::string& value) -{ - if (value == "pending") return HushChatCompatibilityFixtureFileStatus::Pending; - if (value == "ready") return HushChatCompatibilityFixtureFileStatus::Ready; - return std::nullopt; -} - -std::string lowerCopy(const std::string& value) -{ - std::string lowered; - lowered.reserve(value.size()); - for (unsigned char ch : value) lowered.push_back(static_cast(std::tolower(ch))); - return lowered; -} - -bool isProhibitedCaptureManifestKey(const std::string& key) -{ - const std::string lowered = lowerCopy(key); - if (lowered.rfind("no_", 0) == 0) return false; - - static const std::vector prohibitedKeys = { - "fixture", - "fixtures", - "passphrase", - "password", - "plaintext", - "plaintext_hash_hex", - "memo", - "memostr", - "memo_contents", - "header_memo", - "ciphertext", - "ciphertext_memo", - "ciphertext_bytes", - "private_key", - "viewing_key", - "spending_key", - "wallet_seed", - "wallet_file", - "wallet_files", - "stored_chat_key_hex", - "local_public_key_hex", - "peer_public_key_hex", - "public_key_hex", - "secretstream_header", - "derived_key", - "session_key", - "secret_key", - "seed_bytes" - }; - - return std::find(prohibitedKeys.begin(), prohibitedKeys.end(), lowered) != prohibitedKeys.end(); -} - -std::size_t countProhibitedCaptureManifestFields(const nlohmann::json& value) -{ - std::size_t count = 0; - if (value.is_object()) { - for (auto it = value.begin(); it != value.end(); ++it) { - if (isProhibitedCaptureManifestKey(it.key())) ++count; - count += countProhibitedCaptureManifestFields(it.value()); - } - } else if (value.is_array()) { - for (const auto& item : value) count += countProhibitedCaptureManifestFields(item); - } - return count; -} - -std::vector requiredCaptureHandlingFlags() -{ - return { - "disposable_wallets_only", - "non_sensitive_vectors_only", - "no_passphrases", - "no_plaintext", - "no_memo_contents", - "no_private_keys", - "no_wallet_files", - "no_ciphertext_byte_dumps", - "no_derived_keys", - "no_session_keys", - "redacted_report_only" - }; -} - } // namespace const char* hushChatHeaderTypeName(HushChatHeaderType type) @@ -238,368 +100,6 @@ const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue) return "Unknown"; } -const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error) -{ - switch (error) { - case HushChatDecryptPreflightError::None: - return "None"; - case HushChatDecryptPreflightError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatDecryptPreflightError::NonMessageHeader: - return "NonMessageHeader"; - case HushChatDecryptPreflightError::InvalidHeaderNumber: - return "InvalidHeaderNumber"; - case HushChatDecryptPreflightError::UnsupportedVersion: - return "UnsupportedVersion"; - case HushChatDecryptPreflightError::MissingReplyAddress: - return "MissingReplyAddress"; - case HushChatDecryptPreflightError::MissingConversationId: - return "MissingConversationId"; - case HushChatDecryptPreflightError::InvalidSecretstreamHeader: - return "InvalidSecretstreamHeader"; - case HushChatDecryptPreflightError::InvalidPublicKey: - return "InvalidPublicKey"; - case HushChatDecryptPreflightError::EmptyCiphertext: - return "EmptyCiphertext"; - case HushChatDecryptPreflightError::OversizedCiphertext: - return "OversizedCiphertext"; - case HushChatDecryptPreflightError::OddLengthCiphertext: - return "OddLengthCiphertext"; - case HushChatDecryptPreflightError::InvalidCiphertextHex: - return "InvalidCiphertextHex"; - case HushChatDecryptPreflightError::TruncatedCiphertext: - return "TruncatedCiphertext"; - } - return "Unknown"; -} - -const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error) -{ - switch (error) { - case HushChatHexDecodeError::None: - return "None"; - case HushChatHexDecodeError::Empty: - return "Empty"; - case HushChatHexDecodeError::OddLength: - return "OddLength"; - case HushChatHexDecodeError::InvalidHex: - return "InvalidHex"; - case HushChatHexDecodeError::UnexpectedByteLength: - return "UnexpectedByteLength"; - } - return "Unknown"; -} - -const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction) -{ - switch (direction) { - case HushChatDecryptDirection::Incoming: - return "Incoming"; - case HushChatDecryptDirection::Outgoing: - return "Outgoing"; - } - return "Unknown"; -} - -const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection) -{ - switch (selection) { - case HushChatSessionKeySelection::ClientRx: - return "ClientRx"; - case HushChatSessionKeySelection::ServerTx: - return "ServerTx"; - } - return "Unknown"; -} - -const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error) -{ - switch (error) { - case HushChatDecryptInputError::None: - return "None"; - case HushChatDecryptInputError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatDecryptInputError::InvalidStoredChatKey: - return "InvalidStoredChatKey"; - case HushChatDecryptInputError::DecryptPreflightFailed: - return "DecryptPreflightFailed"; - case HushChatDecryptInputError::InvalidPeerPublicKey: - return "InvalidPeerPublicKey"; - case HushChatDecryptInputError::InvalidStreamHeader: - return "InvalidStreamHeader"; - case HushChatDecryptInputError::InvalidCiphertext: - return "InvalidCiphertext"; - } - return "Unknown"; -} - -const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error) -{ - switch (error) { - case HushChatCompatibilityFixtureError::None: - return "None"; - case HushChatCompatibilityFixtureError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatCompatibilityFixtureError::MissingFixtureId: - return "MissingFixtureId"; - case HushChatCompatibilityFixtureError::InvalidLocalPublicKey: - return "InvalidLocalPublicKey"; - case HushChatCompatibilityFixtureError::InvalidPeerPublicKey: - return "InvalidPeerPublicKey"; - case HushChatCompatibilityFixtureError::InvalidHeaderMemo: - return "InvalidHeaderMemo"; - case HushChatCompatibilityFixtureError::InvalidMemoPair: - return "InvalidMemoPair"; - case HushChatCompatibilityFixtureError::NonMemoHeader: - return "NonMemoHeader"; - case HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch: - return "HeaderPublicKeyMismatch"; - case HushChatCompatibilityFixtureError::DecryptInputFailed: - return "DecryptInputFailed"; - case HushChatCompatibilityFixtureError::NotFixtureReady: - return "NotFixtureReady"; - case HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch: - return "ExpectedStoredChatKeyLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch: - return "ExpectedSeedLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch: - return "ExpectedLocalPublicKeyLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch: - return "ExpectedPeerPublicKeyLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch: - return "ExpectedStreamHeaderLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch: - return "ExpectedCiphertextLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch: - return "ExpectedPlaintextLengthMismatch"; - case HushChatCompatibilityFixtureError::ExpectedRoleMismatch: - return "ExpectedRoleMismatch"; - case HushChatCompatibilityFixtureError::InvalidPlaintextHash: - return "InvalidPlaintextHash"; - } - return "Unknown"; -} - -const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind) -{ - switch (kind) { - case HushChatCompatibilityFixtureKind::IncomingMemo: - return "incoming_memo"; - case HushChatCompatibilityFixtureKind::OutgoingMemo: - return "outgoing_memo"; - case HushChatCompatibilityFixtureKind::SeedPublicKeyProjection: - return "seed_public_key_projection"; - case HushChatCompatibilityFixtureKind::CorruptedAuthFailure: - return "corrupted_auth_failure"; - case HushChatCompatibilityFixtureKind::ContactExclusion: - return "cont_exclusion"; - } - return "unknown"; -} - -const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status) -{ - switch (status) { - case HushChatCompatibilityFixtureFileStatus::Pending: - return "pending"; - case HushChatCompatibilityFixtureFileStatus::Ready: - return "ready"; - } - return "unknown"; -} - -const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error) -{ - switch (error) { - case HushChatCompatibilityFixtureFileError::None: - return "None"; - case HushChatCompatibilityFixtureFileError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatCompatibilityFixtureFileError::InvalidJson: - return "InvalidJson"; - case HushChatCompatibilityFixtureFileError::JsonNotObject: - return "JsonNotObject"; - case HushChatCompatibilityFixtureFileError::InvalidSchema: - return "InvalidSchema"; - case HushChatCompatibilityFixtureFileError::MissingKind: - return "MissingKind"; - case HushChatCompatibilityFixtureFileError::UnknownKind: - return "UnknownKind"; - case HushChatCompatibilityFixtureFileError::MissingStatus: - return "MissingStatus"; - case HushChatCompatibilityFixtureFileError::UnknownStatus: - return "UnknownStatus"; - case HushChatCompatibilityFixtureFileError::MissingFixtureId: - return "MissingFixtureId"; - case HushChatCompatibilityFixtureFileError::MissingPendingReason: - return "MissingPendingReason"; - case HushChatCompatibilityFixtureFileError::MissingFixtureObject: - return "MissingFixtureObject"; - case HushChatCompatibilityFixtureFileError::InvalidFixtureField: - return "InvalidFixtureField"; - case HushChatCompatibilityFixtureFileError::FixtureVerificationFailed: - return "FixtureVerificationFailed"; - case HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded: - return "ContactFixtureNotExcluded"; - case HushChatCompatibilityFixtureFileError::FileReadFailed: - return "FileReadFailed"; - } - return "Unknown"; -} - -const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error) -{ - switch (error) { - case HushChatSeedPublicKeyProjectionError::None: - return "None"; - case HushChatSeedPublicKeyProjectionError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatSeedPublicKeyProjectionError::MissingFixtureId: - return "MissingFixtureId"; - case HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey: - return "InvalidStoredChatKey"; - case HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey: - return "InvalidLocalPublicKey"; - case HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch: - return "ExpectedStoredChatKeyLengthMismatch"; - case HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch: - return "ExpectedSeedLengthMismatch"; - case HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch: - return "ExpectedLocalPublicKeyLengthMismatch"; - case HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed: - return "SodiumInitializationFailed"; - case HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed: - return "KeypairProjectionFailed"; - case HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch: - return "ProjectedPublicKeyMismatch"; - } - return "Unknown"; -} - -const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error) -{ - switch (error) { - case HushChatCorruptedAuthFailureReadinessError::None: - return "None"; - case HushChatCorruptedAuthFailureReadinessError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatCorruptedAuthFailureReadinessError::FixturePending: - return "FixturePending"; - case HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind: - return "WrongFixtureKind"; - case HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified: - return "FixtureNotVerified"; - case HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified: - return "SeedProjectionNotVerified"; - } - return "Unknown"; -} - -const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error) -{ - switch (error) { - case HushChatCompatibilityFixtureImportError::None: - return "None"; - case HushChatCompatibilityFixtureImportError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatCompatibilityFixtureImportError::MissingRequiredKind: - return "MissingRequiredKind"; - case HushChatCompatibilityFixtureImportError::DuplicateKind: - return "DuplicateKind"; - case HushChatCompatibilityFixtureImportError::FixtureLoadFailed: - return "FixtureLoadFailed"; - case HushChatCompatibilityFixtureImportError::FixtureKindMismatch: - return "FixtureKindMismatch"; - case HushChatCompatibilityFixtureImportError::FixturePending: - return "FixturePending"; - case HushChatCompatibilityFixtureImportError::FixtureInvalid: - return "FixtureInvalid"; - case HushChatCompatibilityFixtureImportError::FixtureNotVerified: - return "FixtureNotVerified"; - case HushChatCompatibilityFixtureImportError::SeedProjectionFailed: - return "SeedProjectionFailed"; - case HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed: - return "AuthFailureScaffoldFailed"; - case HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded: - return "ContactFixtureNotExcluded"; - } - return "Unknown"; -} - -const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error) -{ - switch (error) { - case HushChatCaptureManifestError::None: - return "None"; - case HushChatCaptureManifestError::FeatureDisabled: - return "FeatureDisabled"; - case HushChatCaptureManifestError::FileReadFailed: - return "FileReadFailed"; - case HushChatCaptureManifestError::InvalidJson: - return "InvalidJson"; - case HushChatCaptureManifestError::JsonNotObject: - return "JsonNotObject"; - case HushChatCaptureManifestError::InvalidSchema: - return "InvalidSchema"; - case HushChatCaptureManifestError::MissingManifestId: - return "MissingManifestId"; - case HushChatCaptureManifestError::MissingStatus: - return "MissingStatus"; - case HushChatCaptureManifestError::UnknownStatus: - return "UnknownStatus"; - case HushChatCaptureManifestError::MissingFixtureDirectory: - return "MissingFixtureDirectory"; - case HushChatCaptureManifestError::MissingDryRunCommand: - return "MissingDryRunCommand"; - case HushChatCaptureManifestError::InvalidDryRunCommand: - return "InvalidDryRunCommand"; - case HushChatCaptureManifestError::MissingProvenance: - return "MissingProvenance"; - case HushChatCaptureManifestError::MissingSourceClient: - return "MissingSourceClient"; - case HushChatCaptureManifestError::InvalidSourceClient: - return "InvalidSourceClient"; - case HushChatCaptureManifestError::MissingSourceClientVersion: - return "MissingSourceClientVersion"; - case HushChatCaptureManifestError::MissingCaptureDate: - return "MissingCaptureDate"; - case HushChatCaptureManifestError::MissingNetwork: - return "MissingNetwork"; - case HushChatCaptureManifestError::MissingCaptureMethod: - return "MissingCaptureMethod"; - case HushChatCaptureManifestError::MissingHandling: - return "MissingHandling"; - case HushChatCaptureManifestError::MissingHandlingFlag: - return "MissingHandlingFlag"; - case HushChatCaptureManifestError::HandlingFlagNotTrue: - return "HandlingFlagNotTrue"; - case HushChatCaptureManifestError::MissingCategories: - return "MissingCategories"; - case HushChatCaptureManifestError::InvalidCategoryEntry: - return "InvalidCategoryEntry"; - case HushChatCaptureManifestError::UnknownCategory: - return "UnknownCategory"; - case HushChatCaptureManifestError::DuplicateCategory: - return "DuplicateCategory"; - case HushChatCaptureManifestError::MissingRequiredCategory: - return "MissingRequiredCategory"; - case HushChatCaptureManifestError::ProhibitedFieldPresent: - return "ProhibitedFieldPresent"; - } - return "Unknown"; -} - -HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction) -{ - switch (direction) { - case HushChatDecryptDirection::Incoming: - return HushChatSessionKeySelection::ClientRx; - case HushChatDecryptDirection::Outgoing: - return HushChatSessionKeySelection::ServerTx; - } - return HushChatSessionKeySelection::ClientRx; -} - HushChatHeaderParseResult parseHushChatHeaderMemo(const std::string& memo) { if (memo.empty()) return fail("empty memo"); @@ -781,1075 +281,4 @@ HushChatTransactionExtractionResult extractHushChatTransactionMetadata( return result; } -HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight( - const HushChatDecryptPreflightInput& input, - bool featureEnabled) -{ - auto failPreflight = [&](HushChatDecryptPreflightError error) { - HushChatDecryptPreflightResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatDecryptPreflightErrorName(error); - return result; - }; - - if (!featureEnabled) return failPreflight(HushChatDecryptPreflightError::FeatureDisabled); - - const auto& header = input.header; - if (header.type != HushChatHeaderType::Message) { - return failPreflight(HushChatDecryptPreflightError::NonMessageHeader); - } - if (header.header_number < 1) return failPreflight(HushChatDecryptPreflightError::InvalidHeaderNumber); - if (header.version != kHushChatSupportedVersion) { - return failPreflight(HushChatDecryptPreflightError::UnsupportedVersion); - } - if (header.reply_zaddr.empty()) return failPreflight(HushChatDecryptPreflightError::MissingReplyAddress); - if (header.conversation_id.empty()) return failPreflight(HushChatDecryptPreflightError::MissingConversationId); - if (header.secretstream_header_hex.size() != kHushChatSecretstreamHeaderHexLength || - !isHexString(header.secretstream_header_hex)) { - return failPreflight(HushChatDecryptPreflightError::InvalidSecretstreamHeader); - } - if (header.public_key_hex.size() != kHushChatPublicKeyHexLength || !isHexString(header.public_key_hex)) { - return failPreflight(HushChatDecryptPreflightError::InvalidPublicKey); - } - - if (input.ciphertext_hex.empty()) return failPreflight(HushChatDecryptPreflightError::EmptyCiphertext); - if (input.ciphertext_hex.size() > kHushChatMemoByteLimit) { - return failPreflight(HushChatDecryptPreflightError::OversizedCiphertext); - } - if (input.ciphertext_hex.size() % 2 != 0) return failPreflight(HushChatDecryptPreflightError::OddLengthCiphertext); - if (!isHexString(input.ciphertext_hex)) return failPreflight(HushChatDecryptPreflightError::InvalidCiphertextHex); - - const std::size_t ciphertextSize = input.ciphertext_hex.size() / 2; - if (ciphertextSize <= kHushChatSecretstreamABytes) { - return failPreflight(HushChatDecryptPreflightError::TruncatedCiphertext); - } - - HushChatDecryptPreflightResult result; - result.ok = true; - result.feature_enabled = true; - result.error = HushChatDecryptPreflightError::None; - result.error_name = hushChatDecryptPreflightErrorName(result.error); - result.ciphertext_size = ciphertextSize; - return result; -} - -HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex, - std::size_t expectedByteLength) -{ - auto failDecode = [](HushChatHexDecodeError error) { - HushChatHexDecodeResult result; - result.error = error; - result.error_name = hushChatHexDecodeErrorName(error); - return result; - }; - - if (hex.empty()) { - if (expectedByteLength == 0) { - HushChatHexDecodeResult result; - result.ok = true; - return result; - } - return failDecode(HushChatHexDecodeError::Empty); - } - if (hex.size() % 2 != 0) return failDecode(HushChatHexDecodeError::OddLength); - if (!isHexString(hex)) return failDecode(HushChatHexDecodeError::InvalidHex); - if (hex.size() / 2 != expectedByteLength) { - return failDecode(HushChatHexDecodeError::UnexpectedByteLength); - } - - HushChatHexDecodeResult result; - result.ok = true; - result.bytes.reserve(expectedByteLength); - for (std::size_t index = 0; index < hex.size(); index += 2) { - auto high = hexNibble(static_cast(hex[index])); - auto low = hexNibble(static_cast(hex[index + 1])); - if (!high || !low) return failDecode(HushChatHexDecodeError::InvalidHex); - result.bytes.push_back(static_cast((*high << 4) | *low)); - } - return result; -} - -HushChatDecryptInputPreparationResult prepareHushChatDecryptInput( - const HushChatDecryptInputMaterial& material, - bool featureEnabled) -{ - auto failPreparation = [&](HushChatDecryptInputError error, - HushChatHexDecodeError hexError = HushChatHexDecodeError::None, - HushChatDecryptPreflightError preflightError = HushChatDecryptPreflightError::None) { - HushChatDecryptInputPreparationResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatDecryptInputErrorName(error); - result.hex_error = hexError; - result.preflight_error = preflightError; - return result; - }; - - if (!featureEnabled) return failPreparation(HushChatDecryptInputError::FeatureDisabled); - - auto storedChatKey = decodeHushChatHexBytes(material.stored_chat_key_hex, - kHushChatStoredChatKeyByteLength); - if (!storedChatKey.ok) { - return failPreparation(HushChatDecryptInputError::InvalidStoredChatKey, storedChatKey.error); - } - - auto preflight = validateHushChatMemoDecryptPreflight( - HushChatDecryptPreflightInput{material.header, material.ciphertext_hex}, - true); - if (!preflight.ok) { - return failPreparation(HushChatDecryptInputError::DecryptPreflightFailed, - HushChatHexDecodeError::None, - preflight.error); - } - - const std::string& peerPublicKeyHex = material.peer_public_key_hex.empty() - ? material.header.public_key_hex - : material.peer_public_key_hex; - auto peerPublicKey = decodeHushChatHexBytes(peerPublicKeyHex, - kHushChatPublicKeyByteLength); - if (!peerPublicKey.ok) { - return failPreparation(HushChatDecryptInputError::InvalidPeerPublicKey, peerPublicKey.error); - } - - auto streamHeader = decodeHushChatHexBytes(material.header.secretstream_header_hex, - kHushChatSecretstreamHeaderByteLength); - if (!streamHeader.ok) { - return failPreparation(HushChatDecryptInputError::InvalidStreamHeader, streamHeader.error); - } - - auto ciphertext = decodeHushChatHexBytes(material.ciphertext_hex, preflight.ciphertext_size); - if (!ciphertext.ok) { - return failPreparation(HushChatDecryptInputError::InvalidCiphertext, ciphertext.error); - } - - HushChatDecryptInputPreparationResult result; - result.ok = true; - result.feature_enabled = true; - result.error = HushChatDecryptInputError::None; - result.error_name = hushChatDecryptInputErrorName(result.error); - result.prepared.stored_chat_key_bytes = std::move(storedChatKey.bytes); - result.prepared.seed_bytes.reserve(kHushChatSeedByteLength); - for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) { - result.prepared.seed_bytes.push_back(static_cast(material.stored_chat_key_hex[index])); - } - result.prepared.peer_public_key_bytes = std::move(peerPublicKey.bytes); - result.prepared.stream_header_bytes = std::move(streamHeader.bytes); - result.prepared.ciphertext_bytes = std::move(ciphertext.bytes); - result.prepared.direction = material.direction; - result.prepared.session_key_selection = hushChatSessionKeySelectionForDirection(material.direction); - result.prepared.plaintext_capacity = result.prepared.ciphertext_bytes.size() - kHushChatSecretstreamABytes; - return result; -} - -HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness( - const HushChatPreparedDecryptInput& prepared) -{ - HushChatDecryptFixtureReadinessResult result; - result.stored_chat_key_size = prepared.stored_chat_key_bytes.size(); - result.seed_size = prepared.seed_bytes.size(); - result.peer_public_key_size = prepared.peer_public_key_bytes.size(); - result.stream_header_size = prepared.stream_header_bytes.size(); - result.ciphertext_size = prepared.ciphertext_bytes.size(); - result.plaintext_capacity = prepared.plaintext_capacity; - result.session_key_selection = prepared.session_key_selection; - result.ready = result.stored_chat_key_size == kHushChatStoredChatKeyByteLength && - result.seed_size == kHushChatSeedByteLength && - result.peer_public_key_size == kHushChatPublicKeyByteLength && - result.stream_header_size == kHushChatSecretstreamHeaderByteLength && - result.ciphertext_size > kHushChatSecretstreamABytes && - result.plaintext_capacity == result.ciphertext_size - kHushChatSecretstreamABytes; - return result; -} - -HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture( - const HushChatCompatibilityFixture& fixture, - bool featureEnabled) -{ - auto failFixture = [&](HushChatCompatibilityFixtureError error, - HushChatHexDecodeError hexError = HushChatHexDecodeError::None) { - HushChatCompatibilityFixtureVerificationResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatCompatibilityFixtureErrorName(error); - result.hex_error = hexError; - return result; - }; - - if (!featureEnabled) return failFixture(HushChatCompatibilityFixtureError::FeatureDisabled); - if (fixture.fixture_id.empty()) return failFixture(HushChatCompatibilityFixtureError::MissingFixtureId); - - auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex, - kHushChatPublicKeyByteLength); - if (!localPublicKey.ok) { - return failFixture(HushChatCompatibilityFixtureError::InvalidLocalPublicKey, localPublicKey.error); - } - - auto peerPublicKey = decodeHushChatHexBytes(fixture.peer_public_key_hex, - kHushChatPublicKeyByteLength); - if (!peerPublicKey.ok) { - return failFixture(HushChatCompatibilityFixtureError::InvalidPeerPublicKey, peerPublicKey.error); - } - - auto parsedHeader = parseHushChatHeaderMemo(fixture.header_memo); - if (!parsedHeader.ok) return failFixture(HushChatCompatibilityFixtureError::InvalidHeaderMemo); - - auto grouped = groupHushChatMemoOutputs({ - HushChatMemoOutput{0, fixture.header_memo}, - HushChatMemoOutput{1, fixture.ciphertext_memo} - }); - if (grouped.pairs.size() != 1 || !grouped.issues.empty()) { - return failFixture(HushChatCompatibilityFixtureError::InvalidMemoPair); - } - - const auto& header = grouped.pairs[0].header; - if (header.type != HushChatHeaderType::Message) { - return failFixture(HushChatCompatibilityFixtureError::NonMemoHeader); - } - - const std::string& expectedHeaderPublicKey = fixture.direction == HushChatDecryptDirection::Incoming - ? fixture.peer_public_key_hex - : fixture.local_public_key_hex; - if (header.public_key_hex != expectedHeaderPublicKey) { - return failFixture(HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch); - } - - auto preparation = prepareHushChatDecryptInput(HushChatDecryptInputMaterial{ - fixture.stored_chat_key_hex, - header, - fixture.ciphertext_memo, - fixture.direction, - fixture.peer_public_key_hex - }, true); - - if (!preparation.ok) { - HushChatCompatibilityFixtureVerificationResult result = failFixture( - HushChatCompatibilityFixtureError::DecryptInputFailed, - preparation.hex_error); - result.decrypt_input_error = preparation.error; - result.preflight_error = preparation.preflight_error; - result.preparation = std::move(preparation); - return result; - } - - auto readiness = inspectHushChatDecryptFixtureReadiness(preparation.prepared); - if (!readiness.ready) { - HushChatCompatibilityFixtureVerificationResult result = failFixture( - HushChatCompatibilityFixtureError::NotFixtureReady); - result.preparation = std::move(preparation); - result.readiness = readiness; - return result; - } - - auto failExpectation = [&](HushChatCompatibilityFixtureError error, - HushChatDecryptInputPreparationResult&& prepared, - const HushChatDecryptFixtureReadinessResult& fixtureReadiness) { - HushChatCompatibilityFixtureVerificationResult result = failFixture(error); - result.header = header; - result.preparation = std::move(prepared); - result.readiness = fixtureReadiness; - result.local_public_key_size = localPublicKey.bytes.size(); - result.peer_public_key_size = peerPublicKey.bytes.size(); - return result; - }; - - if (readiness.stored_chat_key_size != fixture.expected_stored_chat_key_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.seed_size != fixture.expected_seed_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch, - std::move(preparation), - readiness); - } - if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.peer_public_key_size != fixture.expected_peer_public_key_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.stream_header_size != fixture.expected_stream_header_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.ciphertext_size != fixture.expected_ciphertext_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.plaintext_capacity != fixture.expected_plaintext_size) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch, - std::move(preparation), - readiness); - } - if (readiness.session_key_selection != fixture.expected_session_key_selection) { - return failExpectation(HushChatCompatibilityFixtureError::ExpectedRoleMismatch, - std::move(preparation), - readiness); - } - - std::size_t plaintextHashSize = 0; - if (!fixture.expected_plaintext_hash_hex.empty()) { - if (fixture.expected_plaintext_hash_hex.size() % 2 != 0 || - !isHexString(fixture.expected_plaintext_hash_hex)) { - return failExpectation(HushChatCompatibilityFixtureError::InvalidPlaintextHash, - std::move(preparation), - readiness); - } - plaintextHashSize = fixture.expected_plaintext_hash_hex.size() / 2; - } - - HushChatCompatibilityFixtureVerificationResult result; - result.ok = true; - result.feature_enabled = true; - result.error = HushChatCompatibilityFixtureError::None; - result.error_name = hushChatCompatibilityFixtureErrorName(result.error); - result.header = header; - result.preparation = std::move(preparation); - result.readiness = readiness; - result.local_public_key_size = localPublicKey.bytes.size(); - result.peer_public_key_size = peerPublicKey.bytes.size(); - result.plaintext_hash_size = plaintextHashSize; - return result; -} - -HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile( - const std::string& jsonText, - bool featureEnabled) -{ - auto failFile = [&](HushChatCompatibilityFixtureFileError error) { - HushChatCompatibilityFixtureFileParseResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatCompatibilityFixtureFileErrorName(error); - return result; - }; - - if (!featureEnabled) return failFile(HushChatCompatibilityFixtureFileError::FeatureDisabled); - - nlohmann::json object; - try { - object = nlohmann::json::parse(jsonText); - } catch (const nlohmann::json::parse_error&) { - return failFile(HushChatCompatibilityFixtureFileError::InvalidJson); - } - if (!object.is_object()) return failFile(HushChatCompatibilityFixtureFileError::JsonNotObject); - - auto schema = readJsonString(object, "schema"); - if (!schema || *schema != kHushChatCompatibilityFixtureSchema) { - return failFile(HushChatCompatibilityFixtureFileError::InvalidSchema); - } - - auto kindText = readJsonString(object, "kind"); - if (!kindText) return failFile(HushChatCompatibilityFixtureFileError::MissingKind); - auto kind = parseFixtureKind(*kindText); - if (!kind) return failFile(HushChatCompatibilityFixtureFileError::UnknownKind); - - auto statusText = readJsonString(object, "status"); - if (!statusText) return failFile(HushChatCompatibilityFixtureFileError::MissingStatus); - auto status = parseFixtureFileStatus(*statusText); - if (!status) return failFile(HushChatCompatibilityFixtureFileError::UnknownStatus); - - HushChatCompatibilityFixtureFile parsed; - parsed.schema = *schema; - parsed.kind = *kind; - parsed.status = *status; - if (auto fixtureId = readJsonString(object, "id")) parsed.fixture_id = *fixtureId; - - if (parsed.status == HushChatCompatibilityFixtureFileStatus::Pending) { - if (parsed.fixture_id.empty()) return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureId); - - auto pendingReason = readJsonString(object, "pending_reason"); - if (!pendingReason || pendingReason->empty()) { - return failFile(HushChatCompatibilityFixtureFileError::MissingPendingReason); - } - parsed.pending_reason = *pendingReason; - - HushChatCompatibilityFixtureFileParseResult result; - result.ok = true; - result.feature_enabled = true; - result.pending = true; - result.error = HushChatCompatibilityFixtureFileError::None; - result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); - result.file = std::move(parsed); - return result; - } - - if (!object.contains("fixture") || !object["fixture"].is_object()) { - return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureObject); - } - - const auto& fixtureObject = object["fixture"]; - const nlohmann::json expected = fixtureObject.contains("expected") ? fixtureObject["expected"] : nlohmann::json(); - if (!expected.is_object()) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); - - auto readRequiredFixtureString = [&](const nlohmann::json& source, const char* key) -> std::optional { - auto value = readJsonString(source, key); - if (!value || value->empty()) return std::nullopt; - return value; - }; - - HushChatCompatibilityFixture fixture; - auto id = readRequiredFixtureString(fixtureObject, "id"); - auto storedChatKey = readRequiredFixtureString(fixtureObject, "stored_chat_key_hex"); - auto localPublicKey = readRequiredFixtureString(fixtureObject, "local_public_key_hex"); - auto peerPublicKey = readRequiredFixtureString(fixtureObject, "peer_public_key_hex"); - auto headerMemo = readRequiredFixtureString(fixtureObject, "header_memo"); - auto ciphertextMemo = readRequiredFixtureString(fixtureObject, "ciphertext_memo"); - auto directionText = readRequiredFixtureString(fixtureObject, "direction"); - auto roleText = readRequiredFixtureString(expected, "session_key_selection"); - auto storedKeySize = readJsonSize(expected, "stored_chat_key_bytes"); - auto seedSize = readJsonSize(expected, "seed_bytes"); - auto localKeySize = readJsonSize(expected, "local_public_key_bytes"); - auto peerKeySize = readJsonSize(expected, "peer_public_key_bytes"); - auto streamHeaderSize = readJsonSize(expected, "stream_header_bytes"); - auto ciphertextSize = readJsonSize(expected, "ciphertext_bytes"); - auto plaintextSize = readJsonSize(expected, "plaintext_bytes"); - if (!id || !storedChatKey || !localPublicKey || !peerPublicKey || !headerMemo || !ciphertextMemo || - !directionText || !roleText || !storedKeySize || !seedSize || !localKeySize || !peerKeySize || - !streamHeaderSize || !ciphertextSize || !plaintextSize) { - return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); - } - - auto direction = parseFixtureDirection(*directionText); - auto role = parseFixtureSessionKeySelection(*roleText); - if (!direction || !role) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField); - - fixture.fixture_id = *id; - fixture.stored_chat_key_hex = *storedChatKey; - fixture.local_public_key_hex = *localPublicKey; - fixture.peer_public_key_hex = *peerPublicKey; - fixture.header_memo = *headerMemo; - fixture.ciphertext_memo = *ciphertextMemo; - fixture.direction = *direction; - fixture.expected_session_key_selection = *role; - fixture.expected_stored_chat_key_size = *storedKeySize; - fixture.expected_seed_size = *seedSize; - fixture.expected_local_public_key_size = *localKeySize; - fixture.expected_peer_public_key_size = *peerKeySize; - fixture.expected_stream_header_size = *streamHeaderSize; - fixture.expected_ciphertext_size = *ciphertextSize; - fixture.expected_plaintext_size = *plaintextSize; - if (auto plaintextHash = readJsonString(expected, "plaintext_hash_hex")) { - fixture.expected_plaintext_hash_hex = *plaintextHash; - } - - parsed.fixture_id = fixture.fixture_id; - parsed.fixture = fixture; - - auto verification = verifyHushChatCompatibilityFixture(fixture, true); - if (parsed.kind == HushChatCompatibilityFixtureKind::ContactExclusion) { - if (verification.error != HushChatCompatibilityFixtureError::NonMemoHeader) { - HushChatCompatibilityFixtureFileParseResult result = failFile( - HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded); - result.file = std::move(parsed); - result.verification = std::move(verification); - return result; - } - - HushChatCompatibilityFixtureFileParseResult result; - result.ok = true; - result.feature_enabled = true; - result.excluded_from_decrypt = true; - result.error = HushChatCompatibilityFixtureFileError::None; - result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); - result.file = std::move(parsed); - result.verification = std::move(verification); - return result; - } - - if (!verification.ok) { - HushChatCompatibilityFixtureFileParseResult result = failFile( - HushChatCompatibilityFixtureFileError::FixtureVerificationFailed); - result.file = std::move(parsed); - result.verification = std::move(verification); - return result; - } - - HushChatCompatibilityFixtureFileParseResult result; - result.ok = true; - result.feature_enabled = true; - result.verified = true; - result.error = HushChatCompatibilityFixtureFileError::None; - result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); - result.file = std::move(parsed); - result.verification = std::move(verification); - return result; -} - -HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile( - const std::string& path, - bool featureEnabled) -{ - if (!featureEnabled) { - HushChatCompatibilityFixtureFileParseResult result; - result.error = HushChatCompatibilityFixtureFileError::FeatureDisabled; - result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); - return result; - } - - std::ifstream input(path); - if (!input.good()) { - HushChatCompatibilityFixtureFileParseResult result; - result.feature_enabled = true; - result.error = HushChatCompatibilityFixtureFileError::FileReadFailed; - result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error); - return result; - } - - std::ostringstream buffer; - buffer << input.rdbuf(); - return parseHushChatCompatibilityFixtureFile(buffer.str(), true); -} - -HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection( - const HushChatCompatibilityFixture& fixture, - bool featureEnabled) -{ - auto failProjection = [&](HushChatSeedPublicKeyProjectionError error, - HushChatHexDecodeError hexError = HushChatHexDecodeError::None) { - HushChatSeedPublicKeyProjectionResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatSeedPublicKeyProjectionErrorName(error); - result.hex_error = hexError; - return result; - }; - - if (!featureEnabled) return failProjection(HushChatSeedPublicKeyProjectionError::FeatureDisabled); - if (fixture.fixture_id.empty()) return failProjection(HushChatSeedPublicKeyProjectionError::MissingFixtureId); - - auto storedChatKey = decodeHushChatHexBytes(fixture.stored_chat_key_hex, - kHushChatStoredChatKeyByteLength); - if (!storedChatKey.ok) { - return failProjection(HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey, storedChatKey.error); - } - - auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex, - kHushChatPublicKeyByteLength); - if (!localPublicKey.ok) { - return failProjection(HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey, localPublicKey.error); - } - - const std::size_t seedSize = std::min(fixture.stored_chat_key_hex.size(), kHushChatSeedByteLength); - if (storedChatKey.bytes.size() != fixture.expected_stored_chat_key_size) { - auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch); - result.stored_chat_key_size = storedChatKey.bytes.size(); - result.seed_size = seedSize; - result.local_public_key_size = localPublicKey.bytes.size(); - return result; - } - if (seedSize != fixture.expected_seed_size) { - auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch); - result.stored_chat_key_size = storedChatKey.bytes.size(); - result.seed_size = seedSize; - result.local_public_key_size = localPublicKey.bytes.size(); - return result; - } - if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) { - auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch); - result.stored_chat_key_size = storedChatKey.bytes.size(); - result.seed_size = seedSize; - result.local_public_key_size = localPublicKey.bytes.size(); - return result; - } - - if (sodium_init() < 0) { - return failProjection(HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed); - } - - unsigned char seed[kHushChatSeedByteLength]; - for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) { - seed[index] = static_cast(fixture.stored_chat_key_hex[index]); - } - - unsigned char projectedPublicKey[crypto_kx_PUBLICKEYBYTES]; - unsigned char projectedSecretKey[crypto_kx_SECRETKEYBYTES]; - const int projectionStatus = crypto_kx_seed_keypair(projectedPublicKey, projectedSecretKey, seed); - sodium_memzero(projectedSecretKey, sizeof(projectedSecretKey)); - sodium_memzero(seed, sizeof(seed)); - if (projectionStatus != 0) { - return failProjection(HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed); - } - - HushChatSeedPublicKeyProjectionResult result; - result.feature_enabled = true; - result.stored_chat_key_size = storedChatKey.bytes.size(); - result.seed_size = seedSize; - result.local_public_key_size = localPublicKey.bytes.size(); - result.projected_public_key_size = sizeof(projectedPublicKey); - - if (result.projected_public_key_size != localPublicKey.bytes.size() || - std::memcmp(projectedPublicKey, localPublicKey.bytes.data(), localPublicKey.bytes.size()) != 0) { - sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey)); - result.error = HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch; - result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error); - return result; - } - - sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey)); - result.ok = true; - result.error = HushChatSeedPublicKeyProjectionError::None; - result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error); - return result; -} - -HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness( - const HushChatCompatibilityFixtureFileParseResult& parsed, - const HushChatSeedPublicKeyProjectionResult& seedProjection, - bool featureEnabled) -{ - auto failReadiness = [&](HushChatCorruptedAuthFailureReadinessError error) { - HushChatCorruptedAuthFailureReadinessResult result; - result.feature_enabled = featureEnabled; - result.error = error; - result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(error); - return result; - }; - - if (!featureEnabled) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FeatureDisabled); - if (parsed.pending) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixturePending); - if (parsed.file.kind != HushChatCompatibilityFixtureKind::CorruptedAuthFailure) { - return failReadiness(HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind); - } - if (!parsed.verified || !parsed.verification.ok) { - return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified); - } - if (!seedProjection.ok) { - return failReadiness(HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified); - } - - HushChatCorruptedAuthFailureReadinessResult result; - result.ok = true; - result.feature_enabled = true; - result.structurally_ready_for_future_auth_check = true; - result.requires_future_secretstream_auth_failure = true; - result.decrypted = false; - result.authenticated = false; - result.error = HushChatCorruptedAuthFailureReadinessError::None; - result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(result.error); - return result; -} - -std::vector hushChatRequiredCompatibilityFixtureKinds() -{ - return { - HushChatCompatibilityFixtureKind::IncomingMemo, - HushChatCompatibilityFixtureKind::OutgoingMemo, - HushChatCompatibilityFixtureKind::SeedPublicKeyProjection, - HushChatCompatibilityFixtureKind::CorruptedAuthFailure, - HushChatCompatibilityFixtureKind::ContactExclusion - }; -} - -HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist( - const std::vector& candidates, - bool featureEnabled) -{ - HushChatCompatibilityFixtureImportChecklistResult result; - result.feature_enabled = featureEnabled; - - auto setFirstError = [&](HushChatCompatibilityFixtureImportError error) { - if (result.error == HushChatCompatibilityFixtureImportError::None) { - result.error = error; - result.error_name = hushChatCompatibilityFixtureImportErrorName(error); - } - }; - - if (!featureEnabled) { - setFirstError(HushChatCompatibilityFixtureImportError::FeatureDisabled); - return result; - } - - const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds(); - result.required_count = requiredKinds.size(); - result.items.reserve(requiredKinds.size()); - - for (const auto expectedKind : requiredKinds) { - HushChatCompatibilityFixtureImportItem item; - item.expected_kind = expectedKind; - - const HushChatCompatibilityFixtureImportCandidate* selected = nullptr; - std::size_t matchCount = 0; - for (const auto& candidate : candidates) { - if (candidate.expected_kind != expectedKind) continue; - ++matchCount; - if (!selected) selected = &candidate; - } - - if (matchCount == 0) { - item.error = HushChatCompatibilityFixtureImportError::MissingRequiredKind; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.missing_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.supplied = true; - ++result.supplied_count; - - if (matchCount > 1 || !selected) { - item.error = HushChatCompatibilityFixtureImportError::DuplicateKind; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.path = selected->path; - item.parsed = loadHushChatCompatibilityFixtureFile(selected->path, true); - if (!item.parsed.ok) { - if (item.parsed.error == HushChatCompatibilityFixtureFileError::FileReadFailed) { - item.error = HushChatCompatibilityFixtureImportError::FixtureLoadFailed; - } else if (item.parsed.error == HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded) { - item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded; - } else { - item.error = HushChatCompatibilityFixtureImportError::FixtureInvalid; - } - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.loaded_kind = item.parsed.file.kind; - if (item.loaded_kind != expectedKind) { - item.error = HushChatCompatibilityFixtureImportError::FixtureKindMismatch; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - if (item.parsed.pending) { - item.pending = true; - item.error = HushChatCompatibilityFixtureImportError::FixturePending; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.pending_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - if (expectedKind == HushChatCompatibilityFixtureKind::ContactExclusion) { - if (!item.parsed.excluded_from_decrypt) { - item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.replacement_eligible = true; - item.error = HushChatCompatibilityFixtureImportError::None; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.excluded_count; - result.items.push_back(std::move(item)); - continue; - } - - if (!item.parsed.verified) { - item.error = HushChatCompatibilityFixtureImportError::FixtureNotVerified; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.seed_projection = verifyHushChatSeedPublicKeyProjection(item.parsed.file.fixture, true); - if (!item.seed_projection.ok) { - item.error = HushChatCompatibilityFixtureImportError::SeedProjectionFailed; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - if (expectedKind == HushChatCompatibilityFixtureKind::CorruptedAuthFailure) { - item.auth_failure_readiness = inspectHushChatCorruptedAuthFailureReadiness( - item.parsed, - item.seed_projection, - true); - if (!item.auth_failure_readiness.ok) { - item.error = HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.rejected_count; - setFirstError(item.error); - result.items.push_back(std::move(item)); - continue; - } - - item.future_auth_failure_required = item.auth_failure_readiness.requires_future_secretstream_auth_failure; - item.structurally_ready_for_future_auth_check = - item.auth_failure_readiness.structurally_ready_for_future_auth_check; - if (item.future_auth_failure_required) ++result.future_auth_failure_required_count; - if (item.structurally_ready_for_future_auth_check) ++result.auth_failure_structural_ready_count; - } - - item.replacement_eligible = true; - item.seed_projection_verified = true; - item.error = HushChatCompatibilityFixtureImportError::None; - item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error); - ++result.verified_count; - ++result.seed_projection_verified_count; - result.items.push_back(std::move(item)); - } - - result.replacement_ready = result.supplied_count == result.required_count && - result.missing_count == 0 && - result.pending_count == 0 && - result.rejected_count == 0 && - result.verified_count == result.required_count - 1 && - result.seed_projection_verified_count == result.required_count - 1 && - result.future_auth_failure_required_count == 1 && - result.auth_failure_structural_ready_count == 1 && - result.excluded_count == 1; - result.ok = result.replacement_ready; - if (result.ok) { - result.error = HushChatCompatibilityFixtureImportError::None; - result.error_name = hushChatCompatibilityFixtureImportErrorName(result.error); - } - return result; -} - -HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun( - const std::vector& candidates, - bool featureEnabled) -{ - const auto checklist = inspectHushChatCompatibilityFixtureImportChecklist(candidates, featureEnabled); - - HushChatCompatibilityFixtureReplacementDryRunResult result; - result.ok = checklist.replacement_ready; - result.feature_enabled = checklist.feature_enabled; - result.would_replace = checklist.replacement_ready; - result.replacement_refused = !checklist.replacement_ready; - result.error = checklist.error; - result.error_name = checklist.error_name; - result.required_count = checklist.required_count; - result.supplied_count = checklist.supplied_count; - result.missing_count = checklist.missing_count; - result.pending_count = checklist.pending_count; - result.verified_count = checklist.verified_count; - result.seed_projection_verified_count = checklist.seed_projection_verified_count; - result.future_auth_failure_required_count = checklist.future_auth_failure_required_count; - result.auth_failure_structural_ready_count = checklist.auth_failure_structural_ready_count; - result.excluded_count = checklist.excluded_count; - result.rejected_count = checklist.rejected_count; - result.report_items.reserve(checklist.items.size()); - - for (const auto& item : checklist.items) { - HushChatCompatibilityFixtureReplacementReportItem reportItem; - reportItem.expected_kind = item.expected_kind; - reportItem.loaded_kind = item.loaded_kind; - reportItem.path = item.path; - reportItem.supplied = item.supplied; - reportItem.pending = item.pending; - reportItem.replacement_eligible = item.replacement_eligible; - reportItem.refused = !item.replacement_eligible; - reportItem.seed_projection_verified = item.seed_projection_verified; - reportItem.future_auth_failure_required = item.future_auth_failure_required; - reportItem.structurally_ready_for_future_auth_check = item.structurally_ready_for_future_auth_check; - reportItem.cont_excluded = item.parsed.excluded_from_decrypt; - reportItem.decrypted = item.auth_failure_readiness.decrypted; - reportItem.authenticated = item.auth_failure_readiness.authenticated; - reportItem.error = item.error; - reportItem.error_name = item.error_name; - result.report_items.push_back(std::move(reportItem)); - } - - return result; -} - -HushChatCaptureManifestValidationResult validateHushChatCaptureManifest( - const std::string& jsonText, - bool featureEnabled) -{ - HushChatCaptureManifestValidationResult result; - result.feature_enabled = featureEnabled; - const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds(); - result.required_count = requiredKinds.size(); - result.categories.reserve(requiredKinds.size()); - for (const auto kind : requiredKinds) { - HushChatCaptureManifestCategoryReport report; - report.kind = kind; - result.categories.push_back(std::move(report)); - } - - auto failManifest = [&](HushChatCaptureManifestError error) { - result.error = error; - result.error_name = hushChatCaptureManifestErrorName(error); - return result; - }; - - if (!featureEnabled) return failManifest(HushChatCaptureManifestError::FeatureDisabled); - - nlohmann::json object; - try { - object = nlohmann::json::parse(jsonText); - } catch (const nlohmann::json::parse_error&) { - return failManifest(HushChatCaptureManifestError::InvalidJson); - } - if (!object.is_object()) return failManifest(HushChatCaptureManifestError::JsonNotObject); - - result.prohibited_field_count = countProhibitedCaptureManifestFields(object); - if (result.prohibited_field_count > 0) { - return failManifest(HushChatCaptureManifestError::ProhibitedFieldPresent); - } - - auto schema = readJsonString(object, "schema"); - if (!schema || *schema != kHushChatCaptureManifestSchema) { - return failManifest(HushChatCaptureManifestError::InvalidSchema); - } - - auto id = readJsonString(object, "id"); - if (!id || id->empty()) return failManifest(HushChatCaptureManifestError::MissingManifestId); - - auto status = readJsonString(object, "status"); - if (!status) return failManifest(HushChatCaptureManifestError::MissingStatus); - if (*status != "staged") return failManifest(HushChatCaptureManifestError::UnknownStatus); - - auto fixtureDirectory = readJsonString(object, "fixture_directory"); - if (!fixtureDirectory || fixtureDirectory->empty()) { - return failManifest(HushChatCaptureManifestError::MissingFixtureDirectory); - } - result.fixture_directory = *fixtureDirectory; - - auto dryRunCommand = readJsonString(object, "dry_run_command"); - if (!dryRunCommand || dryRunCommand->empty()) { - return failManifest(HushChatCaptureManifestError::MissingDryRunCommand); - } - result.has_dry_run_command = true; - if (dryRunCommand->find("HushChatFixtureCheck") == std::string::npos || - dryRunCommand->find("--replacement-dry-run") == std::string::npos) { - return failManifest(HushChatCaptureManifestError::InvalidDryRunCommand); - } - - auto provenance = object.find("provenance"); - if (provenance == object.end() || !provenance->is_object()) { - return failManifest(HushChatCaptureManifestError::MissingProvenance); - } - auto sourceClient = readJsonString(*provenance, "source_client"); - if (!sourceClient || sourceClient->empty()) { - return failManifest(HushChatCaptureManifestError::MissingSourceClient); - } - if (*sourceClient != "SilentDragonXLite") { - return failManifest(HushChatCaptureManifestError::InvalidSourceClient); - } - auto sourceClientVersion = readJsonString(*provenance, "source_client_version"); - if (!sourceClientVersion || sourceClientVersion->empty()) { - return failManifest(HushChatCaptureManifestError::MissingSourceClientVersion); - } - auto captureDate = readJsonString(*provenance, "capture_date"); - if (!captureDate || captureDate->empty()) return failManifest(HushChatCaptureManifestError::MissingCaptureDate); - auto network = readJsonString(*provenance, "network"); - if (!network || network->empty()) return failManifest(HushChatCaptureManifestError::MissingNetwork); - auto captureMethod = readJsonString(*provenance, "capture_method"); - if (!captureMethod || captureMethod->empty()) { - return failManifest(HushChatCaptureManifestError::MissingCaptureMethod); - } - - auto handling = object.find("handling"); - if (handling == object.end() || !handling->is_object()) { - return failManifest(HushChatCaptureManifestError::MissingHandling); - } - for (const char* flag : requiredCaptureHandlingFlags()) { - auto flagValue = handling->find(flag); - if (flagValue == handling->end()) return failManifest(HushChatCaptureManifestError::MissingHandlingFlag); - if (!flagValue->is_boolean() || !flagValue->get()) { - return failManifest(HushChatCaptureManifestError::HandlingFlagNotTrue); - } - ++result.handling_flag_count; - } - result.no_sensitive_material_declared = true; - - auto categories = object.find("categories"); - if (categories == object.end() || !categories->is_array()) { - return failManifest(HushChatCaptureManifestError::MissingCategories); - } - - auto findCategoryReport = [&](HushChatCompatibilityFixtureKind kind) -> HushChatCaptureManifestCategoryReport* { - for (auto& report : result.categories) { - if (report.kind == kind) return &report; - } - return nullptr; - }; - - for (const auto& category : *categories) { - if (!category.is_object()) return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry); - - auto kindText = readJsonString(category, "kind"); - auto stagedFilename = readJsonString(category, "staged_filename"); - auto categoryStatus = readJsonString(category, "status"); - if (!kindText || kindText->empty() || !stagedFilename || stagedFilename->empty() || - !categoryStatus || *categoryStatus != "ready") { - return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry); - } - - auto kind = parseFixtureKind(*kindText); - if (!kind) return failManifest(HushChatCaptureManifestError::UnknownCategory); - - auto* report = findCategoryReport(*kind); - if (!report) return failManifest(HushChatCaptureManifestError::UnknownCategory); - if (report->declared) { - ++result.duplicate_count; - return failManifest(HushChatCaptureManifestError::DuplicateCategory); - } - - report->declared = true; - report->staged_filename = *stagedFilename; - ++result.declared_count; - } - - for (const auto& report : result.categories) { - if (!report.declared) ++result.missing_count; - } - if (result.missing_count > 0) return failManifest(HushChatCaptureManifestError::MissingRequiredCategory); - - result.ok = true; - result.error = HushChatCaptureManifestError::None; - result.error_name = hushChatCaptureManifestErrorName(result.error); - return result; -} - -HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile( - const std::string& path, - bool featureEnabled) -{ - if (!featureEnabled) { - HushChatCaptureManifestValidationResult result; - result.error = HushChatCaptureManifestError::FeatureDisabled; - result.error_name = hushChatCaptureManifestErrorName(result.error); - return result; - } - - std::ifstream input(path); - if (!input.good()) { - HushChatCaptureManifestValidationResult result; - result.feature_enabled = true; - result.manifest_path = path; - result.error = HushChatCaptureManifestError::FileReadFailed; - result.error_name = hushChatCaptureManifestErrorName(result.error); - return result; - } - - std::ostringstream buffer; - buffer << input.rdbuf(); - auto result = validateHushChatCaptureManifest(buffer.str(), true); - result.manifest_path = path; - return result; -} - -} // namespace dragonx::chat \ No newline at end of file +} // namespace dragonx::chat diff --git a/src/chat/chat_protocol.h b/src/chat/chat_protocol.h index e15dd93..908241e 100644 --- a/src/chat/chat_protocol.h +++ b/src/chat/chat_protocol.h @@ -84,438 +84,10 @@ struct HushChatTransactionExtractionResult { std::size_t ignored_memo_count = 0; }; -enum class HushChatDecryptPreflightError { - None, - FeatureDisabled, - NonMessageHeader, - InvalidHeaderNumber, - UnsupportedVersion, - MissingReplyAddress, - MissingConversationId, - InvalidSecretstreamHeader, - InvalidPublicKey, - EmptyCiphertext, - OversizedCiphertext, - OddLengthCiphertext, - InvalidCiphertextHex, - TruncatedCiphertext -}; - -struct HushChatDecryptPreflightInput { - HushChatHeader header; - std::string ciphertext_hex; -}; - -struct HushChatDecryptPreflightResult { - bool ok = false; - bool feature_enabled = false; - HushChatDecryptPreflightError error = HushChatDecryptPreflightError::None; - const char* error_name = "None"; - std::size_t ciphertext_size = 0; -}; - -enum class HushChatHexDecodeError { - None, - Empty, - OddLength, - InvalidHex, - UnexpectedByteLength -}; - -struct HushChatHexDecodeResult { - bool ok = false; - HushChatHexDecodeError error = HushChatHexDecodeError::None; - const char* error_name = "None"; - std::vector bytes; -}; - -enum class HushChatDecryptDirection { - Incoming, - Outgoing -}; - -enum class HushChatSessionKeySelection { - ClientRx, - ServerTx -}; - -enum class HushChatDecryptInputError { - None, - FeatureDisabled, - InvalidStoredChatKey, - DecryptPreflightFailed, - InvalidPeerPublicKey, - InvalidStreamHeader, - InvalidCiphertext -}; - -struct HushChatDecryptInputMaterial { - std::string stored_chat_key_hex; - HushChatHeader header; - std::string ciphertext_hex; - HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; - std::string peer_public_key_hex; -}; - -struct HushChatPreparedDecryptInput { - std::vector stored_chat_key_bytes; - std::vector seed_bytes; - std::vector peer_public_key_bytes; - std::vector stream_header_bytes; - std::vector ciphertext_bytes; - HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; - HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx; - std::size_t plaintext_capacity = 0; -}; - -struct HushChatDecryptInputPreparationResult { - bool ok = false; - bool feature_enabled = false; - HushChatDecryptInputError error = HushChatDecryptInputError::None; - const char* error_name = "None"; - HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; - HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None; - HushChatPreparedDecryptInput prepared; -}; - -struct HushChatDecryptFixtureReadinessResult { - bool ready = false; - std::size_t stored_chat_key_size = 0; - std::size_t seed_size = 0; - std::size_t peer_public_key_size = 0; - std::size_t stream_header_size = 0; - std::size_t ciphertext_size = 0; - std::size_t plaintext_capacity = 0; - HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx; -}; - -enum class HushChatCompatibilityFixtureError { - None, - FeatureDisabled, - MissingFixtureId, - InvalidLocalPublicKey, - InvalidPeerPublicKey, - InvalidHeaderMemo, - InvalidMemoPair, - NonMemoHeader, - HeaderPublicKeyMismatch, - DecryptInputFailed, - NotFixtureReady, - ExpectedStoredChatKeyLengthMismatch, - ExpectedSeedLengthMismatch, - ExpectedLocalPublicKeyLengthMismatch, - ExpectedPeerPublicKeyLengthMismatch, - ExpectedStreamHeaderLengthMismatch, - ExpectedCiphertextLengthMismatch, - ExpectedPlaintextLengthMismatch, - ExpectedRoleMismatch, - InvalidPlaintextHash -}; - -struct HushChatCompatibilityFixture { - std::string fixture_id; - std::string stored_chat_key_hex; - std::string local_public_key_hex; - std::string peer_public_key_hex; - std::string header_memo; - std::string ciphertext_memo; - HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming; - HushChatSessionKeySelection expected_session_key_selection = HushChatSessionKeySelection::ClientRx; - std::size_t expected_stored_chat_key_size = 32; - std::size_t expected_seed_size = 32; - std::size_t expected_local_public_key_size = 32; - std::size_t expected_peer_public_key_size = 32; - std::size_t expected_stream_header_size = 24; - std::size_t expected_ciphertext_size = 0; - std::size_t expected_plaintext_size = 0; - std::string expected_plaintext_hash_hex; -}; - -struct HushChatCompatibilityFixtureVerificationResult { - bool ok = false; - bool feature_enabled = false; - HushChatCompatibilityFixtureError error = HushChatCompatibilityFixtureError::None; - const char* error_name = "None"; - HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; - HushChatDecryptInputError decrypt_input_error = HushChatDecryptInputError::None; - HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None; - HushChatHeader header; - HushChatDecryptInputPreparationResult preparation; - HushChatDecryptFixtureReadinessResult readiness; - std::size_t local_public_key_size = 0; - std::size_t peer_public_key_size = 0; - std::size_t plaintext_hash_size = 0; -}; - -enum class HushChatCompatibilityFixtureKind { - IncomingMemo, - OutgoingMemo, - SeedPublicKeyProjection, - CorruptedAuthFailure, - ContactExclusion -}; - -enum class HushChatCompatibilityFixtureFileStatus { - Pending, - Ready -}; - -enum class HushChatCompatibilityFixtureFileError { - None, - FeatureDisabled, - InvalidJson, - JsonNotObject, - InvalidSchema, - MissingKind, - UnknownKind, - MissingStatus, - UnknownStatus, - MissingFixtureId, - MissingPendingReason, - MissingFixtureObject, - InvalidFixtureField, - FixtureVerificationFailed, - ContactFixtureNotExcluded, - FileReadFailed -}; - -struct HushChatCompatibilityFixtureFile { - std::string schema; - HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo; - HushChatCompatibilityFixtureFileStatus status = HushChatCompatibilityFixtureFileStatus::Pending; - std::string fixture_id; - std::string pending_reason; - HushChatCompatibilityFixture fixture; -}; - -struct HushChatCompatibilityFixtureFileParseResult { - bool ok = false; - bool feature_enabled = false; - bool pending = false; - bool verified = false; - bool excluded_from_decrypt = false; - HushChatCompatibilityFixtureFileError error = HushChatCompatibilityFixtureFileError::None; - const char* error_name = "None"; - HushChatCompatibilityFixtureFile file; - HushChatCompatibilityFixtureVerificationResult verification; -}; - -enum class HushChatSeedPublicKeyProjectionError { - None, - FeatureDisabled, - MissingFixtureId, - InvalidStoredChatKey, - InvalidLocalPublicKey, - ExpectedStoredChatKeyLengthMismatch, - ExpectedSeedLengthMismatch, - ExpectedLocalPublicKeyLengthMismatch, - SodiumInitializationFailed, - KeypairProjectionFailed, - ProjectedPublicKeyMismatch -}; - -struct HushChatSeedPublicKeyProjectionResult { - bool ok = false; - bool feature_enabled = false; - HushChatSeedPublicKeyProjectionError error = HushChatSeedPublicKeyProjectionError::None; - const char* error_name = "None"; - HushChatHexDecodeError hex_error = HushChatHexDecodeError::None; - std::size_t stored_chat_key_size = 0; - std::size_t seed_size = 0; - std::size_t local_public_key_size = 0; - std::size_t projected_public_key_size = 0; -}; - -enum class HushChatCorruptedAuthFailureReadinessError { - None, - FeatureDisabled, - FixturePending, - WrongFixtureKind, - FixtureNotVerified, - SeedProjectionNotVerified -}; - -struct HushChatCorruptedAuthFailureReadinessResult { - bool ok = false; - bool feature_enabled = false; - bool structurally_ready_for_future_auth_check = false; - bool requires_future_secretstream_auth_failure = false; - bool decrypted = false; - bool authenticated = false; - HushChatCorruptedAuthFailureReadinessError error = HushChatCorruptedAuthFailureReadinessError::None; - const char* error_name = "None"; -}; - -enum class HushChatCompatibilityFixtureImportError { - None, - FeatureDisabled, - MissingRequiredKind, - DuplicateKind, - FixtureLoadFailed, - FixtureKindMismatch, - FixturePending, - FixtureInvalid, - FixtureNotVerified, - SeedProjectionFailed, - AuthFailureScaffoldFailed, - ContactFixtureNotExcluded -}; - -struct HushChatCompatibilityFixtureImportCandidate { - HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; - std::string path; -}; - -struct HushChatCompatibilityFixtureImportItem { - HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; - HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo; - std::string path; - bool supplied = false; - bool pending = false; - bool replacement_eligible = false; - bool seed_projection_verified = false; - bool future_auth_failure_required = false; - bool structurally_ready_for_future_auth_check = false; - HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; - const char* error_name = "None"; - HushChatCompatibilityFixtureFileParseResult parsed; - HushChatSeedPublicKeyProjectionResult seed_projection; - HushChatCorruptedAuthFailureReadinessResult auth_failure_readiness; -}; - -struct HushChatCompatibilityFixtureImportChecklistResult { - bool ok = false; - bool feature_enabled = false; - bool replacement_ready = false; - HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; - const char* error_name = "None"; - std::size_t required_count = 0; - std::size_t supplied_count = 0; - std::size_t missing_count = 0; - std::size_t pending_count = 0; - std::size_t verified_count = 0; - std::size_t seed_projection_verified_count = 0; - std::size_t future_auth_failure_required_count = 0; - std::size_t auth_failure_structural_ready_count = 0; - std::size_t excluded_count = 0; - std::size_t rejected_count = 0; - std::vector items; -}; - -struct HushChatCompatibilityFixtureReplacementReportItem { - HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo; - HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo; - std::string path; - bool supplied = false; - bool pending = false; - bool replacement_eligible = false; - bool refused = true; - bool seed_projection_verified = false; - bool future_auth_failure_required = false; - bool structurally_ready_for_future_auth_check = false; - bool cont_excluded = false; - bool decrypted = false; - bool authenticated = false; - HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; - const char* error_name = "None"; -}; - -struct HushChatCompatibilityFixtureReplacementDryRunResult { - bool ok = false; - bool feature_enabled = false; - bool dry_run_only = true; - bool redacted_report = true; - bool would_replace = false; - bool replacement_refused = true; - HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None; - const char* error_name = "None"; - std::size_t required_count = 0; - std::size_t supplied_count = 0; - std::size_t missing_count = 0; - std::size_t pending_count = 0; - std::size_t verified_count = 0; - std::size_t seed_projection_verified_count = 0; - std::size_t future_auth_failure_required_count = 0; - std::size_t auth_failure_structural_ready_count = 0; - std::size_t excluded_count = 0; - std::size_t rejected_count = 0; - std::vector report_items; -}; - -enum class HushChatCaptureManifestError { - None, - FeatureDisabled, - FileReadFailed, - InvalidJson, - JsonNotObject, - InvalidSchema, - MissingManifestId, - MissingStatus, - UnknownStatus, - MissingFixtureDirectory, - MissingDryRunCommand, - InvalidDryRunCommand, - MissingProvenance, - MissingSourceClient, - InvalidSourceClient, - MissingSourceClientVersion, - MissingCaptureDate, - MissingNetwork, - MissingCaptureMethod, - MissingHandling, - MissingHandlingFlag, - HandlingFlagNotTrue, - MissingCategories, - InvalidCategoryEntry, - UnknownCategory, - DuplicateCategory, - MissingRequiredCategory, - ProhibitedFieldPresent -}; - -enum class HushChatCaptureManifestStatus { - Staged -}; - -struct HushChatCaptureManifestCategoryReport { - HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo; - std::string staged_filename; - bool declared = false; -}; - -struct HushChatCaptureManifestValidationResult { - bool ok = false; - bool feature_enabled = false; - bool redacted_report = true; - bool validates_provenance_only = true; - bool no_sensitive_material_declared = false; - bool has_dry_run_command = false; - HushChatCaptureManifestError error = HushChatCaptureManifestError::None; - const char* error_name = "None"; - HushChatCaptureManifestStatus status = HushChatCaptureManifestStatus::Staged; - std::string manifest_path; - std::string fixture_directory; - std::size_t required_count = 0; - std::size_t declared_count = 0; - std::size_t missing_count = 0; - std::size_t duplicate_count = 0; - std::size_t prohibited_field_count = 0; - std::size_t handling_flag_count = 0; - std::vector categories; -}; - constexpr int kHushChatSupportedVersion = 0; constexpr std::size_t kHushChatMemoByteLimit = 512; constexpr std::size_t kHushChatPublicKeyHexLength = 64; constexpr std::size_t kHushChatSecretstreamHeaderHexLength = 48; -constexpr std::size_t kHushChatSecretstreamABytes = 17; -constexpr std::size_t kHushChatStoredChatKeyByteLength = 32; -constexpr std::size_t kHushChatStoredChatKeyHexLength = kHushChatStoredChatKeyByteLength * 2; -constexpr std::size_t kHushChatSeedByteLength = 32; -constexpr std::size_t kHushChatPublicKeyByteLength = kHushChatPublicKeyHexLength / 2; -constexpr std::size_t kHushChatSecretstreamHeaderByteLength = kHushChatSecretstreamHeaderHexLength / 2; -constexpr const char* kHushChatCompatibilityFixtureSchema = "dragonx.hushchat.compat-fixture.v1"; -constexpr const char* kHushChatCaptureManifestSchema = "dragonx.hushchat.capture-manifest.v1"; constexpr bool hushChatFeatureEnabledAtBuild() { @@ -527,60 +99,7 @@ HushChatMemoGroupingResult groupHushChatMemoOutputs(const std::vector hushChatRequiredCompatibilityFixtureKinds(); -HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist( - const std::vector& candidates, - bool featureEnabled = hushChatFeatureEnabledAtBuild()); -HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun( - const std::vector& candidates, - bool featureEnabled = hushChatFeatureEnabledAtBuild()); -HushChatCaptureManifestValidationResult validateHushChatCaptureManifest( - const std::string& jsonText, - bool featureEnabled = hushChatFeatureEnabledAtBuild()); -HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile( - const std::string& path, - bool featureEnabled = hushChatFeatureEnabledAtBuild()); const char* hushChatHeaderTypeName(HushChatHeaderType type); const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue); -const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error); -const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error); -const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction); -const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection); -const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error); -const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error); -const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind); -const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status); -const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error); -const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error); -const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error); -const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error); -const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error); -} // namespace dragonx::chat \ No newline at end of file +} // namespace dragonx::chat diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 0a259a2..cfb1caf 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -287,16 +287,12 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) { return; } - const auto result = wallet::executeLiteWalletLifecycleUiRequest(*app->settings(), input); - s_settingsState.lite_lifecycle_summary = result.requestSummaryRedacted; - if (result.ok) { - s_settingsState.lite_lifecycle_status = "Ready"; - } else { - s_settingsState.lite_lifecycle_status = result.error.empty() - ? wallet::liteWalletLifecycleUiExecutionStatusName(result.status) - : result.error; - Notifications::instance().warning(s_settingsState.lite_lifecycle_status); - } + // No linked lite backend to execute against (full-node build, or the lite backend + // failed to load / rollout-disabled). The live path above returns when a backend is + // present, so reaching here means there is nothing to run. + s_settingsState.lite_lifecycle_summary.clear(); + s_settingsState.lite_lifecycle_status = "Lite wallet backend unavailable"; + Notifications::instance().warning(s_settingsState.lite_lifecycle_status); } // (APPEARANCE card now uses ChannelsSplit like all other cards) diff --git a/src/wallet/lite_wallet_lifecycle_ui_adapter.cpp b/src/wallet/lite_wallet_lifecycle_ui_adapter.cpp index 9fec5fb..db909f5 100644 --- a/src/wallet/lite_wallet_lifecycle_ui_adapter.cpp +++ b/src/wallet/lite_wallet_lifecycle_ui_adapter.cpp @@ -1,428 +1,7 @@ #include "lite_wallet_lifecycle_ui_adapter.h" -#include -#include - -namespace dragonx { -namespace wallet { - -namespace { - -void addIssue(std::vector& issues, - LiteWalletLifecycleUiExecutionIssue issue, - std::string message) -{ - issues.push_back(LiteWalletLifecycleUiExecutionIssueInfo{issue, std::move(message)}); -} - -LiteWalletLifecycleUiExecutionResult stoppedResult( - LiteWalletLifecycleUiExecutionResult result, - LiteWalletLifecycleUiExecutionStatus status, - LiteWalletLifecycleUiExecutionIssue issue, - const std::string& message) -{ - result.status = status; - result.error = message; - result.ok = false; - addIssue(result.issues, issue, message); - return result; -} - -LiteWalletLifecycleUiExecutionStatus statusFromServerSelection( - LiteWalletServerSelectionUiExecutionStatus status) -{ - switch (status) { - case LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle: - return LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild: - return LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability: - return LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings: - return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection: - return LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner: - return LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus: - return LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi: - return LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction: - return LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed: - return LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed; - case LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed: - return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings; - case LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled: - return LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled; - } - return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings; -} - -LiteWalletLifecycleUiExecutionStatus statusFromLifecycle( - LiteWalletServerLifecycleReadinessStatus status) -{ - switch (status) { - case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: - return LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: - return LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild; - case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: - return LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability; - case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: - return LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: - return LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection; - case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: - return LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: - return LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: - return LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction; - case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: - return LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed; - case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: - return LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled; - } - return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings; -} - -LiteWalletLifecycleUiExecutionIssue issueFromLifecycle( - LiteWalletServerLifecycleReadinessIssue issue) -{ - switch (issue) { - case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: - return LiteWalletLifecycleUiExecutionIssue::FullNodeBuild; - case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: - return LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing; - case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: - return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; - case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: - return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing; - case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: - return LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing; - case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: - return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing; - case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: - return LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing; - case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: - return LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing; - case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: - return LiteWalletLifecycleUiExecutionIssue::LifecycleOperationUnconfirmed; - case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: - return LiteWalletLifecycleUiExecutionIssue::OpenWalletPathMissing; - case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: - return LiteWalletLifecycleUiExecutionIssue::RestoreSeedMissing; - case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: - return LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing; - case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: - return LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing; - case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: - return LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested; - } - return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; -} - -LiteWalletLifecycleUiExecutionIssue issueFromServerSelection( - LiteWalletServerSelectionUiExecutionIssue issue) -{ - switch (issue) { - case LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild: - return LiteWalletLifecycleUiExecutionIssue::FullNodeBuild; - case LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing: - return LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing; - case LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded: - return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; - case LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing: - return LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing; - case LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing: - return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing; - case LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing: - return LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing; - case LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing: - return LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing; - case LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing: - return LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing; - case LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing: - return LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing; - case LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed: - return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; - case LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested: - return LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested; - case LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested: - return LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested; - case LiteWalletServerSelectionUiExecutionIssue::SyncRequested: - return LiteWalletLifecycleUiExecutionIssue::SyncRequested; - case LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested: - return LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested; - case LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested: - return LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested; - case LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested: - return LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested; - case LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested: - return LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested; - case LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested: - return LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested; - } - return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; -} - -void copyServerSelectionIssues(LiteWalletLifecycleUiExecutionResult& result) -{ - for (const auto& issue : result.serverSelectionReport.issues) { - addIssue(result.issues, issueFromServerSelection(issue.issue), issue.message); - } -} - -void copyLifecycleIssues(LiteWalletLifecycleUiExecutionResult& result) -{ - for (const auto& issue : result.lifecycleReadiness.issues) { - addIssue(result.issues, issueFromLifecycle(issue.issue), issue.message); - } -} - -LiteWalletServerSelectionUiExecutionInput makeServerSelectionInput( - const LiteWalletLifecycleUiExecutionInput& input, - const config::Settings& settings) -{ - LiteWalletServerSelectionUiExecutionInput selection; - selection.capabilities = input.capabilities; - selection.persistence.settingsLoaded = input.settingsLoaded; - selection.persistence.havePersistedSelectionIntent = - !input.requirePersistedServerSelectionIntent || settings.getLitePersistSelectedServer(); - selection.persistence.persistSelectedServer = false; - selection.persistence.persistenceOwnerReady = true; - selection.persistence.writeSettings = false; - selection.ui = input.ui; - selection.operation = input.request.operation; - selection.createRequest = input.request.createRequest; - selection.openRequest = input.request.openRequest; - selection.restoreRequest = input.request.restoreRequest; - selection.requireLifecycleReadiness = true; - return selection; -} - -std::string buildDiagnosticSummary(const LiteWalletLifecycleUiExecutionResult& result) -{ - std::ostringstream out; - out << "operation=" << liteWalletLifecycleOperationName(result.lifecycleInput.operation) - << ";server=" << result.selectedServerUrlRedacted - << ";request=" << result.requestSummaryRedacted - << ";network=false;bridge=false;lifecycle=false;sync=false;wallet_state=false;wallet_file=false"; - return out.str(); -} - -} // namespace - -const char* liteWalletLifecycleUiExecutionStatusName( - LiteWalletLifecycleUiExecutionStatus status) -{ - switch (status) { - case LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest: - return "ReadyForFutureLifecycleRequest"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild: - return "WaitingForLiteBuild"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability: - return "WaitingForBackendCapability"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForSettings: - return "WaitingForSettings"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection: - return "WaitingForPersistedServerSelection"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection: - return "WaitingForServerSelection"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus: - return "WaitingForDisplayStatus"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi: - return "WaitingForLifecycleUi"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForRequest: - return "WaitingForRequest"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction: - return "WaitingForPrivateDataRedaction"; - case LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed: - return "WaitingForSyncPlannerFeed"; - case LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled: - return "RuntimeExecutionDisabled"; - } - return "WaitingForSettings"; -} - -const char* liteWalletLifecycleUiExecutionIssueName( - LiteWalletLifecycleUiExecutionIssue issue) -{ - switch (issue) { - case LiteWalletLifecycleUiExecutionIssue::FullNodeBuild: - return "FullNodeBuild"; - case LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing: - return "LiteBackendCapabilityMissing"; - case LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded: - return "SettingsNotLoaded"; - case LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing: - return "PersistedServerSelectionIntentMissing"; - case LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing: - return "ServerSelectionMissing"; - case LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing: - return "SelectedServerDisplayMissing"; - case LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing: - return "LifecycleUiOwnerMissing"; - case LiteWalletLifecycleUiExecutionIssue::LifecycleOperationUnconfirmed: - return "LifecycleOperationUnconfirmed"; - case LiteWalletLifecycleUiExecutionIssue::OpenWalletPathMissing: - return "OpenWalletPathMissing"; - case LiteWalletLifecycleUiExecutionIssue::RestoreSeedMissing: - return "RestoreSeedMissing"; - case LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing: - return "PrivateDataRedactionMissing"; - case LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing: - return "SyncPlannerFeedMissing"; - case LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested: - return "ServerConnectivityCheckRequested"; - case LiteWalletLifecycleUiExecutionIssue::WalletExistsCheckRequested: - return "WalletExistsCheckRequested"; - case LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested: - return "WalletLifecycleExecutionRequested"; - case LiteWalletLifecycleUiExecutionIssue::SyncRequested: - return "SyncRequested"; - case LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested: - return "SyncStatusPollingRequested"; - case LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested: - return "WorkerQueueRequested"; - case LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested: - return "WalletStateMutationRequested"; - case LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested: - return "WalletFilePersistenceRequested"; - case LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested: - return "SendImportExportRequested"; - case LiteWalletLifecycleUiExecutionIssue::SettingsWriteRequested: - return "SettingsWriteRequested"; - } - return "SettingsNotLoaded"; -} - -LiteWalletLifecycleUiExecutionResult executeLiteWalletLifecycleUiRequest( - config::Settings& settings, - const LiteWalletLifecycleUiExecutionInput& input) -{ - LiteWalletLifecycleUiExecutionResult result; - - auto stopForRuntime = [&](LiteWalletLifecycleUiExecutionIssue issue, - const std::string& message) { - return stoppedResult(std::move(result), - LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled, - issue, - message); - }; - - if (input.settingsWriteRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SettingsWriteRequested, - "lite lifecycle request adapter cannot write settings"); - } - if (input.serverConnectivityCheckRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested, - "lite lifecycle request adapter cannot check server connectivity"); - } - if (input.walletExistsCheckRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletExistsCheckRequested, - "lite lifecycle request adapter cannot check wallet existence"); - } - if (input.walletLifecycleExecutionRequested || input.ui.realLifecycleExecutionRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested, - "lite lifecycle request adapter cannot execute wallet lifecycle actions"); - } - if (input.syncStartRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SyncRequested, - "lite lifecycle request adapter cannot start sync"); - } - if (input.syncStatusPollingRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested, - "lite lifecycle request adapter cannot poll syncstatus"); - } - if (input.workerQueueEnqueueRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested, - "lite lifecycle request adapter cannot enqueue wallet workers"); - } - if (input.walletStateMutationRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested, - "lite lifecycle request adapter cannot mutate WalletState"); - } - if (input.walletFilePersistenceRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested, - "lite lifecycle request adapter cannot persist wallet files"); - } - if (input.sendImportExportExecutionRequested) { - return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested, - "lite lifecycle request adapter cannot execute send/import/export flows"); - } - - if (!input.settingsLoaded) { - return stoppedResult(std::move(result), - LiteWalletLifecycleUiExecutionStatus::WaitingForSettings, - LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded, - "lite lifecycle settings are not loaded"); - } - if (!input.request.requestProvided) { - return stoppedResult(std::move(result), - LiteWalletLifecycleUiExecutionStatus::WaitingForRequest, - LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing, - "lite lifecycle request intent is missing"); - } - if (input.requirePersistedServerSelectionIntent && !settings.getLitePersistSelectedServer()) { - return stoppedResult(std::move(result), - LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection, - LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing, - "lite lifecycle requires persisted selected-server intent"); - } - - result.connectionSettings = liteConnectionSettingsFromAppSettings(settings); - result.selectedServerSettingsLoaded = true; - - LiteWalletServerSelectionUiExecutionInput selectionInput = makeServerSelectionInput(input, settings); - result.serverSelectionReport = executeLiteWalletServerSelectionUi(settings, selectionInput); - result.connectionSettings = result.serverSelectionReport.connectionSettings; - result.selectedServer = result.serverSelectionReport.selectedServer; - result.selectedServerUrlRedacted = result.serverSelectionReport.selectedServerUrlRedacted; - result.selectedServerIntentAccepted = result.serverSelectionReport.selectedServerIntentAccepted; - result.status = statusFromServerSelection(result.serverSelectionReport.status); - - result.lifecycleInput = result.serverSelectionReport.lifecycleInput; - result.lifecycleReadiness = result.serverSelectionReport.lifecycleReadiness; - result.lifecycleReadinessAccepted = result.lifecycleReadiness.ok; - result.lifecycleReportCouldFeedSyncPlanners = - result.lifecycleReadiness.lifecycleReportCouldFeedSyncPlanners; - result.requestAccepted = result.lifecycleReadiness.requestAccepted; - result.privateDataRedacted = result.lifecycleReadiness.requestPlan.privateInputsRedacted; - result.privateData = result.lifecycleReadiness.requestPlan.privateData; - result.requestSummaryRedacted = result.lifecycleReadiness.requestPlan.requestSummary; - result.diagnosticSummary = buildDiagnosticSummary(result); - copyServerSelectionIssues(result); - copyLifecycleIssues(result); - - const bool lifecycleReadinessEvaluated = result.lifecycleReadiness.ok || - !result.lifecycleReadiness.issues.empty() || - !result.lifecycleReadiness.error.empty() || - result.lifecycleReadiness.serverSelectionAccepted || - result.lifecycleReadiness.requestAccepted; - - if (!result.serverSelectionReport.ok && !lifecycleReadinessEvaluated) { - result.ok = false; - result.status = statusFromServerSelection(result.serverSelectionReport.status); - result.error = result.serverSelectionReport.error; - return result; - } - - if (!result.lifecycleReadiness.ok) { - result.ok = false; - result.status = statusFromLifecycle(result.lifecycleReadiness.status); - result.error = result.lifecycleReadiness.error.empty() - ? result.serverSelectionReport.error - : result.lifecycleReadiness.error; - return result; - } - - result.ok = true; - result.status = LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest; - return result; -} - -} // namespace wallet -} // namespace dragonx \ No newline at end of file +// The lite-wallet lifecycle UI adapter is now header-only: it declares the live +// LiteWalletLifecycleUiExecutionInput DTO (and its request-intent / prerequisites +// sub-structs) that Settings drives the App-owned controller with. The former +// dry-run "readiness" executor and its status/issue reporting were removed as dead +// scaffolding, leaving no out-of-line definitions in this translation unit. diff --git a/src/wallet/lite_wallet_lifecycle_ui_adapter.h b/src/wallet/lite_wallet_lifecycle_ui_adapter.h index 166e5fa..e09255e 100644 --- a/src/wallet/lite_wallet_lifecycle_ui_adapter.h +++ b/src/wallet/lite_wallet_lifecycle_ui_adapter.h @@ -1,53 +1,21 @@ #pragma once #include "lite_wallet_server_selection_adapter.h" +#include "lite_wallet_lifecycle_service.h" +#include "wallet_capabilities.h" #include "../config/settings.h" -#include -#include - namespace dragonx { namespace wallet { -enum class LiteWalletLifecycleUiExecutionStatus { - ReadyForFutureLifecycleRequest, - WaitingForLiteBuild, - WaitingForBackendCapability, - WaitingForSettings, - WaitingForPersistedServerSelection, - WaitingForServerSelection, - WaitingForDisplayStatus, - WaitingForLifecycleUi, - WaitingForRequest, - WaitingForPrivateDataRedaction, - WaitingForSyncPlannerFeed, - RuntimeExecutionDisabled -}; - -enum class LiteWalletLifecycleUiExecutionIssue { - FullNodeBuild, - LiteBackendCapabilityMissing, - SettingsNotLoaded, - PersistedServerSelectionIntentMissing, - ServerSelectionMissing, - SelectedServerDisplayMissing, - LifecycleUiOwnerMissing, - LifecycleOperationUnconfirmed, - OpenWalletPathMissing, - RestoreSeedMissing, - PrivateDataRedactionMissing, - SyncPlannerFeedMissing, - ServerConnectivityCheckRequested, - WalletExistsCheckRequested, - WalletLifecycleExecutionRequested, - SyncRequested, - SyncStatusPollingRequested, - WorkerQueueRequested, - WalletStateMutationRequested, - WalletFilePersistenceRequested, - SendImportExportRequested, - SettingsWriteRequested +struct LiteWalletLifecycleUiPrerequisites { + bool selectedServerDisplayReady = false; + bool lifecycleUiOwnerReady = false; + bool operationConfirmed = false; + bool privateDataRedactionReady = false; + bool syncPlannerFeedReady = false; + bool realLifecycleExecutionRequested = false; }; struct LiteWalletLifecycleUiRequestIntent { @@ -77,59 +45,5 @@ struct LiteWalletLifecycleUiExecutionInput { bool sendImportExportExecutionRequested = false; }; -struct LiteWalletLifecycleUiExecutionIssueInfo { - LiteWalletLifecycleUiExecutionIssue issue = LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded; - std::string message; -}; - -struct LiteWalletLifecycleUiExecutionResult { - bool ok = false; - bool dryRunOnly = true; - bool noNetwork = true; - bool noBridgeCalls = true; - bool noServerHealthChecked = true; - bool noWalletExistsChecked = true; - bool noWalletCreated = true; - bool noWalletOpened = true; - bool noWalletRestored = true; - bool noSyncStarted = true; - bool noSyncStatusPolled = true; - bool noWalletPersistence = true; - bool noSettingsPersistence = true; - bool noSendImportExportExecution = true; - bool settingsWritten = false; - bool workerQueueEnqueued = false; - bool stateMutationAllowed = false; - bool stateMutated = false; - bool walletStateWritten = false; - bool selectedServerSettingsLoaded = false; - bool selectedServerIntentAccepted = false; - bool lifecycleReadinessAccepted = false; - bool requestAccepted = false; - bool privateDataRedacted = false; - bool lifecycleReportCouldFeedSyncPlanners = false; - LiteWalletLifecycleUiExecutionStatus status = LiteWalletLifecycleUiExecutionStatus::WaitingForSettings; - std::string error; - std::string selectedServerUrlRedacted; - std::string requestSummaryRedacted; - std::string diagnosticSummary; - LiteConnectionSettings connectionSettings; - LiteServerSelectionResult selectedServer; - LiteWalletServerSelectionUiExecutionResult serverSelectionReport; - LiteWalletServerLifecycleReadinessInput lifecycleInput; - LiteWalletServerLifecycleReadinessResult lifecycleReadiness; - std::vector privateData; - std::vector issues; -}; - -const char* liteWalletLifecycleUiExecutionStatusName( - LiteWalletLifecycleUiExecutionStatus status); -const char* liteWalletLifecycleUiExecutionIssueName( - LiteWalletLifecycleUiExecutionIssue issue); - -LiteWalletLifecycleUiExecutionResult executeLiteWalletLifecycleUiRequest( - config::Settings& settings, - const LiteWalletLifecycleUiExecutionInput& input); - } // namespace wallet -} // namespace dragonx \ No newline at end of file +} // namespace dragonx diff --git a/src/wallet/lite_wallet_server_lifecycle_readiness.cpp b/src/wallet/lite_wallet_server_lifecycle_readiness.cpp deleted file mode 100644 index 81c0334..0000000 --- a/src/wallet/lite_wallet_server_lifecycle_readiness.cpp +++ /dev/null @@ -1,386 +0,0 @@ -#include "wallet/lite_wallet_server_lifecycle_readiness.h" - -#include -#include - -namespace dragonx::wallet { -namespace { - -std::string trimCopy(const std::string& value) -{ - auto begin = value.begin(); - while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; - - auto end = value.end(); - while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; - - return std::string(begin, end); -} - -void addIssue(LiteWalletServerLifecycleReadinessResult& result, - LiteWalletServerLifecycleReadinessIssue issue, - std::string message) -{ - result.issues.push_back(LiteWalletServerLifecycleReadinessIssueInfo{issue, std::move(message)}); -} - -LiteWalletServerLifecycleReadinessResult stoppedResult( - LiteWalletServerLifecycleReadinessResult result, - LiteWalletServerLifecycleReadinessStatus status, - LiteWalletServerLifecycleReadinessIssue issue, - std::string message) -{ - result.status = status; - addIssue(result, issue, std::move(message)); - result.error = result.issues.back().message; - result.lifecycleStatus = WalletBackendStatus{WalletBackendState::Unavailable, result.error, {}, {}, 0.0}; - return result; -} - -LiteRedactedPrivateData redactedPrivateField(LitePrivateDataKind kind, const std::string& value) -{ - return LiteRedactedPrivateData{kind, !trimCopy(value).empty(), redactLitePrivateDataValue(value)}; -} - -bool privateDataIsRedacted(const std::vector& privateData) -{ - for (const auto& item : privateData) { - if (!item.present && item.redactedValue != "") return false; - if (item.present && item.redactedValue != "") return false; - } - return true; -} - -LiteWalletSelectedServerDisplayReport displayReportForSelection( - const LiteServerSelectionResult& selection) -{ - LiteWalletSelectedServerDisplayReport report; - if (!selection.ok) { - report.status = LiteWalletSelectedServerDisplayStatus::Missing; - report.message = selection.error.empty() ? "no usable lite server is selected" : selection.error; - return report; - } - - report.ok = true; - report.status = selection.customServer - ? LiteWalletSelectedServerDisplayStatus::CustomServer - : LiteWalletSelectedServerDisplayStatus::SelectedServer; - report.label = selection.server.label; - report.url = selection.server.url; - report.serverIndex = selection.serverIndex; - report.customServer = selection.customServer; - report.message = selection.customServer - ? "custom lite server selected for display" - : "configured lite server selected for display"; - return report; -} - -LiteWalletLifecyclePlan lifecyclePlanForRequest( - LiteWalletLifecycleOperation operation, - const LiteServerSelectionResult& selection) -{ - LiteWalletLifecyclePlan plan; - plan.operation = operation; - plan.bridgeExecutionAllowed = false; - if (!selection.ok) { - plan.error = selection.error.empty() ? "no usable lite server is selected" : selection.error; - return plan; - } - - plan.ok = true; - plan.server = selection.server; - plan.serverIndex = selection.serverIndex; - plan.customServer = selection.customServer; - return plan; -} - -LiteWalletLifecycleUiRequestPlan requestPlanForInput( - const LiteWalletServerLifecycleReadinessInput& input, - const LiteServerSelectionResult& selection) -{ - LiteWalletLifecycleUiRequestPlan plan; - plan.operation = input.operation; - plan.lifecyclePlan = lifecyclePlanForRequest(input.operation, selection); - if (!plan.lifecyclePlan.ok) { - plan.error = plan.lifecyclePlan.error; - return plan; - } - - switch (input.operation) { - case LiteWalletLifecycleOperation::CreateNew: - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::Passphrase, - input.createRequest.passphrase)); - plan.requestSummary = "operation=create;server=;passphrase=" + - std::string(input.createRequest.passphrase.empty() ? "" : ""); - break; - case LiteWalletLifecycleOperation::OpenExisting: - if (trimCopy(input.openRequest.walletPath).empty()) { - plan.error = "lite wallet open requires a wallet path selected by the UI"; - return plan; - } - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::WalletPath, - input.openRequest.walletPath)); - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::Passphrase, - input.openRequest.passphrase)); - plan.requestSummary = "operation=open;server=;wallet_path=;passphrase=" + - std::string(input.openRequest.passphrase.empty() ? "" : ""); - break; - case LiteWalletLifecycleOperation::RestoreFromSeed: - if (trimCopy(input.restoreRequest.seedPhrase).empty()) { - plan.error = "lite wallet restore requires redacted seed material metadata"; - return plan; - } - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::SeedPhrase, - input.restoreRequest.seedPhrase)); - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::WalletPath, - input.restoreRequest.walletPath)); - plan.privateData.push_back(redactedPrivateField( - LitePrivateDataKind::Passphrase, - input.restoreRequest.passphrase)); - plan.requestSummary = "operation=restore;server=;seed=;wallet_path=" + - std::string(input.restoreRequest.walletPath.empty() ? "" : "") + - ";passphrase=" + - std::string(input.restoreRequest.passphrase.empty() ? "" : ""); - break; - } - - plan.privateInputsRedacted = privateDataIsRedacted(plan.privateData); - plan.lifecyclePlan.privateData = plan.privateData; - plan.ok = plan.privateInputsRedacted; - if (!plan.ok) plan.error = "lite wallet lifecycle private inputs are not redacted"; - return plan; -} - -LiteWalletServerSelectionPersistencePlan persistencePlanForInput( - const LiteWalletServerLifecycleReadinessInput& input, - const LiteServerSelectionResult& selection) -{ - LiteWalletServerSelectionPersistencePlan plan; - plan.settingsLoaded = input.persistence.settingsLoaded; - plan.persistedSelectionIntentAccepted = input.persistence.havePersistedSelectionIntent; - plan.wouldPersistSelectedServer = input.persistence.persistSelectedServer; - plan.persistenceOwnerAccepted = input.persistence.persistenceOwnerReady; - plan.selectionMode = input.settings.selectionMode; - if (selection.ok) { - plan.selectedServerUrl = selection.server.url; - plan.selectedServerIndex = selection.serverIndex; - plan.selectedServerCustom = selection.customServer; - } - plan.settingsWritten = false; - plan.ok = true; - return plan; -} - -WalletBackendStatus readyLifecycleStatus(const LiteWalletSelectedServerDisplayReport& display) -{ - const std::string serverLabel = display.label.empty() ? display.url : display.label; - return WalletBackendStatus{ - WalletBackendState::Disconnected, - "lite lifecycle UI readiness accepted for " + serverLabel + "; wallet lifecycle execution is still disabled", - {}, - {}, - 0.0 - }; -} - -} // namespace - -const char* liteWalletServerLifecycleReadinessStatusName( - LiteWalletServerLifecycleReadinessStatus status) -{ - switch (status) { - case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: return "ReadyForFutureLifecycle"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: return "WaitingForServerSelection"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: return "WaitingForPersistenceIntent"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: return "WaitingForDisplayStatus"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: return "WaitingForLifecycleUi"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: return "WaitingForPrivateDataRedaction"; - case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: return "WaitingForSyncPlannerFeed"; - case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: return "RuntimeExecutionDisabled"; - } - return "Unknown"; -} - -const char* liteWalletServerLifecycleReadinessIssueName( - LiteWalletServerLifecycleReadinessIssue issue) -{ - switch (issue) { - case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: return "FullNodeBuild"; - case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing"; - case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: return "PersistedSettingsNotLoaded"; - case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: return "PersistedServerSelectionIntentMissing"; - case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: return "ServerSelectionMissing"; - case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: return "ServerPersistenceOwnerMissing"; - case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: return "SelectedServerDisplayMissing"; - case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: return "LifecycleUiOwnerMissing"; - case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: return "LifecycleOperationUnconfirmed"; - case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: return "OpenWalletPathMissing"; - case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: return "RestoreSeedMissing"; - case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: return "PrivateDataRedactionMissing"; - case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: return "SyncPlannerFeedMissing"; - case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: return "RealLifecycleExecutionRequested"; - } - return "Unknown"; -} - -const char* liteWalletSelectedServerDisplayStatusName( - LiteWalletSelectedServerDisplayStatus status) -{ - switch (status) { - case LiteWalletSelectedServerDisplayStatus::Hidden: return "Hidden"; - case LiteWalletSelectedServerDisplayStatus::SelectedServer: return "SelectedServer"; - case LiteWalletSelectedServerDisplayStatus::CustomServer: return "CustomServer"; - case LiteWalletSelectedServerDisplayStatus::Missing: return "Missing"; - } - return "Unknown"; -} - -LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness( - const LiteWalletServerLifecycleReadinessInput& input, - LiteWalletServerLifecycleReadinessOptions options) -{ - LiteWalletServerLifecycleReadinessResult result; - result.capabilities = input.capabilities; - result.persistencePlan = persistencePlanForInput(input, {}); - - if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild, - LiteWalletServerLifecycleReadinessIssue::FullNodeBuild, - "lite server lifecycle readiness requires a lite build"); - } - result.liteBuildAccepted = true; - - if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability, - LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing, - "lite backend capability is required before lifecycle UI readiness can feed sync planners"); - } - result.backendCapabilityAccepted = true; - - if (options.requirePersistedSettingsLoaded && !input.persistence.settingsLoaded) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, - LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded, - "lite server settings must be loaded before lifecycle UI readiness is evaluated"); - } - - if (options.requirePersistedSelectionIntent && !input.persistence.havePersistedSelectionIntent) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, - LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing, - "lite server selection persistence intent is missing"); - } - - result.selectedServer = selectLiteServer(input.settings); - result.persistencePlan = persistencePlanForInput(input, result.selectedServer); - if (!result.selectedServer.ok) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection, - LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing, - result.selectedServer.error.empty() - ? "no usable lite server is selected" - : result.selectedServer.error); - } - result.serverSelectionAccepted = true; - - if (input.persistence.persistSelectedServer && !input.persistence.persistenceOwnerReady) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, - LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing, - "lite server selection persistence owner is not ready"); - } - result.persistenceIntentAccepted = true; - result.persistencePlan.ok = true; - - result.selectedServerDisplay = displayReportForSelection(result.selectedServer); - if (options.requireSelectedServerDisplay && !input.ui.selectedServerDisplayReady) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus, - LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing, - "selected lite server display status is not ready"); - } - result.selectedServerDisplayAccepted = true; - - if (options.requireLifecycleUiOwner && !input.ui.lifecycleUiOwnerReady) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, - LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing, - "lite wallet lifecycle UI owner is not ready"); - } - result.lifecycleUiOwnerAccepted = true; - - if (input.ui.realLifecycleExecutionRequested) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled, - LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested, - "real lite wallet lifecycle execution is disabled in this scaffold"); - } - - if (options.requireOperationConfirmation && !input.ui.operationConfirmed) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, - LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed, - "lite wallet lifecycle operation requires explicit UI confirmation"); - } - - result.requestPlan = requestPlanForInput(input, result.selectedServer); - if (!result.requestPlan.ok) { - const auto issue = input.operation == LiteWalletLifecycleOperation::OpenExisting - ? LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing - : LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing; - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, - issue, - result.requestPlan.error.empty() - ? "lite wallet lifecycle UI request is incomplete" - : result.requestPlan.error); - } - result.requestAccepted = true; - - if (options.requirePrivateDataRedaction && !input.ui.privateDataRedactionReady) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction, - LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing, - "lite wallet lifecycle private-data redaction owner is not ready"); - } - result.privateDataRedactionAccepted = true; - - if (options.requireSyncPlannerFeed && !input.ui.syncPlannerFeedReady) { - return stoppedResult( - std::move(result), - LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed, - LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing, - "lite lifecycle readiness feed for sync planners is not ready"); - } - result.syncPlannerFeedAccepted = true; - - result.ok = true; - result.status = LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle; - result.futureLifecycleCouldBeEnabled = true; - result.lifecycleReportCouldFeedSyncPlanners = true; - result.lifecycleStatus = readyLifecycleStatus(result.selectedServerDisplay); - return result; -} - -} // namespace dragonx::wallet \ No newline at end of file diff --git a/src/wallet/lite_wallet_server_lifecycle_readiness.h b/src/wallet/lite_wallet_server_lifecycle_readiness.h deleted file mode 100644 index 7ed8936..0000000 --- a/src/wallet/lite_wallet_server_lifecycle_readiness.h +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -#include "lite_wallet_lifecycle_service.h" - -#include -#include -#include - -namespace dragonx::wallet { - -enum class LiteWalletServerLifecycleReadinessStatus { - ReadyForFutureLifecycle, - WaitingForLiteBuild, - WaitingForBackendCapability, - WaitingForServerSelection, - WaitingForPersistenceIntent, - WaitingForDisplayStatus, - WaitingForLifecycleUi, - WaitingForPrivateDataRedaction, - WaitingForSyncPlannerFeed, - RuntimeExecutionDisabled, -}; - -enum class LiteWalletServerLifecycleReadinessIssue { - FullNodeBuild, - LiteBackendCapabilityMissing, - PersistedSettingsNotLoaded, - PersistedServerSelectionIntentMissing, - ServerSelectionMissing, - ServerPersistenceOwnerMissing, - SelectedServerDisplayMissing, - LifecycleUiOwnerMissing, - LifecycleOperationUnconfirmed, - OpenWalletPathMissing, - RestoreSeedMissing, - PrivateDataRedactionMissing, - SyncPlannerFeedMissing, - RealLifecycleExecutionRequested, -}; - -enum class LiteWalletSelectedServerDisplayStatus { - Hidden, - SelectedServer, - CustomServer, - Missing, -}; - -struct LiteWalletServerSelectionPersistenceInput { - bool settingsLoaded = false; - bool havePersistedSelectionIntent = false; - bool persistSelectedServer = false; - bool persistenceOwnerReady = false; -}; - -struct LiteWalletLifecycleUiPrerequisites { - bool selectedServerDisplayReady = false; - bool lifecycleUiOwnerReady = false; - bool operationConfirmed = false; - bool privateDataRedactionReady = false; - bool syncPlannerFeedReady = false; - bool realLifecycleExecutionRequested = false; -}; - -struct LiteWalletServerLifecycleReadinessInput { - WalletCapabilities capabilities; - LiteConnectionSettings settings; - LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew; - LiteWalletServerSelectionPersistenceInput persistence; - LiteWalletLifecycleUiPrerequisites ui; - LiteWalletCreateRequest createRequest; - LiteWalletOpenRequest openRequest; - LiteWalletRestoreRequest restoreRequest; -}; - -struct LiteWalletServerLifecycleReadinessOptions { - bool requireLiteBuild = true; - bool requireLiteBackendCapability = true; - bool requirePersistedSettingsLoaded = true; - bool requirePersistedSelectionIntent = true; - bool requireSelectedServerDisplay = true; - bool requireLifecycleUiOwner = true; - bool requireOperationConfirmation = true; - bool requirePrivateDataRedaction = true; - bool requireSyncPlannerFeed = true; -}; - -struct LiteWalletSelectedServerDisplayReport { - bool ok = false; - LiteWalletSelectedServerDisplayStatus status = LiteWalletSelectedServerDisplayStatus::Hidden; - std::string label; - std::string url; - std::size_t serverIndex = 0; - bool customServer = false; - std::string message; -}; - -struct LiteWalletServerSelectionPersistencePlan { - bool ok = false; - bool settingsLoaded = false; - bool persistedSelectionIntentAccepted = false; - bool wouldPersistSelectedServer = false; - bool persistenceOwnerAccepted = false; - bool settingsWritten = false; - LiteServerSelectionMode selectionMode = LiteServerSelectionMode::Sticky; - std::string selectedServerUrl; - std::size_t selectedServerIndex = 0; - bool selectedServerCustom = false; - std::string error; -}; - -struct LiteWalletLifecycleUiRequestPlan { - bool ok = false; - LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew; - LiteWalletLifecyclePlan lifecyclePlan; - std::vector privateData; - bool privateInputsRedacted = true; - std::string requestSummary; - std::string error; -}; - -struct LiteWalletServerLifecycleReadinessIssueInfo { - LiteWalletServerLifecycleReadinessIssue issue = LiteWalletServerLifecycleReadinessIssue::FullNodeBuild; - std::string message; -}; - -struct LiteWalletServerLifecycleReadinessResult { - bool ok = false; - bool dryRunOnly = true; - bool noNetwork = true; - bool noBridgeCalls = true; - bool noServerHealthChecked = true; - bool noWalletExistsChecked = true; - bool noWalletCreated = true; - bool noWalletOpened = true; - bool noWalletRestored = true; - bool noSyncStarted = true; - bool noSyncStatusPolled = true; - bool noWalletPersistence = true; - bool noSettingsPersistence = true; - bool settingsWritten = false; - bool workerQueueEnqueued = false; - bool stateMutationAllowed = false; - bool stateMutated = false; - bool walletStateWritten = false; - bool walletReady = false; - - bool liteBuildAccepted = false; - bool backendCapabilityAccepted = false; - bool serverSelectionAccepted = false; - bool persistenceIntentAccepted = false; - bool selectedServerDisplayAccepted = false; - bool lifecycleUiOwnerAccepted = false; - bool requestAccepted = false; - bool privateDataRedactionAccepted = false; - bool syncPlannerFeedAccepted = false; - bool futureLifecycleCouldBeEnabled = false; - bool lifecycleReportCouldFeedSyncPlanners = false; - - LiteWalletServerLifecycleReadinessStatus status = LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild; - WalletCapabilities capabilities; - LiteServerSelectionResult selectedServer; - LiteWalletSelectedServerDisplayReport selectedServerDisplay; - LiteWalletServerSelectionPersistencePlan persistencePlan; - LiteWalletLifecycleUiRequestPlan requestPlan; - WalletBackendStatus lifecycleStatus; - std::vector issues; - std::string error; -}; - -const char* liteWalletServerLifecycleReadinessStatusName( - LiteWalletServerLifecycleReadinessStatus status); -const char* liteWalletServerLifecycleReadinessIssueName( - LiteWalletServerLifecycleReadinessIssue issue); -const char* liteWalletSelectedServerDisplayStatusName( - LiteWalletSelectedServerDisplayStatus status); - -LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness( - const LiteWalletServerLifecycleReadinessInput& input, - LiteWalletServerLifecycleReadinessOptions options = {}); - -} // namespace dragonx::wallet \ No newline at end of file diff --git a/src/wallet/lite_wallet_server_selection_adapter.cpp b/src/wallet/lite_wallet_server_selection_adapter.cpp index c8e2ddb..68f8fd6 100644 --- a/src/wallet/lite_wallet_server_selection_adapter.cpp +++ b/src/wallet/lite_wallet_server_selection_adapter.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include namespace dragonx { namespace wallet { @@ -46,146 +44,6 @@ config::Settings::LiteServerSelectionPreferenceMode settingsModeFromWallet( return config::Settings::LiteServerSelectionPreferenceMode::Sticky; } -void addIssue(std::vector& issues, - LiteWalletServerSelectionUiExecutionIssue issue, - std::string message) -{ - issues.push_back(LiteWalletServerSelectionUiExecutionIssueInfo{issue, std::move(message)}); -} - -LiteWalletServerSelectionUiExecutionStatus statusFromLifecycle( - LiteWalletServerLifecycleReadinessStatus status) -{ - switch (status) { - case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: - return LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild; - case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings; - case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection; - case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus; - case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi; - case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction; - case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: - return LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed; - case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: - return LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled; - } - return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings; -} - -LiteWalletServerSelectionUiExecutionIssue issueFromLifecycle( - LiteWalletServerLifecycleReadinessIssue issue) -{ - switch (issue) { - case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: - return LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild; - case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: - return LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing; - case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: - case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: - return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded; - case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: - return LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing; - case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: - return LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing; - case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: - return LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing; - case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: - case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: - case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: - case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: - return LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing; - case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: - return LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing; - case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: - return LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing; - case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: - return LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested; - } - return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded; -} - -void copyLifecycleIssues(LiteWalletServerSelectionUiExecutionResult& result) -{ - for (const auto& issue : result.lifecycleReadiness.issues) { - addIssue(result.issues, issueFromLifecycle(issue.issue), issue.message); - } -} - -LiteWalletServerSelectionUiExecutionResult stoppedResult( - LiteWalletServerSelectionUiExecutionResult result, - LiteWalletServerSelectionUiExecutionStatus status, - LiteWalletServerSelectionUiExecutionIssue issue, - const std::string& message) -{ - result.status = status; - result.error = message; - result.ok = false; - addIssue(result.issues, issue, message); - return result; -} - -std::string buildDiagnosticSummary(const LiteWalletServerSelectionUiExecutionResult& result) -{ - std::ostringstream out; - out << "mode=" << liteServerSelectionModeName(result.connectionSettings.selectionMode) - << ";server=" << result.selectedServerUrlRedacted - << ";settings_written=" << (result.settingsWritten ? "true" : "false") - << ";network=false;bridge=false;lifecycle=false;sync=false;wallet_state=false"; - return out.str(); -} - -LiteConnectionSettings settingsWithIntent(const LiteConnectionSettings& current, - const LiteWalletServerSelectionUiIntent& intent) -{ - LiteConnectionSettings settings = current; - if (!intent.selectedServerIntentProvided) return settings; - - settings.selectionMode = intent.selectionMode; - if (!intent.chainName.empty()) settings.chainName = trimCopy(intent.chainName); - if (intent.replaceServers) settings.servers = intent.servers; - - switch (intent.selectionMode) { - case LiteServerSelectionMode::Sticky: - settings.stickyServerUrl = trimCopy(intent.selectedServerUrl); - break; - case LiteServerSelectionMode::Random: - settings.randomSelectionSeed = intent.randomSelectionSeed; - break; - } - return settings; -} - -LiteWalletServerLifecycleReadinessInput makeLifecycleInput( - const LiteWalletServerSelectionUiExecutionInput& input, - const LiteConnectionSettings& connectionSettings) -{ - LiteWalletServerLifecycleReadinessInput lifecycle; - lifecycle.capabilities = input.capabilities; - lifecycle.settings = connectionSettings; - lifecycle.operation = input.operation; - lifecycle.persistence.settingsLoaded = input.persistence.settingsLoaded; - lifecycle.persistence.havePersistedSelectionIntent = - input.persistence.havePersistedSelectionIntent || input.intent.selectedServerIntentProvided; - lifecycle.persistence.persistSelectedServer = input.persistence.persistSelectedServer; - lifecycle.persistence.persistenceOwnerReady = input.persistence.persistenceOwnerReady; - lifecycle.ui = input.ui; - lifecycle.ui.realLifecycleExecutionRequested = - lifecycle.ui.realLifecycleExecutionRequested || input.walletLifecycleExecutionRequested; - lifecycle.createRequest = input.createRequest; - lifecycle.openRequest = input.openRequest; - lifecycle.restoreRequest = input.restoreRequest; - return lifecycle; -} - } // namespace LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Settings& settings) @@ -229,82 +87,6 @@ void applyLiteConnectionSettingsToAppSettings(config::Settings& settings, settings.setLiteServers(servers); } -const char* liteWalletServerSelectionUiExecutionStatusName( - LiteWalletServerSelectionUiExecutionStatus status) -{ - switch (status) { - case LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle: - return "ReadyForFutureLifecycle"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild: - return "WaitingForLiteBuild"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability: - return "WaitingForBackendCapability"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings: - return "WaitingForSettings"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection: - return "WaitingForServerSelection"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner: - return "WaitingForPersistenceOwner"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus: - return "WaitingForDisplayStatus"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi: - return "WaitingForLifecycleUi"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction: - return "WaitingForPrivateDataRedaction"; - case LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed: - return "WaitingForSyncPlannerFeed"; - case LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed: - return "SettingsSaveFailed"; - case LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled: - return "RuntimeExecutionDisabled"; - } - return "WaitingForSettings"; -} - -const char* liteWalletServerSelectionUiExecutionIssueName( - LiteWalletServerSelectionUiExecutionIssue issue) -{ - switch (issue) { - case LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild: - return "FullNodeBuild"; - case LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing: - return "LiteBackendCapabilityMissing"; - case LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded: - return "SettingsNotLoaded"; - case LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing: - return "ServerSelectionMissing"; - case LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing: - return "ServerPersistenceOwnerMissing"; - case LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing: - return "SelectedServerDisplayMissing"; - case LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing: - return "LifecycleUiOwnerMissing"; - case LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing: - return "PrivateDataRedactionMissing"; - case LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing: - return "SyncPlannerFeedMissing"; - case LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed: - return "SettingsSaveFailed"; - case LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested: - return "ServerConnectivityCheckRequested"; - case LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested: - return "WalletLifecycleExecutionRequested"; - case LiteWalletServerSelectionUiExecutionIssue::SyncRequested: - return "SyncRequested"; - case LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested: - return "SyncStatusPollingRequested"; - case LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested: - return "WorkerQueueRequested"; - case LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested: - return "WalletStateMutationRequested"; - case LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested: - return "WalletFilePersistenceRequested"; - case LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested: - return "SendImportExportRequested"; - } - return "SettingsNotLoaded"; -} - std::string redactLiteServerSelectionValue(const std::string& value) { const std::string trimmed = trimCopy(value); @@ -314,138 +96,5 @@ std::string redactLiteServerSelectionValue(const std::string& value) return trimmed.substr(0, scheme + 3) + ""; } -LiteWalletServerSelectionUiExecutionResult executeLiteWalletServerSelectionUi( - config::Settings& settings, - const LiteWalletServerSelectionUiExecutionInput& input) -{ - LiteWalletServerSelectionUiExecutionResult result; - - auto stopForRuntime = [&](LiteWalletServerSelectionUiExecutionIssue issue, - const std::string& message) { - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled, - issue, - message); - }; - - if (input.serverConnectivityCheckRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested, - "lite server selection settings adapter cannot check server connectivity"); - } - if (input.walletExistsCheckRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested, - "lite server selection settings adapter cannot check wallet existence"); - } - if (input.walletLifecycleExecutionRequested || input.ui.realLifecycleExecutionRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested, - "lite server selection settings adapter cannot execute wallet lifecycle actions"); - } - if (input.syncStartRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncRequested, - "lite server selection settings adapter cannot start sync"); - } - if (input.syncStatusPollingRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested, - "lite server selection settings adapter cannot poll syncstatus"); - } - if (input.workerQueueEnqueueRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested, - "lite server selection settings adapter cannot enqueue wallet workers"); - } - if (input.walletStateMutationRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested, - "lite server selection settings adapter cannot mutate WalletState"); - } - if (input.walletFilePersistenceRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested, - "lite server selection settings adapter cannot persist wallet files"); - } - if (input.sendImportExportExecutionRequested) { - return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested, - "lite server selection settings adapter cannot execute send/import/export flows"); - } - - if (!input.persistence.settingsLoaded) { - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings, - LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded, - "lite server settings are not loaded"); - } - - result.connectionSettings = settingsWithIntent( - liteConnectionSettingsFromAppSettings(settings), input.intent); - if (input.intent.selectedServerIntentProvided && - input.intent.selectionMode == LiteServerSelectionMode::Sticky && - !isLiteServerUrlUsable(trimCopy(input.intent.selectedServerUrl))) { - result.selectedServerUrlRedacted = redactLiteServerSelectionValue(input.intent.selectedServerUrl); - result.selectedServerRedacted = true; - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection, - LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing, - "lite server selection URL is not usable"); - } - result.selectedServer = selectLiteServer(result.connectionSettings); - result.selectedServerUrlRedacted = result.selectedServer.ok - ? redactLiteServerSelectionValue(result.selectedServer.server.url) - : redactLiteServerSelectionValue(result.connectionSettings.stickyServerUrl); - result.selectedServerRedacted = true; - - if (!result.selectedServer.ok) { - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection, - LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing, - result.selectedServer.error.empty() - ? "lite server selection is missing" - : result.selectedServer.error); - } - result.selectedServerIntentAccepted = true; - - if ((input.persistence.persistSelectedServer || input.persistence.writeSettings) && - !input.persistence.persistenceOwnerReady) { - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner, - LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing, - "lite server settings persistence owner is not ready"); - } - result.settingsPersistenceAccepted = true; - - if (input.persistence.persistSelectedServer || input.persistence.writeSettings) { - applyLiteConnectionSettingsToAppSettings(settings, result.connectionSettings); - settings.setLitePersistSelectedServer(input.persistence.persistSelectedServer); - result.settingsMutated = true; - } - - if (input.persistence.writeSettings) { - result.noSettingsPersistence = false; - const bool saved = input.persistence.settingsSavePath.empty() - ? settings.save() - : settings.save(input.persistence.settingsSavePath); - if (!saved) { - return stoppedResult(std::move(result), - LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed, - LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed, - "failed to save lite server settings"); - } - result.settingsWritten = true; - } - - result.lifecycleInput = makeLifecycleInput(input, result.connectionSettings); - result.lifecycleReadiness = evaluateLiteWalletServerLifecycleReadiness(result.lifecycleInput); - result.lifecycleReadinessAccepted = result.lifecycleReadiness.ok; - result.lifecycleReportCouldFeedSyncPlanners = result.lifecycleReadiness.lifecycleReportCouldFeedSyncPlanners; - result.status = statusFromLifecycle(result.lifecycleReadiness.status); - copyLifecycleIssues(result); - result.diagnosticSummary = buildDiagnosticSummary(result); - - if (input.requireLifecycleReadiness && !result.lifecycleReadiness.ok) { - result.ok = false; - result.error = result.lifecycleReadiness.error; - return result; - } - - result.ok = true; - return result; -} - } // namespace wallet -} // namespace dragonx \ No newline at end of file +} // namespace dragonx diff --git a/src/wallet/lite_wallet_server_selection_adapter.h b/src/wallet/lite_wallet_server_selection_adapter.h index dd25ab7..e452c76 100644 --- a/src/wallet/lite_wallet_server_selection_adapter.h +++ b/src/wallet/lite_wallet_server_selection_adapter.h @@ -1,150 +1,19 @@ #pragma once #include "lite_connection_service.h" -#include "lite_wallet_server_lifecycle_readiness.h" #include "wallet_capabilities.h" #include "../config/settings.h" -#include #include -#include namespace dragonx { namespace wallet { -enum class LiteWalletServerSelectionUiExecutionStatus { - ReadyForFutureLifecycle, - WaitingForLiteBuild, - WaitingForBackendCapability, - WaitingForSettings, - WaitingForServerSelection, - WaitingForPersistenceOwner, - WaitingForDisplayStatus, - WaitingForLifecycleUi, - WaitingForPrivateDataRedaction, - WaitingForSyncPlannerFeed, - SettingsSaveFailed, - RuntimeExecutionDisabled -}; - -enum class LiteWalletServerSelectionUiExecutionIssue { - FullNodeBuild, - LiteBackendCapabilityMissing, - SettingsNotLoaded, - ServerSelectionMissing, - ServerPersistenceOwnerMissing, - SelectedServerDisplayMissing, - LifecycleUiOwnerMissing, - PrivateDataRedactionMissing, - SyncPlannerFeedMissing, - SettingsSaveFailed, - ServerConnectivityCheckRequested, - WalletLifecycleExecutionRequested, - SyncRequested, - SyncStatusPollingRequested, - WorkerQueueRequested, - WalletStateMutationRequested, - WalletFilePersistenceRequested, - SendImportExportRequested -}; - -struct LiteWalletServerSelectionUiIntent { - bool selectedServerIntentProvided = false; - LiteServerSelectionMode selectionMode = LiteServerSelectionMode::Sticky; - std::string selectedServerUrl; - std::size_t randomSelectionSeed = 0; - std::string chainName; - bool replaceServers = false; - std::vector servers; -}; - -struct LiteWalletServerSelectionPersistenceExecutionInput { - bool settingsLoaded = true; - bool havePersistedSelectionIntent = true; - bool persistSelectedServer = true; - bool persistenceOwnerReady = true; - bool writeSettings = false; - std::string settingsSavePath; -}; - -struct LiteWalletServerSelectionUiExecutionInput { - WalletCapabilities capabilities; - LiteWalletServerSelectionUiIntent intent; - LiteWalletServerSelectionPersistenceExecutionInput persistence; - LiteWalletLifecycleUiPrerequisites ui; - LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew; - LiteWalletCreateRequest createRequest; - LiteWalletOpenRequest openRequest; - LiteWalletRestoreRequest restoreRequest; - bool requireLifecycleReadiness = false; - - bool serverConnectivityCheckRequested = false; - bool walletExistsCheckRequested = false; - bool walletLifecycleExecutionRequested = false; - bool syncStartRequested = false; - bool syncStatusPollingRequested = false; - bool workerQueueEnqueueRequested = false; - bool walletStateMutationRequested = false; - bool walletFilePersistenceRequested = false; - bool sendImportExportExecutionRequested = false; -}; - -struct LiteWalletServerSelectionUiExecutionIssueInfo { - LiteWalletServerSelectionUiExecutionIssue issue = LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded; - std::string message; -}; - -struct LiteWalletServerSelectionUiExecutionResult { - bool ok = false; - bool dryRunOnly = true; - bool noNetwork = true; - bool noBridgeCalls = true; - bool noServerHealthChecked = true; - bool noWalletExistsChecked = true; - bool noWalletCreated = true; - bool noWalletOpened = true; - bool noWalletRestored = true; - bool noSyncStarted = true; - bool noSyncStatusPolled = true; - bool noWalletPersistence = true; - bool noSendImportExportExecution = true; - bool workerQueueEnqueued = false; - bool stateMutationAllowed = false; - bool stateMutated = false; - bool walletStateWritten = false; - bool settingsMutationAllowed = true; - bool settingsMutated = false; - bool noSettingsPersistence = true; - bool settingsWritten = false; - bool settingsPersistenceAccepted = false; - bool selectedServerIntentAccepted = false; - bool lifecycleReadinessAccepted = false; - bool lifecycleReportCouldFeedSyncPlanners = false; - bool selectedServerRedacted = false; - LiteWalletServerSelectionUiExecutionStatus status = LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings; - std::string error; - std::string selectedServerUrlRedacted; - std::string diagnosticSummary; - LiteConnectionSettings connectionSettings; - LiteServerSelectionResult selectedServer; - LiteWalletServerLifecycleReadinessInput lifecycleInput; - LiteWalletServerLifecycleReadinessResult lifecycleReadiness; - std::vector issues; -}; - LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Settings& settings); void applyLiteConnectionSettingsToAppSettings(config::Settings& settings, const LiteConnectionSettings& connectionSettings); -const char* liteWalletServerSelectionUiExecutionStatusName( - LiteWalletServerSelectionUiExecutionStatus status); -const char* liteWalletServerSelectionUiExecutionIssueName( - LiteWalletServerSelectionUiExecutionIssue issue); std::string redactLiteServerSelectionValue(const std::string& value); -LiteWalletServerSelectionUiExecutionResult executeLiteWalletServerSelectionUi( - config::Settings& settings, - const LiteWalletServerSelectionUiExecutionInput& input); - } // namespace wallet -} // namespace dragonx \ No newline at end of file +} // namespace dragonx diff --git a/tools/hushchat_fixture_check.cpp b/tools/hushchat_fixture_check.cpp index 5202a35..1823d2e 100644 --- a/tools/hushchat_fixture_check.cpp +++ b/tools/hushchat_fixture_check.cpp @@ -1,4 +1,4 @@ -#include "chat/chat_protocol.h" +#include "chat/chat_fixture_tooling.h" #include #include