@onekeyfe/hd-core 1.2.0-alpha.65 → 1.2.0-alpha.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
- package/__tests__/DeviceCommands.test.ts +59 -13
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-settings.test.ts +39 -18
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +13 -1
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +11 -1
- package/__tests__/deviceUploadNft.test.ts +316 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +340 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
- package/__tests__/protocol-v2.test.ts +1506 -1097
- package/__tests__/protocolV2FileWrite.test.ts +152 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +13 -27
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +2 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +6 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +9 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +29 -6
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +2 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +243 -248
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3051 -3774
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -71
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +38 -21
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +59 -60
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -316
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +597 -1005
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +71 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
- package/src/api/device/DeviceSettings.ts +5 -4
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -125
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +202 -63
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +213 -111
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +113 -41
- package/src/data-manager/TransportManager.ts +6 -0
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +274 -49
- package/src/device/DeviceCommands.ts +31 -4
- package/src/device/DeviceConnector.ts +33 -13
- package/src/device/DevicePool.ts +21 -7
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +390 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +3 -5
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +6 -79
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +15 -3
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +60 -22
- package/src/utils/deviceFeaturesCompat.ts +5 -6
- package/src/utils/deviceFeaturesUtils.ts +14 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
DeviceSessionPinType,
|
|
5
5
|
DeviceSettingsPage,
|
|
6
6
|
DeviceType,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
10
11
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -61,6 +62,7 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
62
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
63
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
64
|
import { DevicePool } from '../src/device/DevicePool';
|
|
65
|
+
import { DEVICE } from '../src/events';
|
|
64
66
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
65
67
|
import {
|
|
66
68
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -74,11 +76,15 @@ import {
|
|
|
74
76
|
requestProtocolV2ProtocolInfo,
|
|
75
77
|
supportsProtocolV2Message,
|
|
76
78
|
} from '../src/protocols/protocol-v2/features';
|
|
79
|
+
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
77
80
|
import {
|
|
78
81
|
getProtocolV2WalletSession,
|
|
79
82
|
refreshProtocolV2DeviceStatus,
|
|
80
83
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
81
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
createProtocolV2UnlockContext,
|
|
86
|
+
runMethodWithUnlockPolicy,
|
|
87
|
+
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
82
88
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
83
89
|
import {
|
|
84
90
|
buildProtocolV1FeaturesPayload,
|
|
@@ -92,7 +98,6 @@ import {
|
|
|
92
98
|
getMethodVersionRange,
|
|
93
99
|
} from '../src/utils';
|
|
94
100
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
-
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
96
101
|
import {
|
|
97
102
|
getPassphraseState,
|
|
98
103
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -241,7 +246,7 @@ describe('UploadPortfolio', () => {
|
|
|
241
246
|
(method as any).device = device;
|
|
242
247
|
|
|
243
248
|
method.init();
|
|
244
|
-
await
|
|
249
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
245
250
|
|
|
246
251
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
247
252
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -417,14 +422,16 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
417
422
|
}
|
|
418
423
|
return d.features;
|
|
419
424
|
};
|
|
425
|
+
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
426
|
+
requestProtocolV2ProtocolInfo({
|
|
427
|
+
commands: d.getCommands?.() ?? d.commands,
|
|
428
|
+
timeoutMs,
|
|
429
|
+
});
|
|
420
430
|
d.probeProtocolV2RuntimeState ??= async (
|
|
421
431
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
422
432
|
timeoutMs?: number
|
|
423
433
|
) => {
|
|
424
|
-
const protocolInfo = await
|
|
425
|
-
commands: d.getCommands(),
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
434
|
+
const protocolInfo = await d.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
428
435
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
429
436
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
430
437
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -582,7 +589,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
582
589
|
} as any);
|
|
583
590
|
const protocolV2 = buildProtocolV2FeaturesPayload({
|
|
584
591
|
deviceInfo: {
|
|
585
|
-
hw: { serial_no: 'P2-001' },
|
|
592
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'P2-001' },
|
|
586
593
|
coprocessor: { bt_adv_name: 'Pro 2 BLE' },
|
|
587
594
|
},
|
|
588
595
|
deviceStatus: {
|
|
@@ -781,6 +788,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
781
788
|
if (request === 'DeviceStatusGet') {
|
|
782
789
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
783
790
|
}
|
|
791
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
792
|
+
return { type: 'Success', message: {} };
|
|
793
|
+
}
|
|
784
794
|
if (request === 'DeviceSessionGet') {
|
|
785
795
|
return {
|
|
786
796
|
type: 'DeviceSession',
|
|
@@ -805,7 +815,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
815
|
).resolves.toEqual({
|
|
806
816
|
passphraseState: 'state-1',
|
|
807
817
|
newSession: 'session-1',
|
|
808
|
-
unlockedAttachPin:
|
|
818
|
+
unlockedAttachPin: false,
|
|
809
819
|
resumed: false,
|
|
810
820
|
});
|
|
811
821
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -814,7 +824,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
814
824
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
815
825
|
type: DeviceSessionPinType.Main,
|
|
816
826
|
});
|
|
817
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
827
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
828
|
+
passphrase: '',
|
|
829
|
+
on_device: false,
|
|
830
|
+
});
|
|
831
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
832
|
+
seed_domains: [],
|
|
833
|
+
});
|
|
818
834
|
device.passphraseState = 'state-1';
|
|
819
835
|
expect(device.getInternalState()).toBe('session-1');
|
|
820
836
|
});
|
|
@@ -847,6 +863,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
847
863
|
},
|
|
848
864
|
};
|
|
849
865
|
}
|
|
866
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
850
867
|
if (request === 'DeviceSessionGet') {
|
|
851
868
|
return {
|
|
852
869
|
message: {
|
|
@@ -863,9 +880,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
880
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
864
881
|
|
|
865
882
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
866
|
-
expect(
|
|
883
|
+
expect(
|
|
884
|
+
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
885
|
+
).toHaveLength(1);
|
|
886
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
887
|
+
seed_domains: [],
|
|
888
|
+
});
|
|
867
889
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
868
890
|
session_id: 'standard-session',
|
|
891
|
+
btc_test_address: 'standard-state',
|
|
892
|
+
seed_domains: [],
|
|
869
893
|
});
|
|
870
894
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
871
895
|
passphraseState: 'standard-state',
|
|
@@ -907,6 +931,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
907
931
|
},
|
|
908
932
|
};
|
|
909
933
|
}
|
|
934
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
910
935
|
if (request === 'DeviceSessionAskPin') {
|
|
911
936
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
912
937
|
}
|
|
@@ -1001,6 +1026,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1001
1026
|
if (request === 'DeviceStatusGet') {
|
|
1002
1027
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1003
1028
|
}
|
|
1029
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1004
1030
|
if (request === 'DeviceSessionGet') {
|
|
1005
1031
|
sessionGetCalls += 1;
|
|
1006
1032
|
return {
|
|
@@ -1227,7 +1253,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1227
1253
|
passphrase: 'host hidden wallet',
|
|
1228
1254
|
on_device: false,
|
|
1229
1255
|
});
|
|
1230
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1256
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1257
|
+
seed_domains: [],
|
|
1258
|
+
});
|
|
1231
1259
|
});
|
|
1232
1260
|
|
|
1233
1261
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1335,6 +1363,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1335
1363
|
|
|
1336
1364
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1337
1365
|
session_id: 'session-a',
|
|
1366
|
+
btc_test_address: 'state-a',
|
|
1367
|
+
seed_domains: [],
|
|
1338
1368
|
});
|
|
1339
1369
|
expect(device.getInternalState()).toBe('session-b');
|
|
1340
1370
|
});
|
|
@@ -1402,7 +1432,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1402
1432
|
});
|
|
1403
1433
|
expect(typedCall.mock.calls).toEqual([
|
|
1404
1434
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1405
|
-
[
|
|
1435
|
+
[
|
|
1436
|
+
'DeviceSessionGet',
|
|
1437
|
+
'DeviceSession',
|
|
1438
|
+
{
|
|
1439
|
+
session_id: 'session-a',
|
|
1440
|
+
btc_test_address: 'state-a',
|
|
1441
|
+
seed_domains: [],
|
|
1442
|
+
},
|
|
1443
|
+
],
|
|
1406
1444
|
]);
|
|
1407
1445
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1408
1446
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1450,7 +1488,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1450
1488
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1451
1489
|
],
|
|
1452
1490
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1453
|
-
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1491
|
+
['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
|
|
1454
1492
|
]);
|
|
1455
1493
|
});
|
|
1456
1494
|
|
|
@@ -1553,6 +1591,73 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1591
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1592
|
});
|
|
1555
1593
|
|
|
1594
|
+
test.each([false, true])(
|
|
1595
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1596
|
+
async lockFails => {
|
|
1597
|
+
const deviceId = 'attach-mismatch-device';
|
|
1598
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1599
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1600
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1601
|
+
{
|
|
1602
|
+
status: {
|
|
1603
|
+
device_id: deviceId,
|
|
1604
|
+
unlocked: true,
|
|
1605
|
+
passphrase_enabled: true,
|
|
1606
|
+
attach_to_pin_enabled: true,
|
|
1607
|
+
unlocked_by_attach_to_pin: false,
|
|
1608
|
+
},
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
device.passphraseState = 'expected-state';
|
|
1612
|
+
|
|
1613
|
+
const typedCall = jest.fn((request: string) => {
|
|
1614
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1615
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1616
|
+
if (request === 'DeviceStatusGet') {
|
|
1617
|
+
return {
|
|
1618
|
+
message: {
|
|
1619
|
+
device_id: deviceId,
|
|
1620
|
+
unlocked: true,
|
|
1621
|
+
passphrase_enabled: true,
|
|
1622
|
+
attach_to_pin_enabled: true,
|
|
1623
|
+
unlocked_by_attach_to_pin: true,
|
|
1624
|
+
},
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (request === 'DeviceSessionGet') {
|
|
1628
|
+
return {
|
|
1629
|
+
message: {
|
|
1630
|
+
btc_test_address: 'wrong-attach-state',
|
|
1631
|
+
session_id: 'wrong-attach-session',
|
|
1632
|
+
},
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (request === 'LockDevice') {
|
|
1636
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1637
|
+
return { message: { message: 'Device locked' } };
|
|
1638
|
+
}
|
|
1639
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1640
|
+
});
|
|
1641
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1642
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1643
|
+
|
|
1644
|
+
await expect(
|
|
1645
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1646
|
+
).rejects.toMatchObject({
|
|
1647
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1651
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1652
|
+
btc_test_address: 'expected-state',
|
|
1653
|
+
seed_domains: [],
|
|
1654
|
+
});
|
|
1655
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1656
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1657
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1556
1661
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1662
|
const device = Device.fromDescriptor({
|
|
1558
1663
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1714,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1714
|
}),
|
|
1610
1715
|
{ cancelDeviceOnReject: false }
|
|
1611
1716
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1717
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1718
|
+
DeviceSessionPinType.AttachToPin,
|
|
1719
|
+
expect.objectContaining({
|
|
1720
|
+
emitUiEvent: false,
|
|
1721
|
+
interaction: expect.objectContaining({
|
|
1722
|
+
phase: 'pin',
|
|
1723
|
+
transition: 'start',
|
|
1724
|
+
protocol: 'V2',
|
|
1725
|
+
}),
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1613
1728
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1729
|
});
|
|
1615
1730
|
|
|
@@ -1893,12 +2008,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
2008
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
2009
|
});
|
|
1895
2010
|
|
|
1896
|
-
test('
|
|
2011
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
2012
|
const typedCall = jest
|
|
1898
2013
|
.fn()
|
|
1899
2014
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
2015
|
.mockResolvedValueOnce({
|
|
1903
2016
|
message: {
|
|
1904
2017
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2039,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2039
|
await expect(
|
|
1927
2040
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2041
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2042
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2043
|
+
btc_test_address: 'expected-state',
|
|
2044
|
+
seed_domains: [],
|
|
2045
|
+
});
|
|
2046
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2047
|
});
|
|
1940
2048
|
|
|
1941
2049
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,8 +2109,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2109
|
true
|
|
2002
2110
|
);
|
|
2003
2111
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
[
|
|
2005
|
-
|
|
2112
|
+
[
|
|
2113
|
+
'DeviceSessionGet',
|
|
2114
|
+
'DeviceSession',
|
|
2115
|
+
{
|
|
2116
|
+
session_id: 'session-pro2-app',
|
|
2117
|
+
btc_test_address: 'state-pro2-app',
|
|
2118
|
+
seed_domains: [],
|
|
2119
|
+
},
|
|
2120
|
+
],
|
|
2006
2121
|
]);
|
|
2007
2122
|
});
|
|
2008
2123
|
|
|
@@ -2191,7 +2306,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2191
2306
|
passphrase: 'host hidden wallet',
|
|
2192
2307
|
on_device: false,
|
|
2193
2308
|
});
|
|
2194
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2309
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2310
|
+
seed_domains: [],
|
|
2311
|
+
});
|
|
2195
2312
|
});
|
|
2196
2313
|
|
|
2197
2314
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2269,12 +2386,83 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2386
|
);
|
|
2270
2387
|
|
|
2271
2388
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2390
|
+
btc_test_address: 'expected-state',
|
|
2391
|
+
seed_domains: [],
|
|
2392
|
+
});
|
|
2393
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2394
|
passphrase: 'host hidden wallet',
|
|
2274
2395
|
on_device: false,
|
|
2275
2396
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2397
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2398
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2399
|
+
seed_domains: [],
|
|
2400
|
+
});
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
2404
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2405
|
+
const typedCall = jest.fn((request: string) => {
|
|
2406
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2407
|
+
return { message: { version: 2 } };
|
|
2408
|
+
}
|
|
2409
|
+
if (request === 'DeviceStatusGet') {
|
|
2410
|
+
return {
|
|
2411
|
+
message: {
|
|
2412
|
+
device_id: 'attach-wallet-device',
|
|
2413
|
+
unlocked: true,
|
|
2414
|
+
passphrase_enabled: true,
|
|
2415
|
+
attach_to_pin_enabled: true,
|
|
2416
|
+
unlocked_by_attach_to_pin: true,
|
|
2417
|
+
},
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
if (request === 'DeviceSessionAskPin' || request === 'DeviceSessionAskPassphrase') {
|
|
2421
|
+
return { message: { message: 'Wallet selected' } };
|
|
2422
|
+
}
|
|
2423
|
+
if (request === 'DeviceSessionGet') {
|
|
2424
|
+
return {
|
|
2425
|
+
message: {
|
|
2426
|
+
session_id: 'main-wallet-session',
|
|
2427
|
+
btc_test_address: 'main-wallet-state',
|
|
2428
|
+
},
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
if (request === 'LockDevice') {
|
|
2432
|
+
return { message: { message: 'Device locked' } };
|
|
2433
|
+
}
|
|
2434
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
2438
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2439
|
+
{
|
|
2440
|
+
status: {
|
|
2441
|
+
device_id: 'attach-wallet-device',
|
|
2442
|
+
unlocked: true,
|
|
2443
|
+
passphrase_enabled: true,
|
|
2444
|
+
attach_to_pin_enabled: true,
|
|
2445
|
+
// 模拟缓存滞后:设备状态刷新后才确认当前由 Attach PIN 解锁。
|
|
2446
|
+
unlocked_by_attach_to_pin: false,
|
|
2447
|
+
},
|
|
2448
|
+
}
|
|
2449
|
+
);
|
|
2450
|
+
(device as any).commands = { typedCall };
|
|
2451
|
+
const clearInternalState = jest.spyOn(device, 'clearInternalState');
|
|
2452
|
+
|
|
2453
|
+
await expect(device.checkPassphraseStateSafety(undefined, true, false)).rejects.toMatchObject({
|
|
2454
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2458
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
2459
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2460
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2461
|
+
'DeviceSessionAskPassphrase',
|
|
2462
|
+
'Success',
|
|
2463
|
+
expect.anything()
|
|
2464
|
+
);
|
|
2465
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
2278
2466
|
});
|
|
2279
2467
|
|
|
2280
2468
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2307,10 +2495,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2307
2495
|
await expect(
|
|
2308
2496
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2309
2497
|
).resolves.toBe(true);
|
|
2310
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2498
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
2311
2499
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2500
|
eventless_wallet_session: true,
|
|
2313
2501
|
});
|
|
2502
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2503
|
+
seed_domains: [],
|
|
2504
|
+
});
|
|
2505
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2506
|
+
passphrase: '',
|
|
2507
|
+
on_device: false,
|
|
2508
|
+
});
|
|
2509
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2510
|
+
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2511
|
+
2
|
|
2512
|
+
);
|
|
2513
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2314
2514
|
});
|
|
2315
2515
|
|
|
2316
2516
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2381,7 +2581,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2381
2581
|
uuid: 'CACHED-SERIAL',
|
|
2382
2582
|
deviceId: null,
|
|
2383
2583
|
bleName: 'Cached BLE',
|
|
2384
|
-
name: 'Cached
|
|
2584
|
+
name: 'Cached BLE',
|
|
2385
2585
|
label: 'Cached Label',
|
|
2386
2586
|
deviceType: 'pro2',
|
|
2387
2587
|
});
|
|
@@ -2408,7 +2608,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2408
2608
|
expect(device.features?.label).toBeNull();
|
|
2409
2609
|
});
|
|
2410
2610
|
|
|
2411
|
-
test('
|
|
2611
|
+
test('uses the BLE name for discovery while preserving the Protocol V2 label', () => {
|
|
2412
2612
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2413
2613
|
(device as any).features = {
|
|
2414
2614
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
@@ -2416,9 +2616,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2416
2616
|
bleName: 'Pro2 6136',
|
|
2417
2617
|
};
|
|
2418
2618
|
|
|
2619
|
+
expect(device.getCurrentDisplayName()).toBe('My Pro 2');
|
|
2419
2620
|
expect(device.toMessageObject()).toMatchObject({
|
|
2420
2621
|
bleName: 'Pro2 6136',
|
|
2421
|
-
name: '
|
|
2622
|
+
name: 'Pro2 6136',
|
|
2422
2623
|
label: 'My Pro 2',
|
|
2423
2624
|
});
|
|
2424
2625
|
});
|
|
@@ -2472,10 +2673,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2472
2673
|
hw: true,
|
|
2473
2674
|
fw: true,
|
|
2474
2675
|
coprocessor: true,
|
|
2475
|
-
se1: true,
|
|
2476
|
-
se2: true,
|
|
2477
|
-
se3: true,
|
|
2478
|
-
se4: true,
|
|
2479
2676
|
},
|
|
2480
2677
|
types: {
|
|
2481
2678
|
version: true,
|
|
@@ -2499,8 +2696,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2499
2696
|
).rejects.toThrow('DeviceInfo not supported');
|
|
2500
2697
|
});
|
|
2501
2698
|
|
|
2502
|
-
test('does not inherit Pro or
|
|
2503
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2699
|
+
test('does not inherit Pro or model_touch ranges for Protocol V2 devices', () => {
|
|
2700
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2701
|
+
hw: { Device_type: DeviceType.PRO2 },
|
|
2702
|
+
});
|
|
2504
2703
|
const checkedTypes: string[] = [];
|
|
2505
2704
|
|
|
2506
2705
|
const versionRange = getMethodVersionRange(features, type => {
|
|
@@ -2512,11 +2711,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2512
2711
|
});
|
|
2513
2712
|
|
|
2514
2713
|
expect(versionRange).toBeUndefined();
|
|
2515
|
-
expect(checkedTypes).toEqual(['pro2']);
|
|
2714
|
+
expect(checkedTypes).toEqual(['pro2', 'model_pro2']);
|
|
2516
2715
|
});
|
|
2517
2716
|
|
|
2518
2717
|
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
2519
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2718
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2719
|
+
hw: { Device_type: DeviceType.PRO2 },
|
|
2720
|
+
});
|
|
2520
2721
|
|
|
2521
2722
|
expect(
|
|
2522
2723
|
getFirmwareUpdateField({
|
|
@@ -2667,10 +2868,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2667
2868
|
hw: true,
|
|
2668
2869
|
fw: true,
|
|
2669
2870
|
coprocessor: true,
|
|
2670
|
-
se1: true,
|
|
2671
|
-
se2: true,
|
|
2672
|
-
se3: true,
|
|
2673
|
-
se4: true,
|
|
2674
2871
|
},
|
|
2675
2872
|
types: {
|
|
2676
2873
|
version: true,
|
|
@@ -2681,7 +2878,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2878
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2879
|
}
|
|
2683
2880
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2881
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2882
|
+
eventless_wallet_session: true,
|
|
2883
|
+
});
|
|
2685
2884
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2885
|
});
|
|
2687
2886
|
|
|
@@ -2719,7 +2918,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2918
|
}),
|
|
2720
2919
|
expect.anything()
|
|
2721
2920
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2921
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2922
|
+
eventless_wallet_session: true,
|
|
2923
|
+
});
|
|
2723
2924
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2925
|
expect(device.features).toMatchObject({
|
|
2725
2926
|
deviceId: null,
|
|
@@ -2985,10 +3186,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3186
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3187
|
},
|
|
2987
3188
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3189
|
.mockResolvedValueOnce({
|
|
2993
3190
|
type: 'DeviceStatus',
|
|
2994
3191
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3207,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3207
|
firmwareVersion: '1.2.4',
|
|
3011
3208
|
passphraseProtection: null,
|
|
3012
3209
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3210
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3211
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3212
|
4,
|
|
3016
3213
|
'DeviceInfoGet',
|
|
@@ -3020,10 +3217,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3020
3217
|
hw: true,
|
|
3021
3218
|
fw: true,
|
|
3022
3219
|
coprocessor: true,
|
|
3023
|
-
se1: true,
|
|
3024
|
-
se2: true,
|
|
3025
|
-
se3: true,
|
|
3026
|
-
se4: true,
|
|
3027
3220
|
},
|
|
3028
3221
|
types: {
|
|
3029
3222
|
version: true,
|
|
@@ -3124,9 +3317,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3317
|
status: { init_states: true },
|
|
3125
3318
|
}
|
|
3126
3319
|
);
|
|
3320
|
+
const pinOnDevice = jest.fn();
|
|
3321
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3322
|
|
|
3128
3323
|
const features = await device.unlockDevice();
|
|
3129
3324
|
|
|
3325
|
+
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3326
|
+
device,
|
|
3327
|
+
DeviceSessionPinType.Main,
|
|
3328
|
+
expect.objectContaining({
|
|
3329
|
+
source: 'unlock-coordinator',
|
|
3330
|
+
reason: 'device-unlock',
|
|
3331
|
+
deviceOnly: true,
|
|
3332
|
+
})
|
|
3333
|
+
);
|
|
3334
|
+
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3335
|
+
typedCall.mock.invocationCallOrder[0]
|
|
3336
|
+
);
|
|
3130
3337
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3338
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3339
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3379,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3379
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3380
|
});
|
|
3174
3381
|
(device as any).commands = { typedCall };
|
|
3382
|
+
const pinOnDevice = jest.fn();
|
|
3383
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3384
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3385
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3386
|
|
|
3387
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3388
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3389
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3390
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3417,71 +3627,77 @@ describe('API compatibility handling', () => {
|
|
|
3417
3627
|
});
|
|
3418
3628
|
});
|
|
3419
3629
|
|
|
3420
|
-
test('
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3630
|
+
test.each(['pro2', 'neo'] as const)(
|
|
3631
|
+
'allows %s Solana signing methods through Protocol V2 version checks',
|
|
3632
|
+
deviceType => {
|
|
3633
|
+
const features = {
|
|
3634
|
+
deviceType,
|
|
3635
|
+
} as Features;
|
|
3424
3636
|
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3637
|
+
const solSignMessageMethod = new SolSignMessage({
|
|
3638
|
+
id: 1,
|
|
3639
|
+
payload: {
|
|
3640
|
+
method: 'solSignMessage',
|
|
3641
|
+
path: "m/44'/501'/0'/0'",
|
|
3642
|
+
messageHex: '48656c6c6f',
|
|
3643
|
+
},
|
|
3644
|
+
});
|
|
3645
|
+
const solSignOffchainMessageMethod = new SolSignOffchainMessage({
|
|
3646
|
+
id: 2,
|
|
3647
|
+
payload: {
|
|
3648
|
+
method: 'solSignOffchainMessage',
|
|
3649
|
+
path: "m/44'/501'/0'/0'",
|
|
3650
|
+
messageHex: '48656c6c6f',
|
|
3651
|
+
},
|
|
3652
|
+
});
|
|
3653
|
+
const solSignTransactionMethod = new SolSignTransaction({
|
|
3654
|
+
id: 3,
|
|
3655
|
+
payload: {
|
|
3656
|
+
method: 'solSignTransaction',
|
|
3657
|
+
path: "m/44'/501'/0'/0'",
|
|
3658
|
+
rawTx: '00',
|
|
3659
|
+
},
|
|
3660
|
+
});
|
|
3661
|
+
const solSignVersionedTransactionMethod = new SolSignTransaction({
|
|
3662
|
+
id: 4,
|
|
3663
|
+
payload: {
|
|
3664
|
+
method: 'solSignTransaction',
|
|
3665
|
+
path: "m/44'/501'/0'/0'",
|
|
3666
|
+
rawTx: '80',
|
|
3667
|
+
},
|
|
3668
|
+
});
|
|
3457
3669
|
|
|
3458
|
-
|
|
3459
|
-
|
|
3670
|
+
solSignTransactionMethod.init();
|
|
3671
|
+
solSignVersionedTransactionMethod.init();
|
|
3460
3672
|
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3673
|
+
expect(
|
|
3674
|
+
getMethodVersionRange(features, type => solSignMessageMethod.getVersionRange()[type])
|
|
3675
|
+
).toEqual({
|
|
3676
|
+
min: '0.0.0',
|
|
3677
|
+
});
|
|
3678
|
+
expect(
|
|
3679
|
+
getMethodVersionRange(
|
|
3680
|
+
features,
|
|
3681
|
+
type => solSignOffchainMessageMethod.getVersionRange()[type]
|
|
3682
|
+
)
|
|
3683
|
+
).toEqual({
|
|
3684
|
+
min: '0.0.0',
|
|
3685
|
+
});
|
|
3686
|
+
expect(
|
|
3687
|
+
getMethodVersionRange(features, type => solSignTransactionMethod.getVersionRange()[type])
|
|
3688
|
+
).toEqual({
|
|
3689
|
+
min: '0.0.0',
|
|
3690
|
+
});
|
|
3691
|
+
expect(
|
|
3692
|
+
getMethodVersionRange(
|
|
3693
|
+
features,
|
|
3694
|
+
type => solSignVersionedTransactionMethod.getVersionRange()[type]
|
|
3695
|
+
)
|
|
3696
|
+
).toEqual({
|
|
3697
|
+
min: '0.0.0',
|
|
3698
|
+
});
|
|
3699
|
+
}
|
|
3700
|
+
);
|
|
3485
3701
|
|
|
3486
3702
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
3487
3703
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
@@ -3577,6 +3793,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3793
|
|
|
3578
3794
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3795
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3796
|
+
let forceReloadDataSpy: jest.SpyInstance;
|
|
3797
|
+
|
|
3798
|
+
beforeEach(() => {
|
|
3799
|
+
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3800
|
+
});
|
|
3801
|
+
|
|
3802
|
+
afterEach(() => {
|
|
3803
|
+
jest.restoreAllMocks();
|
|
3804
|
+
});
|
|
3580
3805
|
|
|
3581
3806
|
const buildOkppHeader = ({
|
|
3582
3807
|
type = 'RESC',
|
|
@@ -3701,7 +3926,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3701
3926
|
specific: true,
|
|
3702
3927
|
},
|
|
3703
3928
|
},
|
|
3704
|
-
{ timeoutMs:
|
|
3929
|
+
{ timeoutMs: 30000 }
|
|
3705
3930
|
);
|
|
3706
3931
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3707
3932
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3717,7 +3942,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3942
|
});
|
|
3718
3943
|
});
|
|
3719
3944
|
|
|
3720
|
-
test('
|
|
3945
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3946
|
const method = new FirmwareUpdateV4({
|
|
3722
3947
|
id: 1,
|
|
3723
3948
|
payload: {
|
|
@@ -3752,7 +3977,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3977
|
|
|
3753
3978
|
await method.run();
|
|
3754
3979
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3980
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3981
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3982
|
fwBinaryMap: [
|
|
3758
3983
|
{
|
|
@@ -3763,19 +3988,86 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3763
3988
|
],
|
|
3764
3989
|
bootloaderBinary: null,
|
|
3765
3990
|
});
|
|
3991
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3766
3992
|
});
|
|
3767
3993
|
|
|
3768
|
-
test('
|
|
3994
|
+
test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
|
|
3769
3995
|
const method = new FirmwareUpdateV4({
|
|
3770
3996
|
id: 1,
|
|
3771
3997
|
payload: {
|
|
3772
3998
|
method: 'firmwareUpdateV4',
|
|
3999
|
+
platform: 'web',
|
|
4000
|
+
targetsToUpdate: ['resource'],
|
|
3773
4001
|
},
|
|
3774
4002
|
});
|
|
4003
|
+
method.init();
|
|
4004
|
+
|
|
3775
4005
|
(method as any).device = stubDevice({
|
|
3776
|
-
originalDescriptor: { id: 'usb-id', path: '
|
|
3777
|
-
features: {
|
|
4006
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4007
|
+
features: {
|
|
4008
|
+
deviceType: 'pro2',
|
|
4009
|
+
firmwareVersion: '1.0.0',
|
|
4010
|
+
mode: 'normal',
|
|
4011
|
+
bootloaderMode: false,
|
|
4012
|
+
capabilities: [],
|
|
4013
|
+
},
|
|
3778
4014
|
isBootloader: () => false,
|
|
4015
|
+
isRomloader: () => false,
|
|
4016
|
+
});
|
|
4017
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4018
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4019
|
+
bootloaderBinary: null,
|
|
4020
|
+
fwBinaryMap: [],
|
|
4021
|
+
installItems: [],
|
|
4022
|
+
});
|
|
4023
|
+
|
|
4024
|
+
const order: string[] = [];
|
|
4025
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4026
|
+
order.push('enter-bootloader');
|
|
4027
|
+
return Promise.resolve(true);
|
|
4028
|
+
});
|
|
4029
|
+
(method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
|
|
4030
|
+
order.push('prepare-resources');
|
|
4031
|
+
return Promise.resolve([
|
|
4032
|
+
{
|
|
4033
|
+
name: 'images.okpkg',
|
|
4034
|
+
binary: new Uint8Array([1]).buffer,
|
|
4035
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
4036
|
+
},
|
|
4037
|
+
]);
|
|
4038
|
+
});
|
|
4039
|
+
(method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
|
|
4040
|
+
order.push('execute-update');
|
|
4041
|
+
return Promise.resolve();
|
|
4042
|
+
});
|
|
4043
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4044
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
4045
|
+
bootloaderVersion: '1.0.0',
|
|
4046
|
+
bleVersion: '1.0.0',
|
|
4047
|
+
firmwareVersion: '1.0.0',
|
|
4048
|
+
});
|
|
4049
|
+
method.postTipMessage = jest.fn();
|
|
4050
|
+
|
|
4051
|
+
await method.run();
|
|
4052
|
+
|
|
4053
|
+
expect(order).toEqual(['enter-bootloader', 'prepare-resources', 'execute-update']);
|
|
4054
|
+
});
|
|
4055
|
+
|
|
4056
|
+
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
4057
|
+
const method = new FirmwareUpdateV4({
|
|
4058
|
+
id: 1,
|
|
4059
|
+
payload: {
|
|
4060
|
+
method: 'firmwareUpdateV4',
|
|
4061
|
+
},
|
|
4062
|
+
});
|
|
4063
|
+
(method as any).device = stubDevice({
|
|
4064
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4065
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
4066
|
+
isBootloader: () => false,
|
|
4067
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4068
|
+
mode: 'bootloader',
|
|
4069
|
+
bootloaderMode: true,
|
|
4070
|
+
}),
|
|
3779
4071
|
});
|
|
3780
4072
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
3781
4073
|
message: 'Device rebooted successfully',
|
|
@@ -3803,6 +4095,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3803
4095
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3804
4096
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
3805
4097
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4098
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
3806
4099
|
method.postTipMessage = jest.fn();
|
|
3807
4100
|
|
|
3808
4101
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
@@ -3814,6 +4107,66 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3814
4107
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3815
4108
|
});
|
|
3816
4109
|
|
|
4110
|
+
test('announces bootloader success only after Protocol V2 bootloader mode is confirmed', async () => {
|
|
4111
|
+
const method = new FirmwareUpdateV4({
|
|
4112
|
+
id: 1,
|
|
4113
|
+
payload: {
|
|
4114
|
+
method: 'firmwareUpdateV4',
|
|
4115
|
+
},
|
|
4116
|
+
});
|
|
4117
|
+
(method as any).device = stubDevice({
|
|
4118
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
4119
|
+
isBootloader: () => false,
|
|
4120
|
+
isRomloader: () => false,
|
|
4121
|
+
});
|
|
4122
|
+
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4123
|
+
message: 'Device rebooted successfully',
|
|
4124
|
+
});
|
|
4125
|
+
|
|
4126
|
+
const tipMessages: string[] = [];
|
|
4127
|
+
method.postTipMessage = jest.fn((message: string) => {
|
|
4128
|
+
tipMessages.push(message);
|
|
4129
|
+
});
|
|
4130
|
+
let successWasAnnouncedBeforeConfirmation = false;
|
|
4131
|
+
(method as any).waitForProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4132
|
+
successWasAnnouncedBeforeConfirmation = tipMessages.includes('GoToBootloaderSuccess');
|
|
4133
|
+
return Promise.resolve({ mode: 'bootloader' });
|
|
4134
|
+
});
|
|
4135
|
+
|
|
4136
|
+
await (method as any).enterProtocolV2BootloaderMode();
|
|
4137
|
+
|
|
4138
|
+
expect(successWasAnnouncedBeforeConfirmation).toBe(false);
|
|
4139
|
+
expect(tipMessages).toEqual(['AutoRebootToBootloader', 'GoToBootloaderSuccess']);
|
|
4140
|
+
});
|
|
4141
|
+
|
|
4142
|
+
test('keeps Protocol V2 bootloader active before firmware transfer', async () => {
|
|
4143
|
+
const method = new FirmwareUpdateV4({
|
|
4144
|
+
id: 1,
|
|
4145
|
+
payload: {
|
|
4146
|
+
method: 'firmwareUpdateV4',
|
|
4147
|
+
},
|
|
4148
|
+
});
|
|
4149
|
+
(method as any).device = stubDevice({
|
|
4150
|
+
originalDescriptor: { id: 'usb-id', path: 'bootloader-path', protocolType: 'V2' },
|
|
4151
|
+
features: {
|
|
4152
|
+
deviceType: 'pro2',
|
|
4153
|
+
mode: 'bootloader',
|
|
4154
|
+
bootloaderMode: true,
|
|
4155
|
+
capabilities: [],
|
|
4156
|
+
},
|
|
4157
|
+
isBootloader: () => true,
|
|
4158
|
+
isRomloader: () => false,
|
|
4159
|
+
});
|
|
4160
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4161
|
+
method.postTipMessage = jest.fn();
|
|
4162
|
+
|
|
4163
|
+
await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
|
|
4164
|
+
|
|
4165
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4166
|
+
expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
4167
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
4168
|
+
});
|
|
4169
|
+
|
|
3817
4170
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
3818
4171
|
const method = new FirmwareUpdateV4({
|
|
3819
4172
|
id: 1,
|
|
@@ -3866,6 +4219,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
4219
|
},
|
|
3867
4220
|
});
|
|
3868
4221
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4222
|
+
const prepareResourceBundles = jest
|
|
4223
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4224
|
+
.mockResolvedValue(undefined);
|
|
3869
4225
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
4226
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
4227
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +4234,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
4234
|
|
|
3879
4235
|
await method.run();
|
|
3880
4236
|
|
|
4237
|
+
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
3881
4238
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
4239
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
4240
|
bootloaderBinary: null,
|
|
@@ -3897,6 +4254,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3897
4254
|
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
3898
4255
|
features: { bootloader_mode: false, capabilities: [] },
|
|
3899
4256
|
isBootloader: () => false,
|
|
4257
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4258
|
+
mode: 'bootloader',
|
|
4259
|
+
bootloaderMode: true,
|
|
4260
|
+
}),
|
|
3900
4261
|
});
|
|
3901
4262
|
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
3902
4263
|
if (requestType === 'DeviceInfoGet') {
|
|
@@ -3924,15 +4285,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3924
4285
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3925
4286
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
3926
4287
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4288
|
+
(method as any).protocolV2ExpectedPath = 'app-path';
|
|
3927
4289
|
|
|
3928
4290
|
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
3929
4291
|
|
|
3930
4292
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
3931
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3932
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3933
|
-
'DeviceInfoGet',
|
|
3934
|
-
'ProtocolInfoRequest',
|
|
3935
|
-
]);
|
|
4293
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4294
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
3936
4295
|
});
|
|
3937
4296
|
|
|
3938
4297
|
test('does not run generic initialize during Protocol V2 USB firmware reconnect', async () => {
|
|
@@ -3960,12 +4319,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3960
4319
|
getCommands: () => commands,
|
|
3961
4320
|
mainId: 'usb-path',
|
|
3962
4321
|
});
|
|
3963
|
-
const cachedDevice = {
|
|
4322
|
+
const cachedDevice = {
|
|
4323
|
+
getConnectId: () => 'usb-path',
|
|
4324
|
+
getCurrentSerialNo: () => '',
|
|
4325
|
+
};
|
|
3964
4326
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
3965
4327
|
devices: {},
|
|
3966
4328
|
deviceList: [cachedDevice],
|
|
3967
4329
|
} as any);
|
|
3968
4330
|
(method as any).device = device;
|
|
4331
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
3969
4332
|
|
|
3970
4333
|
try {
|
|
3971
4334
|
await (method as any).reconnectProtocolV2Device();
|
|
@@ -3977,6 +4340,184 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3977
4340
|
expect(initialize).not.toHaveBeenCalled();
|
|
3978
4341
|
});
|
|
3979
4342
|
|
|
4343
|
+
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
4344
|
+
const method = new FirmwareUpdateV4({
|
|
4345
|
+
id: 1,
|
|
4346
|
+
payload: {
|
|
4347
|
+
method: 'firmwareUpdateV4',
|
|
4348
|
+
},
|
|
4349
|
+
});
|
|
4350
|
+
const commands = { disposed: true, mainId: '' };
|
|
4351
|
+
const updateFromCache = jest.fn();
|
|
4352
|
+
const device = stubDevice({
|
|
4353
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4354
|
+
deviceConnector: {
|
|
4355
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4356
|
+
descriptors: [
|
|
4357
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4358
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4359
|
+
],
|
|
4360
|
+
}),
|
|
4361
|
+
},
|
|
4362
|
+
updateFromCache,
|
|
4363
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4364
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4365
|
+
commands,
|
|
4366
|
+
getCommands: () => commands,
|
|
4367
|
+
mainId: 'old-usb-path',
|
|
4368
|
+
});
|
|
4369
|
+
const otherDevice = {
|
|
4370
|
+
getConnectId: () => 'usb-a',
|
|
4371
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4372
|
+
};
|
|
4373
|
+
const expectedDevice = {
|
|
4374
|
+
getConnectId: () => 'usb-b',
|
|
4375
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4376
|
+
};
|
|
4377
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4378
|
+
devices: {},
|
|
4379
|
+
deviceList: [otherDevice, expectedDevice],
|
|
4380
|
+
} as any);
|
|
4381
|
+
(method as any).device = device;
|
|
4382
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4383
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4384
|
+
|
|
4385
|
+
try {
|
|
4386
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4387
|
+
} finally {
|
|
4388
|
+
getDevices.mockRestore();
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
expect(updateFromCache).toHaveBeenCalledWith(expectedDevice);
|
|
4392
|
+
});
|
|
4393
|
+
|
|
4394
|
+
test('rejects ambiguous Protocol V2 USB reconnect candidates without an identity match', async () => {
|
|
4395
|
+
const method = new FirmwareUpdateV4({
|
|
4396
|
+
id: 1,
|
|
4397
|
+
payload: {
|
|
4398
|
+
method: 'firmwareUpdateV4',
|
|
4399
|
+
},
|
|
4400
|
+
});
|
|
4401
|
+
const updateFromCache = jest.fn();
|
|
4402
|
+
const device = stubDevice({
|
|
4403
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4404
|
+
deviceConnector: {
|
|
4405
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4406
|
+
descriptors: [
|
|
4407
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4408
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4409
|
+
],
|
|
4410
|
+
}),
|
|
4411
|
+
},
|
|
4412
|
+
updateFromCache,
|
|
4413
|
+
});
|
|
4414
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4415
|
+
devices: {},
|
|
4416
|
+
deviceList: [
|
|
4417
|
+
{
|
|
4418
|
+
getConnectId: () => 'usb-a',
|
|
4419
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4420
|
+
},
|
|
4421
|
+
{
|
|
4422
|
+
getConnectId: () => 'usb-b',
|
|
4423
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4424
|
+
},
|
|
4425
|
+
],
|
|
4426
|
+
} as any);
|
|
4427
|
+
(method as any).device = device;
|
|
4428
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-C';
|
|
4429
|
+
|
|
4430
|
+
try {
|
|
4431
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4432
|
+
} finally {
|
|
4433
|
+
getDevices.mockRestore();
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4437
|
+
});
|
|
4438
|
+
|
|
4439
|
+
test('allows a Protocol V2 USB reconnect candidate without a serial only when its path matches', async () => {
|
|
4440
|
+
const method = new FirmwareUpdateV4({
|
|
4441
|
+
id: 1,
|
|
4442
|
+
payload: {
|
|
4443
|
+
method: 'firmwareUpdateV4',
|
|
4444
|
+
},
|
|
4445
|
+
});
|
|
4446
|
+
const commands = { disposed: true, mainId: '' };
|
|
4447
|
+
const updateFromCache = jest.fn();
|
|
4448
|
+
const device = stubDevice({
|
|
4449
|
+
originalDescriptor: { id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4450
|
+
deviceConnector: {
|
|
4451
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4452
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4453
|
+
}),
|
|
4454
|
+
},
|
|
4455
|
+
updateFromCache,
|
|
4456
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4457
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4458
|
+
commands,
|
|
4459
|
+
getCommands: () => commands,
|
|
4460
|
+
mainId: 'old-usb-path',
|
|
4461
|
+
});
|
|
4462
|
+
const candidate = {
|
|
4463
|
+
getConnectId: () => 'usb-a',
|
|
4464
|
+
getCurrentSerialNo: () => '',
|
|
4465
|
+
};
|
|
4466
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4467
|
+
devices: {},
|
|
4468
|
+
deviceList: [candidate],
|
|
4469
|
+
} as any);
|
|
4470
|
+
(method as any).device = device;
|
|
4471
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-A';
|
|
4472
|
+
(method as any).protocolV2ExpectedPath = 'usb-a';
|
|
4473
|
+
|
|
4474
|
+
try {
|
|
4475
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4476
|
+
} finally {
|
|
4477
|
+
getDevices.mockRestore();
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
expect(updateFromCache).toHaveBeenCalledWith(candidate);
|
|
4481
|
+
});
|
|
4482
|
+
|
|
4483
|
+
test('rejects a single Protocol V2 USB reconnect candidate with a different physical serial', async () => {
|
|
4484
|
+
const method = new FirmwareUpdateV4({
|
|
4485
|
+
id: 1,
|
|
4486
|
+
payload: {
|
|
4487
|
+
method: 'firmwareUpdateV4',
|
|
4488
|
+
},
|
|
4489
|
+
});
|
|
4490
|
+
const updateFromCache = jest.fn();
|
|
4491
|
+
const device = stubDevice({
|
|
4492
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4493
|
+
deviceConnector: {
|
|
4494
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4495
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4496
|
+
}),
|
|
4497
|
+
},
|
|
4498
|
+
updateFromCache,
|
|
4499
|
+
});
|
|
4500
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4501
|
+
devices: {},
|
|
4502
|
+
deviceList: [
|
|
4503
|
+
{
|
|
4504
|
+
getConnectId: () => 'usb-a',
|
|
4505
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4506
|
+
},
|
|
4507
|
+
],
|
|
4508
|
+
} as any);
|
|
4509
|
+
(method as any).device = device;
|
|
4510
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4511
|
+
|
|
4512
|
+
try {
|
|
4513
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4514
|
+
} finally {
|
|
4515
|
+
getDevices.mockRestore();
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4519
|
+
});
|
|
4520
|
+
|
|
3980
4521
|
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
3981
4522
|
const method = new FirmwareUpdateV4({
|
|
3982
4523
|
id: 1,
|
|
@@ -4018,6 +4559,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4018
4559
|
deviceList: [{ getConnectId: () => 'usb-path' }],
|
|
4019
4560
|
} as any);
|
|
4020
4561
|
(method as any).device = device;
|
|
4562
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4021
4563
|
|
|
4022
4564
|
try {
|
|
4023
4565
|
await (method as any).reconnectProtocolV2Device();
|
|
@@ -4048,6 +4590,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4048
4590
|
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
4049
4591
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4050
4592
|
(method as any).device = stubDevice({
|
|
4593
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4051
4594
|
getCommands: () => ({ typedCall }),
|
|
4052
4595
|
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4053
4596
|
deviceId: 'changed-device',
|
|
@@ -4071,7 +4614,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4071
4614
|
});
|
|
4072
4615
|
method.postTipMessage = jest.fn();
|
|
4073
4616
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4074
|
-
|
|
4617
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4618
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4619
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4075
4620
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4076
4621
|
message: 'Device rebooted successfully',
|
|
4077
4622
|
});
|
|
@@ -4080,9 +4625,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4080
4625
|
|
|
4081
4626
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4082
4627
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4628
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4083
4629
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4084
4630
|
});
|
|
4085
4631
|
|
|
4632
|
+
test('skips a second Normal reboot after Pro2 already returned to App mode', async () => {
|
|
4633
|
+
const method = new FirmwareUpdateV4({
|
|
4634
|
+
id: 1,
|
|
4635
|
+
payload: {
|
|
4636
|
+
method: 'firmwareUpdateV4',
|
|
4637
|
+
},
|
|
4638
|
+
});
|
|
4639
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4640
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4641
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4642
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4643
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4644
|
+
|
|
4645
|
+
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
4646
|
+
|
|
4647
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4648
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4649
|
+
});
|
|
4650
|
+
|
|
4086
4651
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4087
4652
|
const method = new FirmwareUpdateV4({
|
|
4088
4653
|
id: 1,
|
|
@@ -4125,29 +4690,50 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4125
4690
|
});
|
|
4126
4691
|
});
|
|
4127
4692
|
|
|
4128
|
-
test('
|
|
4693
|
+
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
4129
4694
|
const method = new FirmwareUpdateV4({
|
|
4130
4695
|
id: 1,
|
|
4131
4696
|
payload: {
|
|
4132
4697
|
method: 'firmwareUpdateV4',
|
|
4133
4698
|
},
|
|
4134
4699
|
});
|
|
4135
|
-
const typedCall = jest
|
|
4136
|
-
.fn()
|
|
4137
|
-
.mockRejectedValue(
|
|
4138
|
-
Object.assign(
|
|
4139
|
-
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4140
|
-
{ name: 'NotFoundError' }
|
|
4141
|
-
)
|
|
4142
|
-
);
|
|
4700
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
4143
4701
|
|
|
4144
4702
|
(method as any).device = stubDevice({
|
|
4145
4703
|
getCommands: () => ({ typedCall }),
|
|
4146
4704
|
});
|
|
4147
|
-
method.postTipMessage = jest.fn();
|
|
4148
|
-
method.postProgressMessage = jest.fn();
|
|
4149
4705
|
|
|
4150
|
-
await expect(
|
|
4706
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Bootloader)).resolves.toEqual({
|
|
4707
|
+
message: 'Device rebooted successfully',
|
|
4708
|
+
});
|
|
4709
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
4710
|
+
DeviceRebootType.Bootloader
|
|
4711
|
+
);
|
|
4712
|
+
});
|
|
4713
|
+
|
|
4714
|
+
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4715
|
+
const method = new FirmwareUpdateV4({
|
|
4716
|
+
id: 1,
|
|
4717
|
+
payload: {
|
|
4718
|
+
method: 'firmwareUpdateV4',
|
|
4719
|
+
},
|
|
4720
|
+
});
|
|
4721
|
+
const typedCall = jest
|
|
4722
|
+
.fn()
|
|
4723
|
+
.mockRejectedValue(
|
|
4724
|
+
new DOMException(
|
|
4725
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4726
|
+
'NotFoundError'
|
|
4727
|
+
)
|
|
4728
|
+
);
|
|
4729
|
+
|
|
4730
|
+
(method as any).device = stubDevice({
|
|
4731
|
+
getCommands: () => ({ typedCall }),
|
|
4732
|
+
});
|
|
4733
|
+
method.postTipMessage = jest.fn();
|
|
4734
|
+
method.postProgressMessage = jest.fn();
|
|
4735
|
+
|
|
4736
|
+
await expect(
|
|
4151
4737
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
4152
4738
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
4153
4739
|
})
|
|
@@ -4215,7 +4801,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4215
4801
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4216
4802
|
]);
|
|
4217
4803
|
|
|
4218
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4804
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
4219
4805
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4220
4806
|
'DeviceFirmwareUpdateStatusGet',
|
|
4221
4807
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4253,6 +4839,46 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4253
4839
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4254
4840
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4255
4841
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4842
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4843
|
+
});
|
|
4844
|
+
|
|
4845
|
+
test('accepts an empty firmware status dump only after confirming normal mode', async () => {
|
|
4846
|
+
const method = new FirmwareUpdateV4({
|
|
4847
|
+
id: 1,
|
|
4848
|
+
payload: {
|
|
4849
|
+
method: 'firmwareUpdateV4',
|
|
4850
|
+
},
|
|
4851
|
+
});
|
|
4852
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4853
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4854
|
+
message: { records: [] },
|
|
4855
|
+
});
|
|
4856
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4857
|
+
let now = 0;
|
|
4858
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4859
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4860
|
+
now += 31 * 1000;
|
|
4861
|
+
callback();
|
|
4862
|
+
return 0 as any;
|
|
4863
|
+
}) as typeof setTimeout);
|
|
4864
|
+
|
|
4865
|
+
(method as any).device = stubDevice({
|
|
4866
|
+
getCommands: () => ({ typedCall }),
|
|
4867
|
+
});
|
|
4868
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4869
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4870
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4871
|
+
method.postProgressMessage = jest.fn();
|
|
4872
|
+
|
|
4873
|
+
await expect(
|
|
4874
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4875
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4876
|
+
])
|
|
4877
|
+
).resolves.toBeUndefined();
|
|
4878
|
+
|
|
4879
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4880
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4881
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4256
4882
|
});
|
|
4257
4883
|
|
|
4258
4884
|
test.each([
|
|
@@ -4318,94 +4944,123 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4944
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4945
|
});
|
|
4320
4946
|
|
|
4321
|
-
test('
|
|
4947
|
+
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4322
4948
|
const method = new FirmwareUpdateV4({
|
|
4323
4949
|
id: 1,
|
|
4324
4950
|
payload: {
|
|
4325
4951
|
method: 'firmwareUpdateV4',
|
|
4326
|
-
platform: 'desktop',
|
|
4327
|
-
expectedTargetVersions: {
|
|
4328
|
-
app_v1: '2.0.0',
|
|
4329
|
-
},
|
|
4330
4952
|
},
|
|
4331
4953
|
});
|
|
4332
|
-
|
|
4954
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4955
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4956
|
+
message: {
|
|
4957
|
+
records: [
|
|
4958
|
+
{ target_id: 3, status: 2 },
|
|
4959
|
+
{ target_id: 4, status: 2 },
|
|
4960
|
+
],
|
|
4961
|
+
},
|
|
4962
|
+
});
|
|
4963
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4964
|
+
|
|
4965
|
+
(method as any).device = stubDevice({
|
|
4966
|
+
getCommands: () => ({ typedCall }),
|
|
4967
|
+
});
|
|
4968
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4969
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4333
4970
|
method.postProgressMessage = jest.fn();
|
|
4334
4971
|
|
|
4335
|
-
expect(
|
|
4336
|
-
(method as any).
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
)
|
|
4340
|
-
).
|
|
4972
|
+
await expect(
|
|
4973
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4974
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
4975
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
4976
|
+
])
|
|
4977
|
+
).resolves.toBeUndefined();
|
|
4978
|
+
|
|
4979
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4980
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
4981
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
4982
|
+
'DeviceFirmwareUpdateStatus',
|
|
4983
|
+
expect.anything(),
|
|
4984
|
+
expect.anything()
|
|
4985
|
+
);
|
|
4986
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4341
4987
|
});
|
|
4342
4988
|
|
|
4343
|
-
test('
|
|
4989
|
+
test('fails clearly when a requested target stays absent from the full status dump', async () => {
|
|
4344
4990
|
const method = new FirmwareUpdateV4({
|
|
4345
4991
|
id: 1,
|
|
4346
4992
|
payload: {
|
|
4347
4993
|
method: 'firmwareUpdateV4',
|
|
4348
|
-
platform: 'desktop',
|
|
4349
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4350
|
-
expectedTargetVersions: {
|
|
4351
|
-
app_v1: '1.0.0',
|
|
4352
|
-
app_v2: '2.0.0',
|
|
4353
|
-
},
|
|
4354
4994
|
},
|
|
4355
4995
|
});
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
patch_version: 0,
|
|
4361
|
-
};
|
|
4362
|
-
(method as any).protocolV2FinalStatusVerified = true;
|
|
4363
|
-
|
|
4364
|
-
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
4365
|
-
});
|
|
4366
|
-
|
|
4367
|
-
test('rejects unobservable final versions after the status endpoint fallback', () => {
|
|
4368
|
-
const method = new FirmwareUpdateV4({
|
|
4369
|
-
id: 1,
|
|
4370
|
-
payload: {
|
|
4371
|
-
method: 'firmwareUpdateV4',
|
|
4372
|
-
platform: 'desktop',
|
|
4373
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4374
|
-
expectedTargetVersions: {
|
|
4375
|
-
app_v1: '1.0.0',
|
|
4376
|
-
app_v2: '2.0.0',
|
|
4377
|
-
},
|
|
4996
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4997
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4998
|
+
message: {
|
|
4999
|
+
records: [{ target_id: 4, status: 2 }],
|
|
4378
5000
|
},
|
|
4379
5001
|
});
|
|
4380
|
-
|
|
4381
|
-
(
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
5002
|
+
let now = 0;
|
|
5003
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5004
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5005
|
+
now += 31 * 1000;
|
|
5006
|
+
callback();
|
|
5007
|
+
return 0 as any;
|
|
5008
|
+
}) as typeof setTimeout);
|
|
4386
5009
|
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
);
|
|
5010
|
+
(method as any).device = stubDevice({
|
|
5011
|
+
getCommands: () => ({ typedCall }),
|
|
5012
|
+
});
|
|
5013
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5014
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5015
|
+
method.postProgressMessage = jest.fn();
|
|
5016
|
+
|
|
5017
|
+
await expect(
|
|
5018
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5019
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5020
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5021
|
+
])
|
|
5022
|
+
).rejects.toMatchObject({
|
|
5023
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
5024
|
+
message: expect.stringContaining('targetIds=3'),
|
|
5025
|
+
});
|
|
5026
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4390
5027
|
});
|
|
4391
5028
|
|
|
4392
|
-
test('
|
|
5029
|
+
test('resets the missing-record grace period after a Pro2 reboot interruption', async () => {
|
|
4393
5030
|
const method = new FirmwareUpdateV4({
|
|
4394
5031
|
id: 1,
|
|
4395
5032
|
payload: {
|
|
4396
5033
|
method: 'firmwareUpdateV4',
|
|
4397
5034
|
},
|
|
4398
5035
|
});
|
|
4399
|
-
const typedCall = jest
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
5036
|
+
const typedCall = jest
|
|
5037
|
+
.fn()
|
|
5038
|
+
.mockResolvedValueOnce({
|
|
5039
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5040
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5041
|
+
})
|
|
5042
|
+
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
5043
|
+
.mockResolvedValueOnce({
|
|
5044
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5045
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5046
|
+
})
|
|
5047
|
+
.mockResolvedValueOnce({
|
|
5048
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5049
|
+
message: {
|
|
5050
|
+
records: [
|
|
5051
|
+
{ target_id: 3, status: 2 },
|
|
5052
|
+
{ target_id: 4, status: 2 },
|
|
5053
|
+
],
|
|
5054
|
+
},
|
|
5055
|
+
});
|
|
4408
5056
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5057
|
+
let now = 0;
|
|
5058
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5059
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5060
|
+
now += 31 * 1000;
|
|
5061
|
+
callback();
|
|
5062
|
+
return 0 as any;
|
|
5063
|
+
}) as typeof setTimeout);
|
|
4409
5064
|
|
|
4410
5065
|
(method as any).device = stubDevice({
|
|
4411
5066
|
getCommands: () => ({ typedCall }),
|
|
@@ -4416,19 +5071,64 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4416
5071
|
|
|
4417
5072
|
await expect(
|
|
4418
5073
|
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4419
|
-
{ target_id: 3, path: '
|
|
4420
|
-
{ target_id: 4, path: '
|
|
5074
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5075
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4421
5076
|
])
|
|
4422
5077
|
).resolves.toBeUndefined();
|
|
5078
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
5079
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
5080
|
+
});
|
|
4423
5081
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
5082
|
+
test('uses an eight-minute Pro2 install status window', async () => {
|
|
5083
|
+
const method = new FirmwareUpdateV4({
|
|
5084
|
+
id: 1,
|
|
5085
|
+
payload: {
|
|
5086
|
+
method: 'firmwareUpdateV4',
|
|
5087
|
+
},
|
|
5088
|
+
});
|
|
5089
|
+
jest
|
|
5090
|
+
.spyOn(Date, 'now')
|
|
5091
|
+
.mockReturnValueOnce(0)
|
|
5092
|
+
.mockReturnValue(8 * 60 * 1000);
|
|
5093
|
+
|
|
5094
|
+
await expect(
|
|
5095
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5096
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5097
|
+
])
|
|
5098
|
+
).rejects.toThrow('within 480s');
|
|
5099
|
+
});
|
|
5100
|
+
|
|
5101
|
+
test('uses a 90-second Pro2 bootloader reconnect window', async () => {
|
|
5102
|
+
const method = new FirmwareUpdateV4({
|
|
5103
|
+
id: 1,
|
|
5104
|
+
payload: {
|
|
5105
|
+
method: 'firmwareUpdateV4',
|
|
5106
|
+
},
|
|
5107
|
+
});
|
|
5108
|
+
jest
|
|
5109
|
+
.spyOn(Date, 'now')
|
|
5110
|
+
.mockReturnValueOnce(0)
|
|
5111
|
+
.mockReturnValue(90 * 1000);
|
|
5112
|
+
|
|
5113
|
+
await expect((method as any).waitForProtocolV2BootloaderMode()).rejects.toThrow('within 90s');
|
|
5114
|
+
});
|
|
5115
|
+
|
|
5116
|
+
test('uses a three-minute Pro2 final reconnect window', async () => {
|
|
5117
|
+
const method = new FirmwareUpdateV4({
|
|
5118
|
+
id: 1,
|
|
5119
|
+
payload: {
|
|
5120
|
+
method: 'firmwareUpdateV4',
|
|
5121
|
+
},
|
|
5122
|
+
});
|
|
5123
|
+
const waitForProtocolV2ReconnectAndFeatures = jest
|
|
5124
|
+
.fn()
|
|
5125
|
+
.mockRejectedValue(new Error('stop after capturing timeout'));
|
|
5126
|
+
(method as any).waitForProtocolV2ReconnectAndFeatures = waitForProtocolV2ReconnectAndFeatures;
|
|
5127
|
+
|
|
5128
|
+
await expect((method as any).waitForProtocolV2FinalFeatures()).rejects.toThrow(
|
|
5129
|
+
'stop after capturing timeout'
|
|
4430
5130
|
);
|
|
4431
|
-
expect(
|
|
5131
|
+
expect(waitForProtocolV2ReconnectAndFeatures).toHaveBeenCalledWith(3 * 60 * 1000);
|
|
4432
5132
|
});
|
|
4433
5133
|
|
|
4434
5134
|
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
@@ -4465,7 +5165,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4465
5165
|
).toBe(true);
|
|
4466
5166
|
|
|
4467
5167
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4468
|
-
expect(
|
|
5168
|
+
expect(
|
|
4469
5169
|
(method as any).assertProtocolV2TargetStatus(
|
|
4470
5170
|
[
|
|
4471
5171
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4473,8 +5173,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4473
5173
|
],
|
|
4474
5174
|
new Set([4, 10])
|
|
4475
5175
|
)
|
|
4476
|
-
).
|
|
4477
|
-
expect(method.postProgressMessage).
|
|
5176
|
+
).toBe(false);
|
|
5177
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4478
5178
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4479
5179
|
|
|
4480
5180
|
expect(
|
|
@@ -4563,7 +5263,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4563
5263
|
path: true,
|
|
4564
5264
|
},
|
|
4565
5265
|
},
|
|
4566
|
-
{ timeoutMs:
|
|
5266
|
+
{ timeoutMs: 15000 }
|
|
4567
5267
|
);
|
|
4568
5268
|
});
|
|
4569
5269
|
|
|
@@ -4578,14 +5278,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4578
5278
|
const writtenPaths: string[] = [];
|
|
4579
5279
|
method.postTipMessage = jest.fn();
|
|
4580
5280
|
method.postProgressMessage = jest.fn();
|
|
4581
|
-
(method as any).
|
|
5281
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4582
5282
|
writtenPaths.push(params.filePath);
|
|
4583
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
5283
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4584
5284
|
});
|
|
4585
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4586
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4587
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
4588
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4589
5285
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4590
5286
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4591
5287
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4625,12 +5321,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
5321
|
'vol0:/coprocessor.bin',
|
|
4626
5322
|
1
|
|
4627
5323
|
);
|
|
4628
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
4629
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4630
|
-
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4631
|
-
});
|
|
4632
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5324
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
5325
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4633
5326
|
targets: [
|
|
5327
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4634
5328
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4635
5329
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4636
5330
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4640,7 +5334,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4640
5334
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4641
5335
|
});
|
|
4642
5336
|
|
|
4643
|
-
test('announces
|
|
5337
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4644
5338
|
const method = new FirmwareUpdateV4({
|
|
4645
5339
|
id: 1,
|
|
4646
5340
|
payload: {
|
|
@@ -4650,14 +5344,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4650
5344
|
|
|
4651
5345
|
method.postTipMessage = jest.fn();
|
|
4652
5346
|
method.postProgressMessage = jest.fn();
|
|
4653
|
-
(method as any).
|
|
5347
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4654
5348
|
.fn()
|
|
4655
5349
|
.mockImplementation(params =>
|
|
4656
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5350
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4657
5351
|
);
|
|
4658
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4659
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4660
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4661
5352
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4662
5353
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4663
5354
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4677,22 +5368,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
5368
|
{
|
|
4678
5369
|
fileName: 'application_p1.bin',
|
|
4679
5370
|
binary: new Uint8Array([3]).buffer,
|
|
4680
|
-
targetId:
|
|
5371
|
+
targetId: 3,
|
|
4681
5372
|
},
|
|
4682
5373
|
],
|
|
4683
5374
|
});
|
|
4684
5375
|
|
|
4685
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
5376
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4686
5377
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4687
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4688
|
-
expect(method.
|
|
4689
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5378
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5379
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4690
5380
|
1,
|
|
4691
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
5381
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4692
5382
|
);
|
|
4693
|
-
expect((method as any).
|
|
5383
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4694
5384
|
2,
|
|
4695
|
-
expect.objectContaining({ processedSize:
|
|
5385
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4696
5386
|
);
|
|
4697
5387
|
});
|
|
4698
5388
|
|
|
@@ -4706,8 +5396,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4706
5396
|
|
|
4707
5397
|
method.postTipMessage = jest.fn();
|
|
4708
5398
|
method.postProgressMessage = jest.fn();
|
|
4709
|
-
(method as any).
|
|
4710
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5399
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4711
5400
|
(method as any).device = stubDevice({
|
|
4712
5401
|
getCommands: () => ({
|
|
4713
5402
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4753,14 +5442,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4753
5442
|
|
|
4754
5443
|
method.postTipMessage = jest.fn();
|
|
4755
5444
|
method.postProgressMessage = jest.fn();
|
|
4756
|
-
(method as any).
|
|
5445
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4757
5446
|
.fn()
|
|
4758
5447
|
.mockImplementation(params =>
|
|
4759
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5448
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4760
5449
|
);
|
|
4761
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4762
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4763
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4764
5450
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4765
5451
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4766
5452
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4975,33 +5661,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4975
5661
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4976
5662
|
});
|
|
4977
5663
|
|
|
4978
|
-
test('
|
|
5664
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
4979
5665
|
const method = new FirmwareUpdateV4({
|
|
4980
5666
|
id: 1,
|
|
4981
5667
|
payload: {
|
|
4982
5668
|
method: 'firmwareUpdateV4',
|
|
4983
5669
|
platform: 'web',
|
|
4984
|
-
targetsToUpdate: ['resource'],
|
|
4985
5670
|
},
|
|
4986
5671
|
});
|
|
4987
5672
|
method.init();
|
|
4988
5673
|
|
|
5674
|
+
const getSysResourceBinarySpy = jest
|
|
5675
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5676
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
4989
5677
|
const getFirmwareLatestReleaseSpy = jest
|
|
4990
5678
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4991
5679
|
.mockReturnValue({
|
|
4992
5680
|
required: false,
|
|
4993
5681
|
version: [1, 0, 0],
|
|
4994
|
-
url: '',
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
headerHash: 'cd'.repeat(64),
|
|
5682
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5683
|
+
bootloaderVersion: [1, 0, 0],
|
|
5684
|
+
upgradeType: 'payload-package-set',
|
|
5685
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5686
|
+
components: {
|
|
5687
|
+
bootloader: {
|
|
5688
|
+
target: 'BOOTLOADER',
|
|
5689
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5003
5690
|
},
|
|
5004
|
-
|
|
5691
|
+
applicationP1: {
|
|
5692
|
+
target: 'APPLICATION_P1',
|
|
5693
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5694
|
+
},
|
|
5695
|
+
applicationP2: {
|
|
5696
|
+
target: 'APPLICATION_P2',
|
|
5697
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
5698
|
+
},
|
|
5699
|
+
},
|
|
5005
5700
|
fingerprint: '',
|
|
5006
5701
|
changelog: {
|
|
5007
5702
|
'zh-CN': '',
|
|
@@ -5009,356 +5704,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5009
5704
|
},
|
|
5010
5705
|
});
|
|
5011
5706
|
|
|
5012
|
-
const
|
|
5707
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5013
5708
|
deviceType: 'pro2',
|
|
5014
5709
|
firmwareVersion: '1.0.0',
|
|
5710
|
+
bootloaderVersion: '1.0.0',
|
|
5015
5711
|
capabilities: [],
|
|
5016
5712
|
});
|
|
5017
5713
|
|
|
5018
|
-
expect(
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
version: [2, 0, 0],
|
|
5025
|
-
payloadHash: 'ab'.repeat(64),
|
|
5026
|
-
headerHash: 'cd'.repeat(64),
|
|
5027
|
-
},
|
|
5028
|
-
]);
|
|
5029
|
-
expect(bundles[0].binary.byteLength).toBe(0);
|
|
5714
|
+
expect(remoteBinaries).toEqual({
|
|
5715
|
+
bootloaderBinary: null,
|
|
5716
|
+
fwBinaryMap: [],
|
|
5717
|
+
installItems: [],
|
|
5718
|
+
});
|
|
5719
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5030
5720
|
|
|
5721
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5031
5722
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5032
5723
|
});
|
|
5033
5724
|
|
|
5034
|
-
test('
|
|
5035
|
-
const method = new FirmwareUpdateV4({
|
|
5036
|
-
id: 1,
|
|
5037
|
-
payload: {
|
|
5038
|
-
method: 'firmwareUpdateV4',
|
|
5039
|
-
platform: 'web',
|
|
5040
|
-
targetsToUpdate: ['resource'],
|
|
5041
|
-
},
|
|
5042
|
-
});
|
|
5043
|
-
method.init();
|
|
5044
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5045
|
-
|
|
5046
|
-
const getSysResourceBinarySpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
5047
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5048
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5049
|
-
.mockReturnValue({
|
|
5050
|
-
required: false,
|
|
5051
|
-
version: [1, 0, 0],
|
|
5052
|
-
url: '',
|
|
5053
|
-
resourceBundles: [
|
|
5054
|
-
{
|
|
5055
|
-
name: 'images',
|
|
5056
|
-
url: 'https://example.com/images.okpkg',
|
|
5057
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5058
|
-
},
|
|
5059
|
-
],
|
|
5060
|
-
fingerprint: '',
|
|
5061
|
-
changelog: {
|
|
5062
|
-
'zh-CN': '',
|
|
5063
|
-
'en-US': '',
|
|
5064
|
-
},
|
|
5065
|
-
});
|
|
5066
|
-
|
|
5067
|
-
(method as any).device = stubDevice({
|
|
5068
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5069
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5070
|
-
});
|
|
5071
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5072
|
-
bootloaderBinary: null,
|
|
5073
|
-
fwBinaryMap: [],
|
|
5074
|
-
});
|
|
5075
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5076
|
-
method.postTipMessage = jest.fn();
|
|
5077
|
-
|
|
5078
|
-
await expect(method.run()).rejects.toThrow('resourceBundles[0].devicePath');
|
|
5079
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5081
|
-
|
|
5082
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5083
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5084
|
-
});
|
|
5085
|
-
|
|
5086
|
-
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
5087
|
-
const method = new FirmwareUpdateV4({
|
|
5088
|
-
id: 1,
|
|
5089
|
-
payload: {
|
|
5090
|
-
method: 'firmwareUpdateV4',
|
|
5091
|
-
},
|
|
5092
|
-
});
|
|
5093
|
-
const openPreparedSource = jest.fn();
|
|
5094
|
-
(method as any).params = {
|
|
5095
|
-
targetsToUpdate: ['resource'],
|
|
5096
|
-
resourceBundleArtifacts: [
|
|
5097
|
-
{
|
|
5098
|
-
name: 'images',
|
|
5099
|
-
artifact: {
|
|
5100
|
-
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5101
|
-
size: 1,
|
|
5102
|
-
sha256: 'a'.repeat(64),
|
|
5103
|
-
},
|
|
5104
|
-
},
|
|
5105
|
-
],
|
|
5106
|
-
};
|
|
5107
|
-
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
-
.mockReturnValue({
|
|
5111
|
-
required: false,
|
|
5112
|
-
version: [1, 0, 0],
|
|
5113
|
-
url: '',
|
|
5114
|
-
resourceBundles: [
|
|
5115
|
-
{
|
|
5116
|
-
name: 'images',
|
|
5117
|
-
url: 'https://example.com/images.okpkg',
|
|
5118
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
-
},
|
|
5120
|
-
],
|
|
5121
|
-
fingerprint: '',
|
|
5122
|
-
changelog: {
|
|
5123
|
-
'zh-CN': '',
|
|
5124
|
-
'en-US': '',
|
|
5125
|
-
},
|
|
5126
|
-
});
|
|
5127
|
-
|
|
5128
|
-
await expect(
|
|
5129
|
-
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
-
deviceType: 'pro2',
|
|
5131
|
-
})
|
|
5132
|
-
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
-
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5134
|
-
|
|
5135
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5136
|
-
});
|
|
5137
|
-
|
|
5138
|
-
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5139
|
-
const method = new FirmwareUpdateV4({
|
|
5140
|
-
id: 1,
|
|
5141
|
-
payload: {
|
|
5142
|
-
method: 'firmwareUpdateV4',
|
|
5143
|
-
platform: 'web',
|
|
5144
|
-
targetsToUpdate: ['resource'],
|
|
5145
|
-
},
|
|
5146
|
-
});
|
|
5147
|
-
method.init();
|
|
5148
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5149
|
-
|
|
5150
|
-
const order: string[] = [];
|
|
5151
|
-
const resourceBinary = buildOkppHeader().buffer as ArrayBuffer;
|
|
5152
|
-
const getSysResourceBinarySpy = jest
|
|
5153
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5154
|
-
.mockImplementation(() => {
|
|
5155
|
-
order.push('download');
|
|
5156
|
-
return Promise.resolve({ binary: resourceBinary });
|
|
5157
|
-
});
|
|
5158
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5159
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5160
|
-
.mockReturnValue({
|
|
5161
|
-
required: false,
|
|
5162
|
-
version: [1, 0, 0],
|
|
5163
|
-
url: '',
|
|
5164
|
-
resourceBundles: [
|
|
5165
|
-
{
|
|
5166
|
-
name: 'images',
|
|
5167
|
-
url: 'https://example.com/images.okpkg',
|
|
5168
|
-
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
5169
|
-
},
|
|
5170
|
-
],
|
|
5171
|
-
fingerprint: '',
|
|
5172
|
-
changelog: {
|
|
5173
|
-
'zh-CN': '',
|
|
5174
|
-
'en-US': '',
|
|
5175
|
-
},
|
|
5176
|
-
});
|
|
5177
|
-
|
|
5178
|
-
(method as any).device = stubDevice({
|
|
5179
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5180
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5181
|
-
});
|
|
5182
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5183
|
-
bootloaderBinary: null,
|
|
5184
|
-
fwBinaryMap: [],
|
|
5185
|
-
});
|
|
5186
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
5187
|
-
order.push('bootloader');
|
|
5188
|
-
return Promise.resolve(true);
|
|
5189
|
-
});
|
|
5190
|
-
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
5191
|
-
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
5192
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
5193
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5194
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5195
|
-
bootloaderVersion: '1.0.0',
|
|
5196
|
-
bleVersion: '0.0.0',
|
|
5197
|
-
firmwareVersion: '1.0.0',
|
|
5198
|
-
});
|
|
5199
|
-
method.postTipMessage = jest.fn();
|
|
5200
|
-
|
|
5201
|
-
await method.run();
|
|
5202
|
-
|
|
5203
|
-
expect(order).toEqual(['download', 'bootloader']);
|
|
5204
|
-
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
5205
|
-
bootloaderBinary: null,
|
|
5206
|
-
fwBinaryMap: [],
|
|
5207
|
-
resourceBundles: [
|
|
5208
|
-
{
|
|
5209
|
-
name: 'images',
|
|
5210
|
-
binary: resourceBinary,
|
|
5211
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5212
|
-
url: 'https://example.com/images.okpkg',
|
|
5213
|
-
},
|
|
5214
|
-
],
|
|
5215
|
-
});
|
|
5216
|
-
|
|
5217
|
-
executeProtocolV2UpdateSpy.mockRestore();
|
|
5218
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5219
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5220
|
-
});
|
|
5221
|
-
|
|
5222
|
-
test('rejects a remote RESC bundle without an OKPP header when metadata is absent', async () => {
|
|
5223
|
-
const method = new FirmwareUpdateV4({
|
|
5224
|
-
id: 1,
|
|
5225
|
-
payload: {
|
|
5226
|
-
method: 'firmwareUpdateV4',
|
|
5227
|
-
},
|
|
5228
|
-
});
|
|
5229
|
-
method.init();
|
|
5230
|
-
const getSysResourceBinarySpy = jest
|
|
5231
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5232
|
-
.mockResolvedValue({ binary: new Uint8Array([1, 2, 3]).buffer });
|
|
5233
|
-
|
|
5234
|
-
await expect(
|
|
5235
|
-
(method as any).materializeProtocolV2ResourceBundles([
|
|
5236
|
-
{
|
|
5237
|
-
name: 'images',
|
|
5238
|
-
binary: new ArrayBuffer(0),
|
|
5239
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5240
|
-
url: 'https://example.com/images.okpkg',
|
|
5241
|
-
},
|
|
5242
|
-
])
|
|
5243
|
-
).rejects.toThrow('Invalid Protocol V2 RESC bundle header: images');
|
|
5244
|
-
|
|
5245
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5246
|
-
});
|
|
5247
|
-
|
|
5248
|
-
test('rejects an empty remote RESC bundle before entering bootloader', async () => {
|
|
5249
|
-
const method = new FirmwareUpdateV4({
|
|
5250
|
-
id: 1,
|
|
5251
|
-
payload: {
|
|
5252
|
-
method: 'firmwareUpdateV4',
|
|
5253
|
-
platform: 'web',
|
|
5254
|
-
targetsToUpdate: ['resource'],
|
|
5255
|
-
},
|
|
5256
|
-
});
|
|
5257
|
-
method.init();
|
|
5258
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5259
|
-
|
|
5260
|
-
const getSysResourceBinarySpy = jest
|
|
5261
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5262
|
-
.mockResolvedValue({ binary: new ArrayBuffer(0) });
|
|
5263
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5264
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5265
|
-
.mockReturnValue({
|
|
5266
|
-
required: false,
|
|
5267
|
-
version: [1, 0, 0],
|
|
5268
|
-
url: '',
|
|
5269
|
-
resourceBundles: [
|
|
5270
|
-
{
|
|
5271
|
-
name: 'images',
|
|
5272
|
-
url: 'https://example.com/images.okpkg',
|
|
5273
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5274
|
-
},
|
|
5275
|
-
],
|
|
5276
|
-
fingerprint: '',
|
|
5277
|
-
changelog: {
|
|
5278
|
-
'zh-CN': '',
|
|
5279
|
-
'en-US': '',
|
|
5280
|
-
},
|
|
5281
|
-
});
|
|
5282
|
-
(method as any).device = stubDevice({
|
|
5283
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5284
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5285
|
-
});
|
|
5286
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5287
|
-
bootloaderBinary: null,
|
|
5288
|
-
fwBinaryMap: [],
|
|
5289
|
-
});
|
|
5290
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5291
|
-
method.postTipMessage = jest.fn();
|
|
5292
|
-
|
|
5293
|
-
await expect(method.run()).rejects.toThrow('Protocol V2 RESC bundle is empty: images');
|
|
5294
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5295
|
-
|
|
5296
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5297
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5298
|
-
});
|
|
5299
|
-
|
|
5300
|
-
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5301
|
-
const method = new FirmwareUpdateV4({
|
|
5302
|
-
id: 1,
|
|
5303
|
-
payload: {
|
|
5304
|
-
method: 'firmwareUpdateV4',
|
|
5305
|
-
platform: 'web',
|
|
5306
|
-
},
|
|
5307
|
-
});
|
|
5308
|
-
method.init();
|
|
5309
|
-
|
|
5310
|
-
const getSysResourceBinarySpy = jest
|
|
5311
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5312
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5313
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5314
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5315
|
-
.mockReturnValue({
|
|
5316
|
-
required: false,
|
|
5317
|
-
version: [1, 0, 0],
|
|
5318
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5319
|
-
bootloaderVersion: [1, 0, 0],
|
|
5320
|
-
upgradeType: 'payload-package-set',
|
|
5321
|
-
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5322
|
-
components: {
|
|
5323
|
-
bootloader: {
|
|
5324
|
-
target: 'BOOTLOADER',
|
|
5325
|
-
url: 'https://example.com/bootloader.pp.bin',
|
|
5326
|
-
},
|
|
5327
|
-
applicationP1: {
|
|
5328
|
-
target: 'APPLICATION_P1',
|
|
5329
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5330
|
-
},
|
|
5331
|
-
applicationP2: {
|
|
5332
|
-
target: 'APPLICATION_P2',
|
|
5333
|
-
url: 'https://example.com/applicationP2.pp.bin',
|
|
5334
|
-
},
|
|
5335
|
-
},
|
|
5336
|
-
fingerprint: '',
|
|
5337
|
-
changelog: {
|
|
5338
|
-
'zh-CN': '',
|
|
5339
|
-
'en-US': '',
|
|
5340
|
-
},
|
|
5341
|
-
});
|
|
5342
|
-
|
|
5343
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5344
|
-
deviceType: 'pro2',
|
|
5345
|
-
firmwareVersion: '1.0.0',
|
|
5346
|
-
bootloaderVersion: '1.0.0',
|
|
5347
|
-
capabilities: [],
|
|
5348
|
-
});
|
|
5349
|
-
|
|
5350
|
-
expect(remoteBinaries).toEqual({
|
|
5351
|
-
bootloaderBinary: null,
|
|
5352
|
-
fwBinaryMap: [],
|
|
5353
|
-
installItems: [],
|
|
5354
|
-
});
|
|
5355
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5356
|
-
|
|
5357
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5358
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5359
|
-
});
|
|
5360
|
-
|
|
5361
|
-
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5725
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5362
5726
|
const method = new FirmwareUpdateV4({
|
|
5363
5727
|
id: 1,
|
|
5364
5728
|
payload: {
|
|
@@ -5397,6 +5761,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5397
5761
|
|
|
5398
5762
|
await method.run();
|
|
5399
5763
|
|
|
5764
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5400
5765
|
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5401
5766
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5402
5767
|
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
@@ -5404,6 +5769,36 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5404
5769
|
});
|
|
5405
5770
|
});
|
|
5406
5771
|
|
|
5772
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5773
|
+
const method = new FirmwareUpdateV4({
|
|
5774
|
+
id: 1,
|
|
5775
|
+
payload: {
|
|
5776
|
+
method: 'firmwareUpdateV4',
|
|
5777
|
+
platform: 'web',
|
|
5778
|
+
},
|
|
5779
|
+
});
|
|
5780
|
+
method.init();
|
|
5781
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5782
|
+
(method as any).device = stubDevice({
|
|
5783
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5784
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5785
|
+
});
|
|
5786
|
+
forceReloadDataSpy.mockRejectedValue(
|
|
5787
|
+
ERRORS.TypedError(HardwareErrorCode.NetworkError, 'latest config unavailable')
|
|
5788
|
+
);
|
|
5789
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5790
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5791
|
+
method.postTipMessage = jest.fn();
|
|
5792
|
+
|
|
5793
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
5794
|
+
errorCode: HardwareErrorCode.NetworkError,
|
|
5795
|
+
message: 'latest config unavailable',
|
|
5796
|
+
});
|
|
5797
|
+
|
|
5798
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5799
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5800
|
+
});
|
|
5801
|
+
|
|
5407
5802
|
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5408
5803
|
const method = new FirmwareUpdateV4({
|
|
5409
5804
|
id: 1,
|
|
@@ -5440,14 +5835,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5440
5835
|
});
|
|
5441
5836
|
});
|
|
5442
5837
|
|
|
5443
|
-
test('treats manual resource
|
|
5838
|
+
test('treats manual resource files as explicit payload without remote firmware auto-fill', async () => {
|
|
5444
5839
|
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
5445
5840
|
const method = new FirmwareUpdateV4({
|
|
5446
5841
|
id: 1,
|
|
5447
5842
|
payload: {
|
|
5448
5843
|
method: 'firmwareUpdateV4',
|
|
5449
5844
|
platform: 'web',
|
|
5450
|
-
|
|
5845
|
+
resourceFiles: [
|
|
5451
5846
|
{
|
|
5452
5847
|
binary: resourceBundle,
|
|
5453
5848
|
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
@@ -5495,7 +5890,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5495
5890
|
payload: {
|
|
5496
5891
|
method: 'firmwareUpdateV4',
|
|
5497
5892
|
platform: 'web',
|
|
5498
|
-
|
|
5893
|
+
resourceFiles: [
|
|
5499
5894
|
{
|
|
5500
5895
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
5501
5896
|
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
@@ -5514,7 +5909,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5514
5909
|
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5515
5910
|
method.postTipMessage = jest.fn();
|
|
5516
5911
|
|
|
5517
|
-
await expect(method.run()).rejects.toThrow('
|
|
5912
|
+
await expect(method.run()).rejects.toThrow('resourceFiles[0].devicePath');
|
|
5518
5913
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5519
5914
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5520
5915
|
});
|
|
@@ -5529,10 +5924,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5529
5924
|
|
|
5530
5925
|
method.postTipMessage = jest.fn();
|
|
5531
5926
|
method.postProgressMessage = jest.fn();
|
|
5532
|
-
(method as any).
|
|
5533
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5534
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5535
|
-
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5927
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5536
5928
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5537
5929
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5538
5930
|
|
|
@@ -5548,7 +5940,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5548
5940
|
fwBinaryMap: [],
|
|
5549
5941
|
});
|
|
5550
5942
|
|
|
5551
|
-
expect((method as any).
|
|
5943
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5552
5944
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5553
5945
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5554
5946
|
});
|
|
@@ -5576,22 +5968,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5576
5968
|
|
|
5577
5969
|
(method as any).device = stubDevice({
|
|
5578
5970
|
getCommands: () => ({ typedCall }),
|
|
5971
|
+
getCurrentDeviceType: () => 'pro2',
|
|
5972
|
+
toMessageObject: () => ({ connectId: 'firmware-device' }),
|
|
5579
5973
|
});
|
|
5580
|
-
method.
|
|
5974
|
+
method.postMessage = jest.fn();
|
|
5581
5975
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5976
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5977
|
+
payload: new Uint8Array(4097).buffer,
|
|
5978
|
+
filePath: 'vol1:firmware.bin',
|
|
5979
|
+
processedSize: 0,
|
|
5980
|
+
totalSize: 4097,
|
|
5584
5981
|
});
|
|
5585
|
-
try {
|
|
5586
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5587
|
-
source,
|
|
5588
|
-
filePath: 'vol1:firmware.bin',
|
|
5589
|
-
processedSize: 0,
|
|
5590
|
-
totalSize: 4097,
|
|
5591
|
-
});
|
|
5592
|
-
} finally {
|
|
5593
|
-
await source?.close();
|
|
5594
|
-
}
|
|
5595
5982
|
|
|
5596
5983
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5597
5984
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5599,6 +5986,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5599
5986
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
5600
5987
|
expect(writePayloads.every(payload => payload.append === false)).toBe(true);
|
|
5601
5988
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
5989
|
+
expect(method.postMessage).toHaveBeenLastCalledWith({
|
|
5990
|
+
event: 'UI_EVENT',
|
|
5991
|
+
type: UI_REQUEST.FIRMWARE_PROGRESS,
|
|
5992
|
+
payload: expect.objectContaining({
|
|
5993
|
+
progress: 99,
|
|
5994
|
+
progressType: 'transferData',
|
|
5995
|
+
transferredBytes: 4097,
|
|
5996
|
+
totalBytes: 4097,
|
|
5997
|
+
elapsedMs: expect.any(Number),
|
|
5998
|
+
}),
|
|
5999
|
+
});
|
|
5602
6000
|
});
|
|
5603
6001
|
|
|
5604
6002
|
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
@@ -5648,16 +6046,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5648
6046
|
method.postProgressMessage = jest.fn();
|
|
5649
6047
|
|
|
5650
6048
|
try {
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
});
|
|
5654
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
-
source,
|
|
6049
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6050
|
+
payload: new Uint8Array(8001).buffer,
|
|
5656
6051
|
filePath: 'vol0:/firmware.bin',
|
|
5657
6052
|
processedSize: 0,
|
|
5658
6053
|
totalSize: 8001,
|
|
5659
6054
|
});
|
|
5660
|
-
await source?.close();
|
|
5661
6055
|
} finally {
|
|
5662
6056
|
setTimeoutSpy.mockRestore();
|
|
5663
6057
|
}
|
|
@@ -5692,21 +6086,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5692
6086
|
});
|
|
5693
6087
|
method.postProgressMessage = jest.fn();
|
|
5694
6088
|
method.postTipMessage = jest.fn();
|
|
5695
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5696
6089
|
|
|
5697
|
-
const source = await openFirmwareByteSource({
|
|
5698
|
-
binary: new Uint8Array(4097).buffer,
|
|
5699
|
-
});
|
|
5700
6090
|
await expect(
|
|
5701
|
-
(method as any).
|
|
5702
|
-
|
|
6091
|
+
(method as any).protocolV2WriteWholeFile({
|
|
6092
|
+
payload: new Uint8Array(4097).buffer,
|
|
5703
6093
|
filePath: 'vol0:/firmware.bin',
|
|
5704
6094
|
processedSize: 0,
|
|
5705
6095
|
totalSize: 4097,
|
|
5706
6096
|
})
|
|
5707
6097
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5708
|
-
|
|
5709
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6098
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5710
6099
|
});
|
|
5711
6100
|
|
|
5712
6101
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5739,24 +6128,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5739
6128
|
});
|
|
5740
6129
|
method.postProgressMessage = jest.fn();
|
|
5741
6130
|
|
|
5742
|
-
|
|
5743
|
-
|
|
6131
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6132
|
+
payload: new Uint8Array(1801).buffer,
|
|
6133
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
6134
|
+
processedSize: 0,
|
|
6135
|
+
totalSize: 1801,
|
|
5744
6136
|
});
|
|
5745
|
-
try {
|
|
5746
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5747
|
-
source,
|
|
5748
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
5749
|
-
processedSize: 0,
|
|
5750
|
-
totalSize: 1801,
|
|
5751
|
-
});
|
|
5752
|
-
} finally {
|
|
5753
|
-
await source?.close();
|
|
5754
|
-
}
|
|
5755
6137
|
|
|
5756
6138
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5757
6139
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
5758
6140
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
5759
6141
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
6142
|
+
expect(typedCall.mock.calls.map(call => call[3])).toEqual([
|
|
6143
|
+
{ writeWithResponse: true },
|
|
6144
|
+
{ writeWithResponse: true },
|
|
6145
|
+
]);
|
|
5760
6146
|
});
|
|
5761
6147
|
|
|
5762
6148
|
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
@@ -5868,9 +6254,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
6254
|
const typedCall = jest
|
|
5869
6255
|
.fn()
|
|
5870
6256
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6257
|
+
new DOMException(
|
|
6258
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6259
|
+
'NotFoundError'
|
|
5874
6260
|
)
|
|
5875
6261
|
);
|
|
5876
6262
|
|
|
@@ -5924,7 +6310,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5924
6310
|
expect(typedCall).not.toHaveBeenCalled();
|
|
5925
6311
|
});
|
|
5926
6312
|
|
|
5927
|
-
test.each([0, 2])(
|
|
6313
|
+
test.each([0, 1, 2])(
|
|
5928
6314
|
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
5929
6315
|
async targetId => {
|
|
5930
6316
|
const typedCall = jest.fn();
|
|
@@ -5957,8 +6343,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5957
6343
|
targets: [
|
|
5958
6344
|
{
|
|
5959
6345
|
target_id: undefined,
|
|
5960
|
-
targetId:
|
|
5961
|
-
path: 'vol0:
|
|
6346
|
+
targetId: 3,
|
|
6347
|
+
path: 'vol0:bootloader.okpkg',
|
|
5962
6348
|
},
|
|
5963
6349
|
],
|
|
5964
6350
|
} as any,
|
|
@@ -5970,7 +6356,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5970
6356
|
|
|
5971
6357
|
await method.run();
|
|
5972
6358
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
5973
|
-
targets: [{ target_id:
|
|
6359
|
+
targets: [{ target_id: 3, path: 'vol0:bootloader.okpkg' }],
|
|
5974
6360
|
});
|
|
5975
6361
|
});
|
|
5976
6362
|
|
|
@@ -6035,20 +6421,63 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
6421
|
});
|
|
6036
6422
|
|
|
6037
6423
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6424
|
+
const createResourceFilesystemTypedCall = (
|
|
6425
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6426
|
+
missingTypes: string[] = []
|
|
6427
|
+
) => {
|
|
6428
|
+
const missing = new Set(missingTypes);
|
|
6429
|
+
const resourceByPath = new Map(
|
|
6430
|
+
stable.map(resource => [
|
|
6431
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
6432
|
+
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6433
|
+
],
|
|
6434
|
+
resource,
|
|
6435
|
+
])
|
|
6436
|
+
);
|
|
6437
|
+
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6438
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
6439
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6440
|
+
}
|
|
6441
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6442
|
+
const resource = resourceByPath.get(payload.path);
|
|
6443
|
+
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6444
|
+
return {
|
|
6445
|
+
message: {
|
|
6446
|
+
exist: exists,
|
|
6447
|
+
directory: false,
|
|
6448
|
+
size: exists ? resource?.size : 0,
|
|
6449
|
+
},
|
|
6450
|
+
};
|
|
6451
|
+
}
|
|
6452
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6453
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
6454
|
+
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6455
|
+
const header = new Uint8Array(0x52a0);
|
|
6456
|
+
const view = new DataView(header.buffer);
|
|
6457
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6458
|
+
header[index] = char.charCodeAt(0);
|
|
6459
|
+
});
|
|
6460
|
+
'RESC'.split('').forEach((char, index) => {
|
|
6461
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6462
|
+
});
|
|
6463
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6464
|
+
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6465
|
+
header[0x240 + index] = Number.parseInt(
|
|
6466
|
+
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6467
|
+
16
|
|
6468
|
+
);
|
|
6469
|
+
}
|
|
6470
|
+
const offset = Number(payload.file.offset);
|
|
6471
|
+
const chunkLength = Number(payload.chunk_len);
|
|
6472
|
+
return {
|
|
6473
|
+
message: {
|
|
6474
|
+
data: header.slice(offset, offset + chunkLength),
|
|
6475
|
+
},
|
|
6476
|
+
};
|
|
6477
|
+
}
|
|
6478
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6046
6479
|
});
|
|
6047
|
-
|
|
6048
|
-
method.init();
|
|
6049
|
-
|
|
6050
|
-
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
-
});
|
|
6480
|
+
};
|
|
6052
6481
|
|
|
6053
6482
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6054
6483
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6059,15 +6488,15 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6059
6488
|
).not.toThrow();
|
|
6060
6489
|
});
|
|
6061
6490
|
|
|
6062
|
-
test('
|
|
6063
|
-
expect(() =>
|
|
6064
|
-
|
|
6065
|
-
);
|
|
6066
|
-
expect(() =>
|
|
6067
|
-
|
|
6068
|
-
);
|
|
6491
|
+
test('requires the same descriptor path when either physical serial is unavailable', () => {
|
|
6492
|
+
expect(() =>
|
|
6493
|
+
assertProtocolV2ReconnectIdentity('expected-serial', undefined, 'usb-path', 'usb-path')
|
|
6494
|
+
).not.toThrow();
|
|
6495
|
+
expect(() =>
|
|
6496
|
+
assertProtocolV2ReconnectIdentity(undefined, 'actual-serial', 'usb-path', 'other-path')
|
|
6497
|
+
).toThrow('identity unavailable');
|
|
6069
6498
|
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
6070
|
-
|
|
6499
|
+
'identity unavailable'
|
|
6071
6500
|
);
|
|
6072
6501
|
});
|
|
6073
6502
|
|
|
@@ -6086,6 +6515,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6086
6515
|
},
|
|
6087
6516
|
});
|
|
6088
6517
|
(method as any).device = stubDevice({
|
|
6518
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6089
6519
|
features: { deviceId: 'wallet-derived-id' },
|
|
6090
6520
|
getCommands: () => ({ typedCall }),
|
|
6091
6521
|
});
|
|
@@ -6101,7 +6531,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6101
6531
|
);
|
|
6102
6532
|
});
|
|
6103
6533
|
|
|
6104
|
-
test('
|
|
6534
|
+
test('allows firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6105
6535
|
const method = new FirmwareUpdateV4({
|
|
6106
6536
|
id: 1,
|
|
6107
6537
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6111,42 +6541,204 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6111
6541
|
message: { protocol_version: 1, hw: {} },
|
|
6112
6542
|
});
|
|
6113
6543
|
(method as any).device = stubDevice({
|
|
6544
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6114
6545
|
getCommands: () => ({ typedCall }),
|
|
6115
6546
|
});
|
|
6116
6547
|
|
|
6117
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6118
|
-
|
|
6119
|
-
|
|
6548
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).resolves.toBeUndefined();
|
|
6549
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
6550
|
+
expect((method as any).protocolV2ExpectedPath).toBeDefined();
|
|
6120
6551
|
});
|
|
6121
6552
|
|
|
6122
|
-
test('uses
|
|
6553
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6123
6554
|
const method = new FirmwareUpdateV4({
|
|
6124
6555
|
id: 1,
|
|
6125
6556
|
payload: {
|
|
6126
6557
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
6558
|
+
platform: 'web',
|
|
6559
|
+
targetsToUpdate: ['resource'],
|
|
6129
6560
|
},
|
|
6130
6561
|
});
|
|
6131
6562
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6563
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6564
|
+
(type, index) => ({
|
|
6565
|
+
type,
|
|
6566
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6567
|
+
size: 0x52a0 + index + 1,
|
|
6568
|
+
fileHash: 'a'.repeat(64),
|
|
6569
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6570
|
+
})
|
|
6571
|
+
);
|
|
6572
|
+
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
6573
|
+
(method as any).device = stubDevice({
|
|
6574
|
+
getCommands: () => ({ typedCall }),
|
|
6575
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6576
|
+
});
|
|
6577
|
+
const resourcesSpy = jest
|
|
6578
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6579
|
+
.mockReturnValue(stable as any);
|
|
6580
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6581
|
+
|
|
6582
|
+
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
6583
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6584
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
6585
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
6586
|
+
resourcesSpy.mockRestore();
|
|
6587
|
+
});
|
|
6588
|
+
|
|
6589
|
+
test('downloads only changed resources from loader inventory', async () => {
|
|
6590
|
+
const method = new FirmwareUpdateV4({
|
|
6591
|
+
id: 1,
|
|
6592
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6593
|
+
});
|
|
6594
|
+
method.init();
|
|
6595
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6596
|
+
(type, index) => ({
|
|
6597
|
+
type,
|
|
6598
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6599
|
+
size: 0x52a0 + index + 1,
|
|
6600
|
+
fileHash: 'a'.repeat(64),
|
|
6601
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6602
|
+
})
|
|
6603
|
+
);
|
|
6604
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6605
|
+
(method as any).device = stubDevice({
|
|
6606
|
+
getCommands: () => ({ typedCall }),
|
|
6607
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6608
|
+
});
|
|
6609
|
+
const resourcesSpy = jest
|
|
6610
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6611
|
+
.mockReturnValue(stable as any);
|
|
6612
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6613
|
+
Promise.resolve({
|
|
6614
|
+
name: `${resource.type}.okpkg`,
|
|
6615
|
+
binary: new ArrayBuffer(resource.size),
|
|
6616
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6617
|
+
})
|
|
6618
|
+
);
|
|
6619
|
+
|
|
6620
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6621
|
+
|
|
6622
|
+
expect(bundles).toHaveLength(1);
|
|
6623
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6624
|
+
resourcesSpy.mockRestore();
|
|
6625
|
+
});
|
|
6626
|
+
|
|
6627
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
6628
|
+
const method = new FirmwareUpdateV4({
|
|
6629
|
+
id: 1,
|
|
6630
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6142
6631
|
});
|
|
6632
|
+
method.init();
|
|
6633
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6634
|
+
(type, index) => ({
|
|
6635
|
+
type,
|
|
6636
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6637
|
+
size: 0x52a0 + index + 1,
|
|
6638
|
+
fileHash: 'a'.repeat(64),
|
|
6639
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6640
|
+
})
|
|
6641
|
+
);
|
|
6642
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6143
6643
|
(method as any).device = stubDevice({
|
|
6144
6644
|
getCommands: () => ({ typedCall }),
|
|
6645
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6646
|
+
});
|
|
6647
|
+
const resourcesSpy = jest
|
|
6648
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6649
|
+
.mockReturnValue(stable as any);
|
|
6650
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6651
|
+
Promise.resolve({
|
|
6652
|
+
name: `${resource.type}.okpkg`,
|
|
6653
|
+
binary: new ArrayBuffer(resource.size),
|
|
6654
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6655
|
+
})
|
|
6656
|
+
);
|
|
6657
|
+
|
|
6658
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6659
|
+
|
|
6660
|
+
expect(bundles).toHaveLength(1);
|
|
6661
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6662
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
6663
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6664
|
+
resourcesSpy.mockRestore();
|
|
6665
|
+
});
|
|
6666
|
+
|
|
6667
|
+
test('does not resolve boot resources unless the optional target is selected', async () => {
|
|
6668
|
+
const method = new FirmwareUpdateV4({
|
|
6669
|
+
id: 1,
|
|
6670
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
6671
|
+
});
|
|
6672
|
+
method.init();
|
|
6673
|
+
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6674
|
+
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
6675
|
+
|
|
6676
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
6677
|
+
|
|
6678
|
+
expect(configSpy).not.toHaveBeenCalled();
|
|
6679
|
+
expect(downloadSpy).not.toHaveBeenCalled();
|
|
6680
|
+
});
|
|
6681
|
+
|
|
6682
|
+
test('downloads and maps selected boot resources as direct RES files', async () => {
|
|
6683
|
+
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
6684
|
+
const binary = bytes.buffer as ArrayBuffer;
|
|
6685
|
+
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
6686
|
+
const resource = {
|
|
6687
|
+
required: false as const,
|
|
6688
|
+
target: 'RES' as const,
|
|
6689
|
+
files: [
|
|
6690
|
+
{
|
|
6691
|
+
name: 'bootloader_crest.bin',
|
|
6692
|
+
url: 'https://example.com/bootloader_crest.bin',
|
|
6693
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6694
|
+
size: bytes.byteLength,
|
|
6695
|
+
fileHash,
|
|
6696
|
+
},
|
|
6697
|
+
],
|
|
6698
|
+
};
|
|
6699
|
+
const method = new FirmwareUpdateV4({
|
|
6700
|
+
id: 1,
|
|
6701
|
+
payload: {
|
|
6702
|
+
method: 'firmwareUpdateV4',
|
|
6703
|
+
platform: 'web',
|
|
6704
|
+
targetsToUpdate: ['boot_resources'],
|
|
6705
|
+
},
|
|
6706
|
+
});
|
|
6707
|
+
method.init();
|
|
6708
|
+
(method as any).device = stubDevice({ getCurrentDeviceType: () => 'pro2' });
|
|
6709
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
6710
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
6711
|
+
|
|
6712
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual([
|
|
6713
|
+
{
|
|
6714
|
+
name: 'bootloader_crest.bin',
|
|
6715
|
+
binary,
|
|
6716
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6717
|
+
},
|
|
6718
|
+
]);
|
|
6719
|
+
});
|
|
6720
|
+
|
|
6721
|
+
test('rejects a manually supplied resource file when its manifest hash does not match', () => {
|
|
6722
|
+
const method = new FirmwareUpdateV4({
|
|
6723
|
+
id: 1,
|
|
6724
|
+
payload: {
|
|
6725
|
+
method: 'firmwareUpdateV4',
|
|
6726
|
+
platform: 'web',
|
|
6727
|
+
resourceFiles: [
|
|
6728
|
+
{
|
|
6729
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
6730
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6731
|
+
size: 3,
|
|
6732
|
+
fileHash: '00'.repeat(32),
|
|
6733
|
+
},
|
|
6734
|
+
],
|
|
6735
|
+
},
|
|
6145
6736
|
});
|
|
6737
|
+
method.init();
|
|
6146
6738
|
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6739
|
+
expect(() => (method as any).prepareExplicitProtocolV2ResourceFiles()).toThrow(
|
|
6740
|
+
'SHA-256 mismatch'
|
|
6741
|
+
);
|
|
6150
6742
|
});
|
|
6151
6743
|
});
|
|
6152
6744
|
|
|
@@ -6265,334 +6857,253 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6265
6857
|
|
|
6266
6858
|
expect(method.protocolV2UiInteraction).toEqual({
|
|
6267
6859
|
request: 'button',
|
|
6268
|
-
source: 'method-lifecycle',
|
|
6269
|
-
reason: 'device-management',
|
|
6270
|
-
completion: 'operation-completed',
|
|
6271
|
-
deviceOnly: true,
|
|
6272
|
-
operation: 'change-label',
|
|
6273
|
-
});
|
|
6274
|
-
});
|
|
6275
|
-
|
|
6276
|
-
test.each([
|
|
6277
|
-
['deviceInfoGet', DeviceInfoGet],
|
|
6278
|
-
['deviceStatusGet', DeviceStatusGet],
|
|
6279
|
-
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6280
|
-
const method = new MethodClass({
|
|
6281
|
-
id: 1,
|
|
6282
|
-
payload: { method: _name },
|
|
6283
|
-
} as any);
|
|
6284
|
-
method.init();
|
|
6285
|
-
|
|
6286
|
-
expect(method.unlockPolicy).toBe('none');
|
|
6287
|
-
});
|
|
6288
|
-
|
|
6289
|
-
test('unlocks and retries an opted-in Protocol V2 method once', async () => {
|
|
6290
|
-
const calls: string[] = [];
|
|
6291
|
-
const method = {
|
|
6292
|
-
name: 'testBusinessMethod',
|
|
6293
|
-
unlockPolicy: 'retry-on-locked',
|
|
6294
|
-
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6295
|
-
run: jest
|
|
6296
|
-
.fn()
|
|
6297
|
-
.mockImplementationOnce(() => {
|
|
6298
|
-
calls.push('run-1');
|
|
6299
|
-
return Promise.reject(deviceLockedError());
|
|
6300
|
-
})
|
|
6301
|
-
.mockImplementationOnce(() => {
|
|
6302
|
-
calls.push('run-2');
|
|
6303
|
-
return Promise.resolve({ message: 'ok' });
|
|
6304
|
-
}),
|
|
6305
|
-
};
|
|
6306
|
-
const device = {
|
|
6307
|
-
isProtocolV2: () => true,
|
|
6308
|
-
unlockDevice: jest.fn(() => {
|
|
6309
|
-
calls.push('unlock');
|
|
6310
|
-
return Promise.resolve();
|
|
6311
|
-
}),
|
|
6312
|
-
};
|
|
6313
|
-
const uiCoordinator = {
|
|
6314
|
-
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6317
|
-
};
|
|
6318
|
-
|
|
6319
|
-
await expect(
|
|
6320
|
-
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6321
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6322
|
-
expect(calls).toEqual([
|
|
6323
|
-
'method-prompt',
|
|
6324
|
-
'run-1',
|
|
6325
|
-
'unlock-prompt',
|
|
6326
|
-
'unlock',
|
|
6327
|
-
'method-prompt',
|
|
6328
|
-
'run-2',
|
|
6329
|
-
]);
|
|
6330
|
-
});
|
|
6331
|
-
|
|
6332
|
-
test('restores the expected hidden-wallet session before retrying after unlock', async () => {
|
|
6333
|
-
const calls: string[] = [];
|
|
6334
|
-
const method = {
|
|
6335
|
-
name: 'evmSignMessage',
|
|
6336
|
-
payload: { passphraseState: 'hidden-state' },
|
|
6337
|
-
useDevicePassphraseState: true,
|
|
6338
|
-
unlockPolicy: 'retry-on-locked',
|
|
6339
|
-
run: jest
|
|
6340
|
-
.fn()
|
|
6341
|
-
.mockImplementationOnce(() => {
|
|
6342
|
-
calls.push('run-1');
|
|
6343
|
-
return Promise.reject(deviceLockedError());
|
|
6344
|
-
})
|
|
6345
|
-
.mockImplementationOnce(() => {
|
|
6346
|
-
calls.push('run-2');
|
|
6347
|
-
return Promise.resolve({ message: 'ok' });
|
|
6348
|
-
}),
|
|
6349
|
-
};
|
|
6350
|
-
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6351
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6352
|
-
calls.push('negotiate-session');
|
|
6353
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6354
|
-
}
|
|
6355
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6356
|
-
calls.push('resume-hidden-session');
|
|
6357
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6358
|
-
return Promise.resolve({
|
|
6359
|
-
message: {
|
|
6360
|
-
session_id: 'hidden-session',
|
|
6361
|
-
btc_test_address: 'hidden-state',
|
|
6362
|
-
},
|
|
6363
|
-
});
|
|
6364
|
-
}
|
|
6365
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6366
|
-
});
|
|
6367
|
-
const device = {
|
|
6368
|
-
features: { unlocked: true, passphraseProtection: true },
|
|
6369
|
-
passphraseState: 'hidden-state',
|
|
6370
|
-
commands: { typedCall },
|
|
6371
|
-
isProtocolV2: () => true,
|
|
6372
|
-
unlockDevice: jest.fn(() => {
|
|
6373
|
-
calls.push('unlock');
|
|
6374
|
-
return Promise.resolve();
|
|
6375
|
-
}),
|
|
6376
|
-
getCurrentPassphraseProtection: () => true,
|
|
6377
|
-
getInternalState: () => 'hidden-session',
|
|
6378
|
-
clearInternalState: jest.fn(),
|
|
6379
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6380
|
-
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6381
|
-
};
|
|
6382
|
-
|
|
6383
|
-
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6384
|
-
message: 'ok',
|
|
6860
|
+
source: 'method-lifecycle',
|
|
6861
|
+
reason: 'device-management',
|
|
6862
|
+
completion: 'operation-completed',
|
|
6863
|
+
deviceOnly: true,
|
|
6864
|
+
operation: 'change-label',
|
|
6385
6865
|
});
|
|
6386
|
-
expect(calls).toEqual([
|
|
6387
|
-
'run-1',
|
|
6388
|
-
'unlock',
|
|
6389
|
-
'negotiate-session',
|
|
6390
|
-
'resume-hidden-session',
|
|
6391
|
-
'validate-hidden-session',
|
|
6392
|
-
'run-2',
|
|
6393
|
-
]);
|
|
6394
6866
|
});
|
|
6395
6867
|
|
|
6396
|
-
test(
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
};
|
|
6406
|
-
const typedCall = jest.fn((requestType: string) => {
|
|
6407
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6408
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6409
|
-
}
|
|
6410
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6411
|
-
return Promise.reject(restoreError);
|
|
6412
|
-
}
|
|
6413
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6414
|
-
});
|
|
6415
|
-
const device = {
|
|
6416
|
-
features: { unlocked: true, passphraseProtection: true },
|
|
6417
|
-
passphraseState: 'hidden-state',
|
|
6418
|
-
commands: { typedCall },
|
|
6419
|
-
isProtocolV2: () => true,
|
|
6420
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6421
|
-
getCurrentPassphraseProtection: () => true,
|
|
6422
|
-
getInternalState: () => 'hidden-session',
|
|
6423
|
-
clearInternalState: jest.fn(),
|
|
6424
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6425
|
-
updateInternalState: jest.fn(),
|
|
6426
|
-
};
|
|
6868
|
+
test.each([
|
|
6869
|
+
['deviceInfoGet', DeviceInfoGet],
|
|
6870
|
+
['deviceStatusGet', DeviceStatusGet],
|
|
6871
|
+
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6872
|
+
const method = new MethodClass({
|
|
6873
|
+
id: 1,
|
|
6874
|
+
payload: { method: _name },
|
|
6875
|
+
} as any);
|
|
6876
|
+
method.init();
|
|
6427
6877
|
|
|
6428
|
-
|
|
6429
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6430
|
-
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6878
|
+
expect(method.unlockPolicy).toBe('none');
|
|
6433
6879
|
});
|
|
6434
6880
|
|
|
6435
|
-
test('
|
|
6881
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6436
6882
|
const calls: string[] = [];
|
|
6883
|
+
const features = { unlocked: true };
|
|
6437
6884
|
const method = {
|
|
6438
|
-
name: '
|
|
6439
|
-
|
|
6885
|
+
name: 'confluxSignMessageCIP23',
|
|
6886
|
+
unlockPolicy: 'none',
|
|
6440
6887
|
useDevicePassphraseState: true,
|
|
6441
|
-
|
|
6888
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6442
6889
|
run: jest.fn(() => {
|
|
6443
6890
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6891
|
+
return Promise.resolve({ address: '0x1' });
|
|
6445
6892
|
}),
|
|
6446
6893
|
};
|
|
6447
|
-
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6448
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6449
|
-
calls.push('negotiate-session');
|
|
6450
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6451
|
-
}
|
|
6452
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6453
|
-
calls.push('resume-hidden-session');
|
|
6454
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6455
|
-
return Promise.resolve({
|
|
6456
|
-
message: {
|
|
6457
|
-
session_id: 'hidden-session',
|
|
6458
|
-
btc_test_address: 'hidden-state',
|
|
6459
|
-
},
|
|
6460
|
-
});
|
|
6461
|
-
}
|
|
6462
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6463
|
-
});
|
|
6464
|
-
const features = {
|
|
6465
|
-
unlocked: false,
|
|
6466
|
-
unlockedAttachPin: true,
|
|
6467
|
-
passphraseProtection: true,
|
|
6468
|
-
};
|
|
6469
6894
|
const device = {
|
|
6470
6895
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6896
|
+
commands: {
|
|
6897
|
+
typedCall: jest.fn(() => {
|
|
6898
|
+
calls.push('status');
|
|
6899
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6900
|
+
}),
|
|
6901
|
+
},
|
|
6473
6902
|
isProtocolV2: () => true,
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
features.
|
|
6478
|
-
return
|
|
6903
|
+
isBootloader: () => false,
|
|
6904
|
+
isRomloader: () => false,
|
|
6905
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6906
|
+
features.unlocked = status.unlocked === true;
|
|
6907
|
+
return features;
|
|
6479
6908
|
}),
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6909
|
+
unlockDevice: jest.fn(),
|
|
6910
|
+
};
|
|
6911
|
+
const uiCoordinator = {
|
|
6912
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6913
|
+
enterUnlockInteraction: jest.fn(),
|
|
6485
6914
|
};
|
|
6486
6915
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
]);
|
|
6916
|
+
await expect(
|
|
6917
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6918
|
+
uiCoordinator: uiCoordinator as any,
|
|
6919
|
+
})
|
|
6920
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6921
|
+
|
|
6922
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6923
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6924
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6925
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6926
|
});
|
|
6499
6927
|
|
|
6500
|
-
test('
|
|
6928
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6929
|
+
const calls: string[] = [];
|
|
6930
|
+
const identityError = new Error('Unexpected device');
|
|
6501
6931
|
const method = {
|
|
6502
|
-
name: '
|
|
6503
|
-
|
|
6932
|
+
name: 'evmSignMessage',
|
|
6933
|
+
unlockPolicy: 'none',
|
|
6504
6934
|
useDevicePassphraseState: true,
|
|
6505
|
-
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6935
|
+
run: jest.fn(),
|
|
6507
6936
|
};
|
|
6937
|
+
const features = { unlocked: false };
|
|
6508
6938
|
const device = {
|
|
6509
|
-
features
|
|
6510
|
-
|
|
6939
|
+
features,
|
|
6940
|
+
commands: {
|
|
6941
|
+
typedCall: jest.fn(() => {
|
|
6942
|
+
calls.push('status');
|
|
6943
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6944
|
+
}),
|
|
6945
|
+
},
|
|
6511
6946
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6947
|
+
isBootloader: () => false,
|
|
6948
|
+
isRomloader: () => false,
|
|
6949
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6950
|
+
unlockDevice: jest.fn(),
|
|
6513
6951
|
};
|
|
6514
6952
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6953
|
+
await expect(
|
|
6954
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6955
|
+
afterStatusBeforeUnlock: () => {
|
|
6956
|
+
calls.push('identity');
|
|
6957
|
+
throw identityError;
|
|
6958
|
+
},
|
|
6959
|
+
})
|
|
6960
|
+
).rejects.toBe(identityError);
|
|
6961
|
+
|
|
6962
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6963
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6964
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6965
|
});
|
|
6521
6966
|
|
|
6522
|
-
test('
|
|
6967
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6523
6968
|
const calls: string[] = [];
|
|
6969
|
+
const features = { unlocked: true };
|
|
6524
6970
|
const method = {
|
|
6525
|
-
name: '
|
|
6971
|
+
name: 'evmSignMessage',
|
|
6526
6972
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
protocolV2UiInteraction: { reason: '
|
|
6973
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6528
6974
|
run: jest.fn(() => {
|
|
6529
6975
|
calls.push('run');
|
|
6530
|
-
return Promise.resolve({
|
|
6976
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6531
6977
|
}),
|
|
6532
6978
|
};
|
|
6533
|
-
const features = { unlocked: true };
|
|
6534
6979
|
const device = {
|
|
6535
6980
|
features,
|
|
6536
6981
|
commands: {
|
|
6537
6982
|
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('
|
|
6983
|
+
calls.push('status');
|
|
6539
6984
|
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
6985
|
}),
|
|
6541
6986
|
},
|
|
6542
6987
|
isProtocolV2: () => true,
|
|
6988
|
+
isBootloader: () => false,
|
|
6989
|
+
isRomloader: () => false,
|
|
6543
6990
|
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6544
|
-
features.unlocked = status.unlocked
|
|
6991
|
+
features.unlocked = status.unlocked === true;
|
|
6545
6992
|
return features;
|
|
6546
6993
|
}),
|
|
6547
6994
|
unlockDevice: jest.fn(() => {
|
|
6548
6995
|
calls.push('unlock');
|
|
6549
6996
|
features.unlocked = true;
|
|
6550
|
-
return Promise.resolve();
|
|
6997
|
+
return Promise.resolve(features);
|
|
6551
6998
|
}),
|
|
6552
6999
|
};
|
|
6553
7000
|
const uiCoordinator = {
|
|
6554
7001
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6555
|
-
enterUnlockInteraction: jest.fn(() =>
|
|
6556
|
-
|
|
7002
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
7003
|
+
calls.push('unlock-prompt');
|
|
7004
|
+
return undefined;
|
|
7005
|
+
}),
|
|
6557
7006
|
};
|
|
6558
7007
|
|
|
6559
7008
|
await expect(
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
7009
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
7010
|
+
uiCoordinator: uiCoordinator as any,
|
|
7011
|
+
prepare: () => {
|
|
7012
|
+
calls.push('wallet-session');
|
|
7013
|
+
return Promise.resolve();
|
|
7014
|
+
},
|
|
7015
|
+
})
|
|
7016
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
7017
|
+
|
|
7018
|
+
expect(calls).toEqual([
|
|
7019
|
+
'status',
|
|
7020
|
+
'unlock-prompt',
|
|
7021
|
+
'unlock',
|
|
7022
|
+
'wallet-session',
|
|
7023
|
+
'method-prompt',
|
|
7024
|
+
'run',
|
|
7025
|
+
]);
|
|
7026
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6564
7027
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
7028
|
});
|
|
6568
7029
|
|
|
6569
|
-
test('
|
|
6570
|
-
const
|
|
7030
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
7031
|
+
const method = {
|
|
7032
|
+
name: 'evmGetAddress',
|
|
7033
|
+
unlockPolicy: 'unlock-before-run',
|
|
7034
|
+
run: jest.fn(),
|
|
7035
|
+
};
|
|
7036
|
+
const device = {
|
|
7037
|
+
features: { unlocked: undefined },
|
|
7038
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
7039
|
+
isProtocolV2: () => true,
|
|
7040
|
+
isBootloader: () => false,
|
|
7041
|
+
isRomloader: () => false,
|
|
7042
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
7043
|
+
unlockDevice: jest.fn(),
|
|
7044
|
+
};
|
|
7045
|
+
|
|
7046
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
7047
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
7048
|
+
});
|
|
7049
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7050
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
7051
|
+
});
|
|
7052
|
+
|
|
7053
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
7054
|
+
const error = deviceLockedError();
|
|
6571
7055
|
const method = {
|
|
6572
|
-
name: '
|
|
7056
|
+
name: 'evmSignMessage',
|
|
6573
7057
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().
|
|
7058
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6575
7059
|
};
|
|
7060
|
+
const features = { unlocked: true };
|
|
6576
7061
|
const device = {
|
|
6577
7062
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
7063
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
7064
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
7065
|
+
isBootloader: () => false,
|
|
7066
|
+
isRomloader: () => false,
|
|
7067
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
7068
|
unlockDevice: jest.fn(),
|
|
6587
7069
|
};
|
|
6588
7070
|
|
|
6589
|
-
await expect(
|
|
6590
|
-
message: 'ok',
|
|
6591
|
-
});
|
|
7071
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
6592
7072
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6593
7073
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6594
7074
|
});
|
|
6595
7075
|
|
|
7076
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
7077
|
+
const features = { unlocked: true };
|
|
7078
|
+
const context = createProtocolV2UnlockContext();
|
|
7079
|
+
const firstMethod = {
|
|
7080
|
+
name: 'allNetworkGetAddress',
|
|
7081
|
+
unlockPolicy: 'unlock-before-run',
|
|
7082
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
7083
|
+
};
|
|
7084
|
+
const nestedMethod = {
|
|
7085
|
+
name: 'evmGetAddress',
|
|
7086
|
+
unlockPolicy: 'unlock-before-run',
|
|
7087
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
7088
|
+
};
|
|
7089
|
+
const device = {
|
|
7090
|
+
features,
|
|
7091
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
7092
|
+
isProtocolV2: () => true,
|
|
7093
|
+
isBootloader: () => false,
|
|
7094
|
+
isRomloader: () => false,
|
|
7095
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7096
|
+
unlockDevice: jest.fn(),
|
|
7097
|
+
};
|
|
7098
|
+
|
|
7099
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
7100
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
7101
|
+
|
|
7102
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
7103
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
7104
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
7105
|
+
});
|
|
7106
|
+
|
|
6596
7107
|
test.each(['bootloader', 'romloader'] as const)(
|
|
6597
7108
|
'skips DeviceStatus and unlock in Protocol V2 %s mode',
|
|
6598
7109
|
async mode => {
|
|
@@ -6610,7 +7121,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
7121
|
unlockDevice: jest.fn(),
|
|
6611
7122
|
};
|
|
6612
7123
|
|
|
6613
|
-
await expect(
|
|
7124
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
7125
|
message: 'updating',
|
|
6615
7126
|
});
|
|
6616
7127
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +7130,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
7130
|
}
|
|
6620
7131
|
);
|
|
6621
7132
|
|
|
6622
|
-
test('
|
|
6623
|
-
const method = {
|
|
6624
|
-
name: 'evmSignMessage',
|
|
6625
|
-
params: { message: 'not-exposed-in-event' },
|
|
6626
|
-
payload: {},
|
|
6627
|
-
unlockPolicy: 'retry-on-locked',
|
|
6628
|
-
run: jest.fn().mockResolvedValue({ signature: 'test' }),
|
|
6629
|
-
};
|
|
6630
|
-
const device = {
|
|
6631
|
-
isProtocolV2: () => true,
|
|
6632
|
-
unlockDevice: jest.fn(),
|
|
6633
|
-
};
|
|
6634
|
-
const uiCoordinator = {
|
|
6635
|
-
enterMethodInteraction: jest.fn(),
|
|
6636
|
-
enterUnlockInteraction: jest.fn(),
|
|
6637
|
-
resumeMethodInteraction: jest.fn(),
|
|
6638
|
-
};
|
|
6639
|
-
|
|
6640
|
-
await runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any);
|
|
6641
|
-
|
|
6642
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledWith({
|
|
6643
|
-
request: 'button',
|
|
6644
|
-
source: 'method-lifecycle',
|
|
6645
|
-
reason: 'signing-confirmation',
|
|
6646
|
-
completion: 'operation-completed',
|
|
6647
|
-
deviceOnly: true,
|
|
6648
|
-
operation: 'evmSignMessage',
|
|
6649
|
-
});
|
|
6650
|
-
});
|
|
6651
|
-
|
|
6652
|
-
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
7133
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
7134
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
7135
|
+
[false, 'unlock-before-run'],
|
|
6655
7136
|
[true, 'none'],
|
|
6656
7137
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
7138
|
const method = {
|
|
6659
7139
|
unlockPolicy,
|
|
6660
|
-
|
|
7140
|
+
useDevicePassphraseState: false,
|
|
7141
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
7142
|
};
|
|
6662
7143
|
const device = {
|
|
7144
|
+
commands: { typedCall: jest.fn() },
|
|
6663
7145
|
isProtocolV2: () => isProtocolV2,
|
|
6664
7146
|
unlockDevice: jest.fn(),
|
|
6665
7147
|
};
|
|
6666
7148
|
|
|
6667
|
-
await expect(
|
|
7149
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
7150
|
+
message: 'ok',
|
|
7151
|
+
});
|
|
7152
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
7153
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
7154
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
7155
|
}
|
|
6671
7156
|
});
|
|
6672
|
-
|
|
6673
|
-
test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
|
|
6674
|
-
const method = {
|
|
6675
|
-
name: 'uploadPortfolio',
|
|
6676
|
-
unlockPolicy: 'none',
|
|
6677
|
-
protocolV2UiMode: 'none',
|
|
6678
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6679
|
-
};
|
|
6680
|
-
const device = {
|
|
6681
|
-
features: { unlocked: false },
|
|
6682
|
-
isProtocolV2: () => true,
|
|
6683
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6684
|
-
};
|
|
6685
|
-
const uiCoordinator = {
|
|
6686
|
-
enterMethodInteraction: jest.fn(),
|
|
6687
|
-
enterUnlockInteraction: jest.fn(),
|
|
6688
|
-
resumeMethodInteraction: jest.fn(),
|
|
6689
|
-
};
|
|
6690
|
-
|
|
6691
|
-
await expect(
|
|
6692
|
-
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6693
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6694
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6695
|
-
expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
|
|
6696
|
-
expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
|
|
6697
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6698
|
-
});
|
|
6699
|
-
|
|
6700
|
-
test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
|
|
6701
|
-
const error = deviceLockedError();
|
|
6702
|
-
const method = {
|
|
6703
|
-
name: 'uploadPortfolio',
|
|
6704
|
-
unlockPolicy: 'none',
|
|
6705
|
-
run: jest.fn().mockRejectedValue(error),
|
|
6706
|
-
};
|
|
6707
|
-
const device = {
|
|
6708
|
-
features: { unlocked: true },
|
|
6709
|
-
isProtocolV2: () => true,
|
|
6710
|
-
unlockDevice: jest.fn(),
|
|
6711
|
-
};
|
|
6712
|
-
|
|
6713
|
-
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6714
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6715
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6716
|
-
});
|
|
6717
|
-
|
|
6718
|
-
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
6719
|
-
const initialError = deviceLockedError();
|
|
6720
|
-
const unlockError = new Error('PIN cancelled');
|
|
6721
|
-
const unlockFailMethod = {
|
|
6722
|
-
unlockPolicy: 'retry-on-locked',
|
|
6723
|
-
run: jest.fn().mockRejectedValue(initialError),
|
|
6724
|
-
};
|
|
6725
|
-
const unlockFailDevice = {
|
|
6726
|
-
isProtocolV2: () => true,
|
|
6727
|
-
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
6728
|
-
};
|
|
6729
|
-
const unlockFailCoordinator = {
|
|
6730
|
-
enterMethodInteraction: jest.fn(),
|
|
6731
|
-
enterUnlockInteraction: jest.fn(),
|
|
6732
|
-
resumeMethodInteraction: jest.fn(),
|
|
6733
|
-
};
|
|
6734
|
-
|
|
6735
|
-
await expect(
|
|
6736
|
-
runMethodWithUnlockRetry(
|
|
6737
|
-
unlockFailMethod as any,
|
|
6738
|
-
unlockFailDevice as any,
|
|
6739
|
-
unlockFailCoordinator as any
|
|
6740
|
-
)
|
|
6741
|
-
).rejects.toBe(unlockError);
|
|
6742
|
-
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
6743
|
-
expect(unlockFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6744
|
-
expect(unlockFailCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6745
|
-
|
|
6746
|
-
const retryError = deviceLockedError();
|
|
6747
|
-
const retryFailMethod = {
|
|
6748
|
-
unlockPolicy: 'retry-on-locked',
|
|
6749
|
-
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
6750
|
-
};
|
|
6751
|
-
const retryFailDevice = {
|
|
6752
|
-
isProtocolV2: () => true,
|
|
6753
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6754
|
-
};
|
|
6755
|
-
const retryFailCoordinator = {
|
|
6756
|
-
enterMethodInteraction: jest.fn(),
|
|
6757
|
-
enterUnlockInteraction: jest.fn(),
|
|
6758
|
-
resumeMethodInteraction: jest.fn(),
|
|
6759
|
-
};
|
|
6760
|
-
|
|
6761
|
-
await expect(
|
|
6762
|
-
runMethodWithUnlockRetry(
|
|
6763
|
-
retryFailMethod as any,
|
|
6764
|
-
retryFailDevice as any,
|
|
6765
|
-
retryFailCoordinator as any
|
|
6766
|
-
)
|
|
6767
|
-
).rejects.toBe(retryError);
|
|
6768
|
-
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
6769
|
-
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6770
|
-
expect(retryFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6771
|
-
expect(retryFailCoordinator.resumeMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6772
|
-
});
|
|
6773
7157
|
});
|
|
6774
7158
|
|
|
6775
7159
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +7285,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
7285
|
|
|
6902
7286
|
await method.run();
|
|
6903
7287
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7288
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7289
|
+
eventless_wallet_session: true,
|
|
7290
|
+
});
|
|
6905
7291
|
});
|
|
6906
7292
|
|
|
6907
7293
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -6977,6 +7363,29 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6977
7363
|
|
|
6978
7364
|
await expect(method.run()).resolves.toBe(features);
|
|
6979
7365
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
7366
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
7367
|
+
emitUiEvent: false,
|
|
7368
|
+
});
|
|
7369
|
+
});
|
|
7370
|
+
|
|
7371
|
+
test('forwards an explicit PIN type when unlocking a Protocol V2 device', async () => {
|
|
7372
|
+
const method = new DeviceUnlock({
|
|
7373
|
+
id: 1,
|
|
7374
|
+
payload: {
|
|
7375
|
+
method: 'deviceUnlock',
|
|
7376
|
+
pinType: DeviceSessionPinType.Any,
|
|
7377
|
+
},
|
|
7378
|
+
});
|
|
7379
|
+
method.init();
|
|
7380
|
+
|
|
7381
|
+
const unlockDevice = jest.fn().mockResolvedValue({ unlocked: true });
|
|
7382
|
+
(method as any).device = { unlockDevice };
|
|
7383
|
+
|
|
7384
|
+
await method.run();
|
|
7385
|
+
|
|
7386
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
7387
|
+
emitUiEvent: false,
|
|
7388
|
+
});
|
|
6980
7389
|
});
|
|
6981
7390
|
|
|
6982
7391
|
test.each([
|