@onekeyfe/hd-core 1.2.0-alpha.66 → 1.2.0-alpha.67
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 +0 -313
- package/__tests__/DeviceCommands.test.ts +13 -59
- package/__tests__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -198
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/device-lifecycle-events.test.ts +3 -105
- package/__tests__/device-pool-state.test.ts +0 -22
- package/__tests__/device-settings.test.ts +18 -39
- package/__tests__/device-state-contract.test.ts +0 -1
- package/__tests__/device-state-mapper.test.ts +1 -13
- package/__tests__/device-utils.test.ts +1 -48
- package/__tests__/deviceSettings.test.ts +1 -11
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +12 -132
- package/__tests__/homescreen.test.ts +1 -41
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +26 -3
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +0 -19
- package/__tests__/open-wallet-session.test.ts +37 -568
- package/__tests__/protocol-v2-unlock-policy.test.ts +37 -300
- package/__tests__/protocol-v2.test.ts +1153 -1458
- package/__tests__/protocolV2FileWrite.test.ts +0 -152
- package/__tests__/protocolV2UiInteraction.test.ts +0 -63
- package/__tests__/public-pro2-api-boundary.test.ts +0 -1
- package/__tests__/search-devices.test.ts +3 -12
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/dist/api/BaseMethod.d.ts +1 -3
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +27 -13
- 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 +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +11 -4
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -9
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -2
- 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/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 +0 -2
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -9
- 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 +6 -29
- 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 +1 -2
- 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 +0 -4
- 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 +2 -4
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -31
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +2 -10
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +247 -243
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3773 -3054
- package/dist/inject.d.ts +1 -6
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +4 -0
- 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/lockedError.d.ts +2 -0
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -6
- 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 +5 -3
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +1 -5
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -2
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +70 -4
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +10 -7
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +0 -3
- 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 +0 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +21 -38
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -4
- 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 +0 -4
- 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/pro2Wallpaper.d.ts +0 -10
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +10 -8
- package/src/api/CheckAllFirmwareRelease.ts +60 -59
- package/src/api/FirmwareUpdate.ts +15 -7
- package/src/api/FirmwareUpdateV2.ts +298 -126
- package/src/api/FirmwareUpdateV3.ts +265 -63
- package/src/api/FirmwareUpdateV4.ts +1017 -602
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -71
- package/src/api/SearchDevices.ts +1 -3
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -36
- package/src/api/device/DeviceSettings.ts +4 -5
- package/src/api/device/DeviceUnlock.ts +3 -8
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -27
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +144 -39
- package/src/api/helpers/protocolV2FileWrite.ts +63 -202
- package/src/api/index.ts +0 -2
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +13 -2
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
- 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 +0 -4
- package/src/core/index.ts +111 -213
- package/src/data/messages/messages-protocol-v2.json +0 -25
- package/src/data-manager/DataManager.ts +41 -113
- package/src/data-manager/TransportManager.ts +0 -6
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +49 -274
- package/src/device/DeviceCommands.ts +4 -31
- package/src/device/DeviceConnector.ts +13 -33
- package/src/device/DevicePool.ts +7 -21
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +0 -3
- package/src/events/device.ts +0 -4
- package/src/events/logBlockEvent.ts +0 -1
- package/src/events/ui-promise.ts +2 -4
- package/src/events/ui-request.ts +2 -36
- package/src/events/ui-response.ts +2 -12
- package/src/index.ts +5 -0
- package/src/inject.ts +24 -60
- package/src/lowLevelInject.ts +8 -7
- package/src/protocols/protocol-v2/features.ts +7 -10
- package/src/protocols/protocol-v2/firmware.ts +0 -2
- package/src/protocols/protocol-v2/lockedError.ts +10 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
- package/src/protocols/protocol-v2/walletSession.ts +36 -214
- package/src/topLevelInject.ts +8 -7
- package/src/types/api/checkAllFirmwareRelease.ts +5 -3
- package/src/types/api/deviceUnlock.ts +1 -12
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -1
- package/src/types/api/firmwareUpdate.ts +78 -6
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -13
- package/src/types/api/protocolV2.ts +0 -13
- package/src/types/device.ts +3 -15
- package/src/types/params.ts +1 -7
- package/src/types/settings.ts +22 -60
- package/src/utils/deviceFeaturesCompat.ts +6 -5
- package/src/utils/deviceFeaturesUtils.ts +3 -14
- package/src/utils/deviceInfoUtils.ts +0 -2
- package/src/utils/deviceSettings.ts +0 -3
- package/src/utils/homescreen.ts +6 -32
- package/src/utils/index.ts +1 -6
- package/src/utils/pro2Wallpaper.ts +8 -35
- package/__tests__/core-initialization.test.ts +0 -23
- package/__tests__/device-connector-protocol.test.ts +0 -81
- package/__tests__/deviceUploadNft.test.ts +0 -316
- package/__tests__/pro2Nft.test.ts +0 -108
- package/__tests__/protocol-binding.test.ts +0 -89
- package/__tests__/protocol-v2-resources.test.ts +0 -340
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
- package/__tests__/uiProgressThrottle.test.ts +0 -74
- package/__tests__/uiPromiseRegistry.test.ts +0 -115
- package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
- package/dist/core/uiPromiseRegistry.d.ts +0 -6
- package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -30
- package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/utils/pro2Nft.d.ts +0 -31
- package/dist/utils/pro2Nft.d.ts.map +0 -1
- package/dist/utils/uiProgressThrottle.d.ts +0 -3
- package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
- package/src/api/DetectDeviceConnectProtocol.ts +0 -18
- package/src/api/protocol-v2/DeviceUploadNft.ts +0 -197
- package/src/core/uiPromiseRegistry.ts +0 -63
- package/src/protocols/protocol-v2/resources.ts +0 -390
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -131
- package/src/types/api/detectDeviceConnectProtocol.ts +0 -6
- package/src/utils/pro2Nft.ts +0 -142
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { EFirmwareType,
|
|
1
|
+
import { EFirmwareType, 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';
|
|
9
8
|
|
|
10
9
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
11
10
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -62,7 +61,6 @@ import { createCoreApi } from '../src/inject';
|
|
|
62
61
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
63
62
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
64
63
|
import { DevicePool } from '../src/device/DevicePool';
|
|
65
|
-
import { DEVICE } from '../src/events';
|
|
66
64
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
67
65
|
import {
|
|
68
66
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -76,15 +74,11 @@ import {
|
|
|
76
74
|
requestProtocolV2ProtocolInfo,
|
|
77
75
|
supportsProtocolV2Message,
|
|
78
76
|
} from '../src/protocols/protocol-v2/features';
|
|
79
|
-
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
80
77
|
import {
|
|
81
78
|
getProtocolV2WalletSession,
|
|
82
79
|
refreshProtocolV2DeviceStatus,
|
|
83
80
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
84
|
-
import {
|
|
85
|
-
createProtocolV2UnlockContext,
|
|
86
|
-
runMethodWithUnlockPolicy,
|
|
87
|
-
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
81
|
+
import { runMethodWithUnlockRetry } from '../src/protocols/protocol-v2/unlockRetry';
|
|
88
82
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
89
83
|
import {
|
|
90
84
|
buildProtocolV1FeaturesPayload,
|
|
@@ -98,6 +92,7 @@ import {
|
|
|
98
92
|
getMethodVersionRange,
|
|
99
93
|
} from '../src/utils';
|
|
100
94
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
+
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
101
96
|
import {
|
|
102
97
|
getPassphraseState,
|
|
103
98
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -246,7 +241,7 @@ describe('UploadPortfolio', () => {
|
|
|
246
241
|
(method as any).device = device;
|
|
247
242
|
|
|
248
243
|
method.init();
|
|
249
|
-
await
|
|
244
|
+
await runMethodWithUnlockRetry(method, device as any);
|
|
250
245
|
|
|
251
246
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
252
247
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -422,16 +417,14 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
422
417
|
}
|
|
423
418
|
return d.features;
|
|
424
419
|
};
|
|
425
|
-
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
426
|
-
requestProtocolV2ProtocolInfo({
|
|
427
|
-
commands: d.getCommands?.() ?? d.commands,
|
|
428
|
-
timeoutMs,
|
|
429
|
-
});
|
|
430
420
|
d.probeProtocolV2RuntimeState ??= async (
|
|
431
421
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
432
422
|
timeoutMs?: number
|
|
433
423
|
) => {
|
|
434
|
-
const protocolInfo = await
|
|
424
|
+
const protocolInfo = await requestProtocolV2ProtocolInfo({
|
|
425
|
+
commands: d.getCommands(),
|
|
426
|
+
timeoutMs,
|
|
427
|
+
});
|
|
435
428
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
436
429
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
437
430
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -589,7 +582,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
589
582
|
} as any);
|
|
590
583
|
const protocolV2 = buildProtocolV2FeaturesPayload({
|
|
591
584
|
deviceInfo: {
|
|
592
|
-
hw: {
|
|
585
|
+
hw: { serial_no: 'P2-001' },
|
|
593
586
|
coprocessor: { bt_adv_name: 'Pro 2 BLE' },
|
|
594
587
|
},
|
|
595
588
|
deviceStatus: {
|
|
@@ -788,9 +781,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
788
781
|
if (request === 'DeviceStatusGet') {
|
|
789
782
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
790
783
|
}
|
|
791
|
-
if (request === 'DeviceSessionAskPassphrase') {
|
|
792
|
-
return { type: 'Success', message: {} };
|
|
793
|
-
}
|
|
794
784
|
if (request === 'DeviceSessionGet') {
|
|
795
785
|
return {
|
|
796
786
|
type: 'DeviceSession',
|
|
@@ -815,7 +805,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
815
805
|
).resolves.toEqual({
|
|
816
806
|
passphraseState: 'state-1',
|
|
817
807
|
newSession: 'session-1',
|
|
818
|
-
unlockedAttachPin:
|
|
808
|
+
unlockedAttachPin: undefined,
|
|
819
809
|
resumed: false,
|
|
820
810
|
});
|
|
821
811
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -824,13 +814,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
824
814
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
825
815
|
type: DeviceSessionPinType.Main,
|
|
826
816
|
});
|
|
827
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
828
|
-
passphrase: '',
|
|
829
|
-
on_device: false,
|
|
830
|
-
});
|
|
831
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
832
|
-
seed_domains: [],
|
|
833
|
-
});
|
|
817
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
834
818
|
device.passphraseState = 'state-1';
|
|
835
819
|
expect(device.getInternalState()).toBe('session-1');
|
|
836
820
|
});
|
|
@@ -863,7 +847,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
847
|
},
|
|
864
848
|
};
|
|
865
849
|
}
|
|
866
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
867
850
|
if (request === 'DeviceSessionGet') {
|
|
868
851
|
return {
|
|
869
852
|
message: {
|
|
@@ -880,16 +863,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
880
863
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
881
864
|
|
|
882
865
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
883
|
-
expect(
|
|
884
|
-
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
885
|
-
).toHaveLength(1);
|
|
886
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
887
|
-
seed_domains: [],
|
|
888
|
-
});
|
|
866
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
889
867
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
890
868
|
session_id: 'standard-session',
|
|
891
|
-
btc_test_address: 'standard-state',
|
|
892
|
-
seed_domains: [],
|
|
893
869
|
});
|
|
894
870
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
895
871
|
passphraseState: 'standard-state',
|
|
@@ -931,7 +907,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
931
907
|
},
|
|
932
908
|
};
|
|
933
909
|
}
|
|
934
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
935
910
|
if (request === 'DeviceSessionAskPin') {
|
|
936
911
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
937
912
|
}
|
|
@@ -1026,7 +1001,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1026
1001
|
if (request === 'DeviceStatusGet') {
|
|
1027
1002
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1028
1003
|
}
|
|
1029
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1030
1004
|
if (request === 'DeviceSessionGet') {
|
|
1031
1005
|
sessionGetCalls += 1;
|
|
1032
1006
|
return {
|
|
@@ -1253,9 +1227,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1253
1227
|
passphrase: 'host hidden wallet',
|
|
1254
1228
|
on_device: false,
|
|
1255
1229
|
});
|
|
1256
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1257
|
-
seed_domains: [],
|
|
1258
|
-
});
|
|
1230
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1259
1231
|
});
|
|
1260
1232
|
|
|
1261
1233
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1363,8 +1335,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1363
1335
|
|
|
1364
1336
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1365
1337
|
session_id: 'session-a',
|
|
1366
|
-
btc_test_address: 'state-a',
|
|
1367
|
-
seed_domains: [],
|
|
1368
1338
|
});
|
|
1369
1339
|
expect(device.getInternalState()).toBe('session-b');
|
|
1370
1340
|
});
|
|
@@ -1432,15 +1402,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1432
1402
|
});
|
|
1433
1403
|
expect(typedCall.mock.calls).toEqual([
|
|
1434
1404
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1435
|
-
[
|
|
1436
|
-
'DeviceSessionGet',
|
|
1437
|
-
'DeviceSession',
|
|
1438
|
-
{
|
|
1439
|
-
session_id: 'session-a',
|
|
1440
|
-
btc_test_address: 'state-a',
|
|
1441
|
-
seed_domains: [],
|
|
1442
|
-
},
|
|
1443
|
-
],
|
|
1405
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
1444
1406
|
]);
|
|
1445
1407
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1446
1408
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1488,7 +1450,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1488
1450
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1489
1451
|
],
|
|
1490
1452
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1491
|
-
['DeviceSessionGet', 'DeviceSession', {
|
|
1453
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1492
1454
|
]);
|
|
1493
1455
|
});
|
|
1494
1456
|
|
|
@@ -1591,73 +1553,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1591
1553
|
expect(device.getInternalState()).toBeUndefined();
|
|
1592
1554
|
});
|
|
1593
1555
|
|
|
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
|
-
|
|
1661
1556
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1662
1557
|
const device = Device.fromDescriptor({
|
|
1663
1558
|
id: 'cache-device-attach-recovery',
|
|
@@ -1714,17 +1609,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1714
1609
|
}),
|
|
1715
1610
|
{ cancelDeviceOnReject: false }
|
|
1716
1611
|
);
|
|
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
|
-
);
|
|
1612
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
|
|
1728
1613
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1729
1614
|
});
|
|
1730
1615
|
|
|
@@ -2008,10 +1893,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2008
1893
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
2009
1894
|
});
|
|
2010
1895
|
|
|
2011
|
-
test('
|
|
1896
|
+
test('marks an interactive cache miss as a wallet-session recovery', async () => {
|
|
2012
1897
|
const typedCall = jest
|
|
2013
1898
|
.fn()
|
|
2014
1899
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
+
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
+
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
2015
1902
|
.mockResolvedValueOnce({
|
|
2016
1903
|
message: {
|
|
2017
1904
|
btc_test_address: 'expected-state',
|
|
@@ -2039,11 +1926,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2039
1926
|
await expect(
|
|
2040
1927
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
2041
1928
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2042
|
-
expect(
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1929
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
1930
|
+
{
|
|
1931
|
+
existsAttachPinUser: false,
|
|
1932
|
+
deviceOnly: false,
|
|
1933
|
+
source: 'wallet-session-coordinator',
|
|
1934
|
+
reason: 'session-recovery',
|
|
1935
|
+
expectedPassphraseState: 'expected-state',
|
|
1936
|
+
},
|
|
1937
|
+
{ cancelDeviceOnReject: false }
|
|
1938
|
+
);
|
|
2047
1939
|
});
|
|
2048
1940
|
|
|
2049
1941
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2109,15 +2001,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2109
2001
|
true
|
|
2110
2002
|
);
|
|
2111
2003
|
expect(typedCall.mock.calls).toEqual([
|
|
2112
|
-
[
|
|
2113
|
-
|
|
2114
|
-
'DeviceSession',
|
|
2115
|
-
{
|
|
2116
|
-
session_id: 'session-pro2-app',
|
|
2117
|
-
btc_test_address: 'state-pro2-app',
|
|
2118
|
-
seed_domains: [],
|
|
2119
|
-
},
|
|
2120
|
-
],
|
|
2004
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
2005
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
2121
2006
|
]);
|
|
2122
2007
|
});
|
|
2123
2008
|
|
|
@@ -2306,9 +2191,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2306
2191
|
passphrase: 'host hidden wallet',
|
|
2307
2192
|
on_device: false,
|
|
2308
2193
|
});
|
|
2309
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2310
|
-
seed_domains: [],
|
|
2311
|
-
});
|
|
2194
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2312
2195
|
});
|
|
2313
2196
|
|
|
2314
2197
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2386,83 +2269,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2386
2269
|
);
|
|
2387
2270
|
|
|
2388
2271
|
expect(device.getInternalState()).toBeUndefined();
|
|
2389
|
-
expect(typedCall).
|
|
2390
|
-
btc_test_address: 'expected-state',
|
|
2391
|
-
seed_domains: [],
|
|
2392
|
-
});
|
|
2393
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2272
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2394
2273
|
passphrase: 'host hidden wallet',
|
|
2395
2274
|
on_device: false,
|
|
2396
2275
|
});
|
|
2397
|
-
expect(typedCall).
|
|
2398
|
-
expect(typedCall).
|
|
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);
|
|
2276
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2277
|
+
expect(typedCall).toHaveBeenNthCalledWith(4, 'DeviceSessionGet', 'DeviceSession', {});
|
|
2466
2278
|
});
|
|
2467
2279
|
|
|
2468
2280
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2495,22 +2307,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2495
2307
|
await expect(
|
|
2496
2308
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2497
2309
|
).resolves.toBe(true);
|
|
2498
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2310
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
2499
2311
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2500
2312
|
eventless_wallet_session: true,
|
|
2501
2313
|
});
|
|
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());
|
|
2514
2314
|
});
|
|
2515
2315
|
|
|
2516
2316
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2581,7 +2381,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2581
2381
|
uuid: 'CACHED-SERIAL',
|
|
2582
2382
|
deviceId: null,
|
|
2583
2383
|
bleName: 'Cached BLE',
|
|
2584
|
-
name: 'Cached
|
|
2384
|
+
name: 'Cached Label',
|
|
2585
2385
|
label: 'Cached Label',
|
|
2586
2386
|
deviceType: 'pro2',
|
|
2587
2387
|
});
|
|
@@ -2608,7 +2408,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2608
2408
|
expect(device.features?.label).toBeNull();
|
|
2609
2409
|
});
|
|
2610
2410
|
|
|
2611
|
-
test('
|
|
2411
|
+
test('prefers the real Protocol V2 label over the BLE name for display', () => {
|
|
2612
2412
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2613
2413
|
(device as any).features = {
|
|
2614
2414
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
@@ -2616,10 +2416,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2616
2416
|
bleName: 'Pro2 6136',
|
|
2617
2417
|
};
|
|
2618
2418
|
|
|
2619
|
-
expect(device.getCurrentDisplayName()).toBe('My Pro 2');
|
|
2620
2419
|
expect(device.toMessageObject()).toMatchObject({
|
|
2621
2420
|
bleName: 'Pro2 6136',
|
|
2622
|
-
name: '
|
|
2421
|
+
name: 'My Pro 2',
|
|
2623
2422
|
label: 'My Pro 2',
|
|
2624
2423
|
});
|
|
2625
2424
|
});
|
|
@@ -2673,6 +2472,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2673
2472
|
hw: true,
|
|
2674
2473
|
fw: true,
|
|
2675
2474
|
coprocessor: true,
|
|
2475
|
+
se1: true,
|
|
2476
|
+
se2: true,
|
|
2477
|
+
se3: true,
|
|
2478
|
+
se4: true,
|
|
2676
2479
|
},
|
|
2677
2480
|
types: {
|
|
2678
2481
|
version: true,
|
|
@@ -2696,10 +2499,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2696
2499
|
).rejects.toThrow('DeviceInfo not supported');
|
|
2697
2500
|
});
|
|
2698
2501
|
|
|
2699
|
-
test('does not inherit Pro or
|
|
2700
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2701
|
-
hw: { Device_type: DeviceType.PRO2 },
|
|
2702
|
-
});
|
|
2502
|
+
test('does not inherit Pro or Pro model fallback ranges for Protocol V2 devices', () => {
|
|
2503
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any);
|
|
2703
2504
|
const checkedTypes: string[] = [];
|
|
2704
2505
|
|
|
2705
2506
|
const versionRange = getMethodVersionRange(features, type => {
|
|
@@ -2711,13 +2512,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2711
2512
|
});
|
|
2712
2513
|
|
|
2713
2514
|
expect(versionRange).toBeUndefined();
|
|
2714
|
-
expect(checkedTypes).toEqual(['pro2'
|
|
2515
|
+
expect(checkedTypes).toEqual(['pro2']);
|
|
2715
2516
|
});
|
|
2716
2517
|
|
|
2717
2518
|
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
2718
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2719
|
-
hw: { Device_type: DeviceType.PRO2 },
|
|
2720
|
-
});
|
|
2519
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any);
|
|
2721
2520
|
|
|
2722
2521
|
expect(
|
|
2723
2522
|
getFirmwareUpdateField({
|
|
@@ -2868,6 +2667,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2868
2667
|
hw: true,
|
|
2869
2668
|
fw: true,
|
|
2870
2669
|
coprocessor: true,
|
|
2670
|
+
se1: true,
|
|
2671
|
+
se2: true,
|
|
2672
|
+
se3: true,
|
|
2673
|
+
se4: true,
|
|
2871
2674
|
},
|
|
2872
2675
|
types: {
|
|
2873
2676
|
version: true,
|
|
@@ -2878,9 +2681,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2878
2681
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2879
2682
|
}
|
|
2880
2683
|
);
|
|
2881
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2882
|
-
eventless_wallet_session: true,
|
|
2883
|
-
});
|
|
2684
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2884
2685
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2885
2686
|
});
|
|
2886
2687
|
|
|
@@ -2918,9 +2719,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2918
2719
|
}),
|
|
2919
2720
|
expect.anything()
|
|
2920
2721
|
);
|
|
2921
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2922
|
-
eventless_wallet_session: true,
|
|
2923
|
-
});
|
|
2722
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2924
2723
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2925
2724
|
expect(device.features).toMatchObject({
|
|
2926
2725
|
deviceId: null,
|
|
@@ -3186,6 +2985,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3186
2985
|
fw: { application: { version: '1.2.4' } },
|
|
3187
2986
|
},
|
|
3188
2987
|
})
|
|
2988
|
+
.mockResolvedValueOnce({
|
|
2989
|
+
type: 'ProtocolInfo',
|
|
2990
|
+
message: protocolV2ApplicationInfo,
|
|
2991
|
+
})
|
|
3189
2992
|
.mockResolvedValueOnce({
|
|
3190
2993
|
type: 'DeviceStatus',
|
|
3191
2994
|
message: { init_states: true, unlocked: true },
|
|
@@ -3207,7 +3010,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3207
3010
|
firmwareVersion: '1.2.4',
|
|
3208
3011
|
passphraseProtection: null,
|
|
3209
3012
|
});
|
|
3210
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3013
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
3211
3014
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3212
3015
|
4,
|
|
3213
3016
|
'DeviceInfoGet',
|
|
@@ -3217,6 +3020,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3217
3020
|
hw: true,
|
|
3218
3021
|
fw: true,
|
|
3219
3022
|
coprocessor: true,
|
|
3023
|
+
se1: true,
|
|
3024
|
+
se2: true,
|
|
3025
|
+
se3: true,
|
|
3026
|
+
se4: true,
|
|
3220
3027
|
},
|
|
3221
3028
|
types: {
|
|
3222
3029
|
version: true,
|
|
@@ -3317,23 +3124,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3317
3124
|
status: { init_states: true },
|
|
3318
3125
|
}
|
|
3319
3126
|
);
|
|
3320
|
-
const pinOnDevice = jest.fn();
|
|
3321
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3322
3127
|
|
|
3323
3128
|
const features = await device.unlockDevice();
|
|
3324
3129
|
|
|
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
|
-
);
|
|
3337
3130
|
expect(typedCall.mock.calls).toEqual([
|
|
3338
3131
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3339
3132
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3379,12 +3172,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3379
3172
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3380
3173
|
});
|
|
3381
3174
|
(device as any).commands = { typedCall };
|
|
3382
|
-
const pinOnDevice = jest.fn();
|
|
3383
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3384
3175
|
|
|
3385
|
-
await device.unlockDevice(
|
|
3176
|
+
await device.unlockDevice();
|
|
3386
3177
|
|
|
3387
|
-
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3388
3178
|
expect(typedCall.mock.calls).toEqual([
|
|
3389
3179
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3390
3180
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3627,77 +3417,71 @@ describe('API compatibility handling', () => {
|
|
|
3627
3417
|
});
|
|
3628
3418
|
});
|
|
3629
3419
|
|
|
3630
|
-
test
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
deviceType,
|
|
3635
|
-
} as Features;
|
|
3420
|
+
test('allows Pro2 Solana signing methods through Protocol V2 version checks', () => {
|
|
3421
|
+
const features = {
|
|
3422
|
+
deviceType: 'pro2',
|
|
3423
|
+
} as Features;
|
|
3636
3424
|
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3425
|
+
const solSignMessageMethod = new SolSignMessage({
|
|
3426
|
+
id: 1,
|
|
3427
|
+
payload: {
|
|
3428
|
+
method: 'solSignMessage',
|
|
3429
|
+
path: "m/44'/501'/0'/0'",
|
|
3430
|
+
messageHex: '48656c6c6f',
|
|
3431
|
+
},
|
|
3432
|
+
});
|
|
3433
|
+
const solSignOffchainMessageMethod = new SolSignOffchainMessage({
|
|
3434
|
+
id: 2,
|
|
3435
|
+
payload: {
|
|
3436
|
+
method: 'solSignOffchainMessage',
|
|
3437
|
+
path: "m/44'/501'/0'/0'",
|
|
3438
|
+
messageHex: '48656c6c6f',
|
|
3439
|
+
},
|
|
3440
|
+
});
|
|
3441
|
+
const solSignTransactionMethod = new SolSignTransaction({
|
|
3442
|
+
id: 3,
|
|
3443
|
+
payload: {
|
|
3444
|
+
method: 'solSignTransaction',
|
|
3445
|
+
path: "m/44'/501'/0'/0'",
|
|
3446
|
+
rawTx: '00',
|
|
3447
|
+
},
|
|
3448
|
+
});
|
|
3449
|
+
const solSignVersionedTransactionMethod = new SolSignTransaction({
|
|
3450
|
+
id: 4,
|
|
3451
|
+
payload: {
|
|
3452
|
+
method: 'solSignTransaction',
|
|
3453
|
+
path: "m/44'/501'/0'/0'",
|
|
3454
|
+
rawTx: '80',
|
|
3455
|
+
},
|
|
3456
|
+
});
|
|
3669
3457
|
|
|
3670
|
-
|
|
3671
|
-
|
|
3458
|
+
solSignTransactionMethod.init();
|
|
3459
|
+
solSignVersionedTransactionMethod.init();
|
|
3672
3460
|
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
min: '0.0.0',
|
|
3698
|
-
});
|
|
3699
|
-
}
|
|
3700
|
-
);
|
|
3461
|
+
expect(
|
|
3462
|
+
getMethodVersionRange(features, type => solSignMessageMethod.getVersionRange()[type])
|
|
3463
|
+
).toEqual({
|
|
3464
|
+
min: '0.0.0',
|
|
3465
|
+
});
|
|
3466
|
+
expect(
|
|
3467
|
+
getMethodVersionRange(features, type => solSignOffchainMessageMethod.getVersionRange()[type])
|
|
3468
|
+
).toEqual({
|
|
3469
|
+
min: '0.0.0',
|
|
3470
|
+
});
|
|
3471
|
+
expect(
|
|
3472
|
+
getMethodVersionRange(features, type => solSignTransactionMethod.getVersionRange()[type])
|
|
3473
|
+
).toEqual({
|
|
3474
|
+
min: '0.0.0',
|
|
3475
|
+
});
|
|
3476
|
+
expect(
|
|
3477
|
+
getMethodVersionRange(
|
|
3478
|
+
features,
|
|
3479
|
+
type => solSignVersionedTransactionMethod.getVersionRange()[type]
|
|
3480
|
+
)
|
|
3481
|
+
).toEqual({
|
|
3482
|
+
min: '0.0.0',
|
|
3483
|
+
});
|
|
3484
|
+
});
|
|
3701
3485
|
|
|
3702
3486
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
3703
3487
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
@@ -3793,15 +3577,6 @@ describe('API compatibility handling', () => {
|
|
|
3793
3577
|
|
|
3794
3578
|
describe('Protocol V2 firmware update targets', () => {
|
|
3795
3579
|
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
|
-
});
|
|
3805
3580
|
|
|
3806
3581
|
const buildOkppHeader = ({
|
|
3807
3582
|
type = 'RESC',
|
|
@@ -3926,7 +3701,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3926
3701
|
specific: true,
|
|
3927
3702
|
},
|
|
3928
3703
|
},
|
|
3929
|
-
{ timeoutMs:
|
|
3704
|
+
{ timeoutMs: 5000 }
|
|
3930
3705
|
);
|
|
3931
3706
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3932
3707
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3942,7 +3717,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3942
3717
|
});
|
|
3943
3718
|
});
|
|
3944
3719
|
|
|
3945
|
-
test('
|
|
3720
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
3946
3721
|
const method = new FirmwareUpdateV4({
|
|
3947
3722
|
id: 1,
|
|
3948
3723
|
payload: {
|
|
@@ -3977,7 +3752,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3977
3752
|
|
|
3978
3753
|
await method.run();
|
|
3979
3754
|
|
|
3980
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3755
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3981
3756
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3982
3757
|
fwBinaryMap: [
|
|
3983
3758
|
{
|
|
@@ -3988,72 +3763,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3988
3763
|
],
|
|
3989
3764
|
bootloaderBinary: null,
|
|
3990
3765
|
});
|
|
3991
|
-
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3992
3766
|
});
|
|
3993
3767
|
|
|
3994
|
-
test('
|
|
3995
|
-
const method = new FirmwareUpdateV4({
|
|
3996
|
-
id: 1,
|
|
3997
|
-
payload: {
|
|
3998
|
-
method: 'firmwareUpdateV4',
|
|
3999
|
-
platform: 'web',
|
|
4000
|
-
targetsToUpdate: ['resource'],
|
|
4001
|
-
},
|
|
4002
|
-
});
|
|
4003
|
-
method.init();
|
|
4004
|
-
|
|
4005
|
-
(method as any).device = stubDevice({
|
|
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
|
-
},
|
|
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 () => {
|
|
3768
|
+
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
4057
3769
|
const method = new FirmwareUpdateV4({
|
|
4058
3770
|
id: 1,
|
|
4059
3771
|
payload: {
|
|
@@ -4064,10 +3776,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4064
3776
|
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4065
3777
|
features: { bootloader_mode: false, capabilities: [] },
|
|
4066
3778
|
isBootloader: () => false,
|
|
4067
|
-
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4068
|
-
mode: 'bootloader',
|
|
4069
|
-
bootloaderMode: true,
|
|
4070
|
-
}),
|
|
4071
3779
|
});
|
|
4072
3780
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4073
3781
|
message: 'Device rebooted successfully',
|
|
@@ -4095,7 +3803,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4095
3803
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4096
3804
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
4097
3805
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4098
|
-
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4099
3806
|
method.postTipMessage = jest.fn();
|
|
4100
3807
|
|
|
4101
3808
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
@@ -4107,66 +3814,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4107
3814
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4108
3815
|
});
|
|
4109
3816
|
|
|
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
|
-
|
|
4170
3817
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
4171
3818
|
const method = new FirmwareUpdateV4({
|
|
4172
3819
|
id: 1,
|
|
@@ -4219,9 +3866,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4219
3866
|
},
|
|
4220
3867
|
});
|
|
4221
3868
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4222
|
-
const prepareResourceBundles = jest
|
|
4223
|
-
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4224
|
-
.mockResolvedValue(undefined);
|
|
4225
3869
|
(method as any).protocolV2Reboot = jest.fn();
|
|
4226
3870
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
4227
3871
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -4234,7 +3878,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4234
3878
|
|
|
4235
3879
|
await method.run();
|
|
4236
3880
|
|
|
4237
|
-
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
4238
3881
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4239
3882
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
4240
3883
|
bootloaderBinary: null,
|
|
@@ -4254,10 +3897,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4254
3897
|
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4255
3898
|
features: { bootloader_mode: false, capabilities: [] },
|
|
4256
3899
|
isBootloader: () => false,
|
|
4257
|
-
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4258
|
-
mode: 'bootloader',
|
|
4259
|
-
bootloaderMode: true,
|
|
4260
|
-
}),
|
|
4261
3900
|
});
|
|
4262
3901
|
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
4263
3902
|
if (requestType === 'DeviceInfoGet') {
|
|
@@ -4285,13 +3924,15 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4285
3924
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4286
3925
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
4287
3926
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4288
|
-
(method as any).protocolV2ExpectedPath = 'app-path';
|
|
4289
3927
|
|
|
4290
3928
|
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
4291
3929
|
|
|
4292
3930
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
4293
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
4294
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3931
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
3932
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3933
|
+
'DeviceInfoGet',
|
|
3934
|
+
'ProtocolInfoRequest',
|
|
3935
|
+
]);
|
|
4295
3936
|
});
|
|
4296
3937
|
|
|
4297
3938
|
test('does not run generic initialize during Protocol V2 USB firmware reconnect', async () => {
|
|
@@ -4319,16 +3960,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4319
3960
|
getCommands: () => commands,
|
|
4320
3961
|
mainId: 'usb-path',
|
|
4321
3962
|
});
|
|
4322
|
-
const cachedDevice = {
|
|
4323
|
-
getConnectId: () => 'usb-path',
|
|
4324
|
-
getCurrentSerialNo: () => '',
|
|
4325
|
-
};
|
|
3963
|
+
const cachedDevice = { getConnectId: () => 'usb-path' };
|
|
4326
3964
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4327
3965
|
devices: {},
|
|
4328
3966
|
deviceList: [cachedDevice],
|
|
4329
3967
|
} as any);
|
|
4330
3968
|
(method as any).device = device;
|
|
4331
|
-
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4332
3969
|
|
|
4333
3970
|
try {
|
|
4334
3971
|
await (method as any).reconnectProtocolV2Device();
|
|
@@ -4340,184 +3977,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4340
3977
|
expect(initialize).not.toHaveBeenCalled();
|
|
4341
3978
|
});
|
|
4342
3979
|
|
|
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
|
-
|
|
4521
3980
|
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4522
3981
|
const method = new FirmwareUpdateV4({
|
|
4523
3982
|
id: 1,
|
|
@@ -4559,7 +4018,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4559
4018
|
deviceList: [{ getConnectId: () => 'usb-path' }],
|
|
4560
4019
|
} as any);
|
|
4561
4020
|
(method as any).device = device;
|
|
4562
|
-
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4563
4021
|
|
|
4564
4022
|
try {
|
|
4565
4023
|
await (method as any).reconnectProtocolV2Device();
|
|
@@ -4590,7 +4048,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4590
4048
|
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
4591
4049
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4592
4050
|
(method as any).device = stubDevice({
|
|
4593
|
-
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4594
4051
|
getCommands: () => ({ typedCall }),
|
|
4595
4052
|
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4596
4053
|
deviceId: 'changed-device',
|
|
@@ -4614,9 +4071,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4614
4071
|
});
|
|
4615
4072
|
method.postTipMessage = jest.fn();
|
|
4616
4073
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4617
|
-
|
|
4618
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4619
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4074
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4620
4075
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4621
4076
|
message: 'Device rebooted successfully',
|
|
4622
4077
|
});
|
|
@@ -4625,29 +4080,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
4080
|
|
|
4626
4081
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4627
4082
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4628
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4629
4083
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4630
4084
|
});
|
|
4631
4085
|
|
|
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
|
-
|
|
4651
4086
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4652
4087
|
const method = new FirmwareUpdateV4({
|
|
4653
4088
|
id: 1,
|
|
@@ -4690,48 +4125,27 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4690
4125
|
});
|
|
4691
4126
|
});
|
|
4692
4127
|
|
|
4693
|
-
test('
|
|
4128
|
+
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4694
4129
|
const method = new FirmwareUpdateV4({
|
|
4695
4130
|
id: 1,
|
|
4696
4131
|
payload: {
|
|
4697
4132
|
method: 'firmwareUpdateV4',
|
|
4698
4133
|
},
|
|
4699
4134
|
});
|
|
4700
|
-
const typedCall = jest
|
|
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
|
+
);
|
|
4701
4143
|
|
|
4702
4144
|
(method as any).device = stubDevice({
|
|
4703
4145
|
getCommands: () => ({ typedCall }),
|
|
4704
4146
|
});
|
|
4705
|
-
|
|
4706
|
-
|
|
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();
|
|
4147
|
+
method.postTipMessage = jest.fn();
|
|
4148
|
+
method.postProgressMessage = jest.fn();
|
|
4735
4149
|
|
|
4736
4150
|
await expect(
|
|
4737
4151
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
@@ -4801,7 +4215,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4801
4215
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4802
4216
|
]);
|
|
4803
4217
|
|
|
4804
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4218
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
4805
4219
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4806
4220
|
'DeviceFirmwareUpdateStatusGet',
|
|
4807
4221
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4839,46 +4253,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4839
4253
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4840
4254
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4841
4255
|
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');
|
|
4882
4256
|
});
|
|
4883
4257
|
|
|
4884
4258
|
test.each([
|
|
@@ -4944,65 +4318,54 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4944
4318
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4945
4319
|
});
|
|
4946
4320
|
|
|
4947
|
-
test('
|
|
4321
|
+
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
4948
4322
|
const method = new FirmwareUpdateV4({
|
|
4949
4323
|
id: 1,
|
|
4950
4324
|
payload: {
|
|
4951
4325
|
method: 'firmwareUpdateV4',
|
|
4326
|
+
platform: 'desktop',
|
|
4327
|
+
expectedTargetVersions: {
|
|
4328
|
+
app_v1: '2.0.0',
|
|
4329
|
+
},
|
|
4952
4330
|
},
|
|
4953
4331
|
});
|
|
4954
|
-
|
|
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);
|
|
4332
|
+
method.init();
|
|
4970
4333
|
method.postProgressMessage = jest.fn();
|
|
4971
4334
|
|
|
4972
|
-
|
|
4973
|
-
(method as any).
|
|
4974
|
-
{ target_id:
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
).
|
|
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');
|
|
4335
|
+
expect(() =>
|
|
4336
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
4337
|
+
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
4338
|
+
new Set([4])
|
|
4339
|
+
)
|
|
4340
|
+
).toThrow('expected 131072');
|
|
4987
4341
|
});
|
|
4988
4342
|
|
|
4989
|
-
test('fails
|
|
4343
|
+
test('fails install polling immediately with the original firmware verification error', async () => {
|
|
4990
4344
|
const method = new FirmwareUpdateV4({
|
|
4991
4345
|
id: 1,
|
|
4992
4346
|
payload: {
|
|
4993
4347
|
method: 'firmwareUpdateV4',
|
|
4348
|
+
platform: 'desktop',
|
|
4349
|
+
expectedTargetVersions: {
|
|
4350
|
+
app_v1: '2.0.0',
|
|
4351
|
+
},
|
|
4994
4352
|
},
|
|
4995
4353
|
});
|
|
4354
|
+
method.init();
|
|
4996
4355
|
const typedCall = jest.fn().mockResolvedValue({
|
|
4997
4356
|
type: 'DeviceFirmwareUpdateStatus',
|
|
4998
4357
|
message: {
|
|
4999
|
-
records: [{ target_id: 4, status: 2 }],
|
|
4358
|
+
records: [{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
5000
4359
|
},
|
|
5001
4360
|
});
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
4361
|
+
const dateNowSpy = jest
|
|
4362
|
+
.spyOn(Date, 'now')
|
|
4363
|
+
.mockReturnValueOnce(0)
|
|
4364
|
+
.mockReturnValueOnce(0)
|
|
4365
|
+
.mockReturnValue(300_001);
|
|
4366
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
4367
|
+
callback: () => void
|
|
4368
|
+
) => {
|
|
5006
4369
|
callback();
|
|
5007
4370
|
return 0 as any;
|
|
5008
4371
|
}) as typeof setTimeout);
|
|
@@ -5012,52 +4375,45 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5012
4375
|
});
|
|
5013
4376
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5014
4377
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5015
|
-
method.postProgressMessage = jest.fn();
|
|
5016
4378
|
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
4379
|
+
try {
|
|
4380
|
+
await expect(
|
|
4381
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4382
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4383
|
+
])
|
|
4384
|
+
).rejects.toMatchObject({
|
|
4385
|
+
errorCode: HardwareErrorCode.FirmwareVerificationFailed,
|
|
4386
|
+
message: expect.stringContaining('reached payload version 65536, expected 131072'),
|
|
4387
|
+
});
|
|
4388
|
+
} finally {
|
|
4389
|
+
dateNowSpy.mockRestore();
|
|
4390
|
+
setTimeoutSpy.mockRestore();
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
5027
4394
|
});
|
|
5028
4395
|
|
|
5029
|
-
test('
|
|
4396
|
+
test('fails install polling immediately with the original status conflict error', async () => {
|
|
5030
4397
|
const method = new FirmwareUpdateV4({
|
|
5031
4398
|
id: 1,
|
|
5032
4399
|
payload: {
|
|
5033
4400
|
method: 'firmwareUpdateV4',
|
|
5034
4401
|
},
|
|
5035
4402
|
});
|
|
5036
|
-
const typedCall = jest
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
.
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
records: [
|
|
5051
|
-
{ target_id: 3, status: 2 },
|
|
5052
|
-
{ target_id: 4, status: 2 },
|
|
5053
|
-
],
|
|
5054
|
-
},
|
|
5055
|
-
});
|
|
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;
|
|
4403
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4404
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4405
|
+
message: {
|
|
4406
|
+
records: [{ target_id: 4, status: 2, path: 'vol0:/unexpected.bin' }],
|
|
4407
|
+
},
|
|
4408
|
+
});
|
|
4409
|
+
const dateNowSpy = jest
|
|
4410
|
+
.spyOn(Date, 'now')
|
|
4411
|
+
.mockReturnValueOnce(0)
|
|
4412
|
+
.mockReturnValueOnce(0)
|
|
4413
|
+
.mockReturnValue(300_001);
|
|
4414
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
4415
|
+
callback: () => void
|
|
4416
|
+
) => {
|
|
5061
4417
|
callback();
|
|
5062
4418
|
return 0 as any;
|
|
5063
4419
|
}) as typeof setTimeout);
|
|
@@ -5065,70 +4421,118 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5065
4421
|
(method as any).device = stubDevice({
|
|
5066
4422
|
getCommands: () => ({ typedCall }),
|
|
5067
4423
|
});
|
|
5068
|
-
(method as any).reconnectProtocolV2Device =
|
|
4424
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5069
4425
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5070
|
-
method.postProgressMessage = jest.fn();
|
|
5071
4426
|
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
4427
|
+
try {
|
|
4428
|
+
await expect(
|
|
4429
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4430
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4431
|
+
])
|
|
4432
|
+
).rejects.toMatchObject({
|
|
4433
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
4434
|
+
message: 'Protocol V2 install status conflicts with target 4',
|
|
4435
|
+
params: {
|
|
4436
|
+
firmwareUpdateCode: 'FirmwareInstallStatusConflict',
|
|
4437
|
+
},
|
|
4438
|
+
});
|
|
4439
|
+
} finally {
|
|
4440
|
+
dateNowSpy.mockRestore();
|
|
4441
|
+
setTimeoutSpy.mockRestore();
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
5080
4445
|
});
|
|
5081
4446
|
|
|
5082
|
-
test('
|
|
4447
|
+
test('does not fail a completed multi-app install when final app versions are unobservable', () => {
|
|
5083
4448
|
const method = new FirmwareUpdateV4({
|
|
5084
4449
|
id: 1,
|
|
5085
4450
|
payload: {
|
|
5086
4451
|
method: 'firmwareUpdateV4',
|
|
4452
|
+
platform: 'desktop',
|
|
4453
|
+
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4454
|
+
expectedTargetVersions: {
|
|
4455
|
+
app_v1: '1.0.0',
|
|
4456
|
+
app_v2: '2.0.0',
|
|
4457
|
+
},
|
|
5087
4458
|
},
|
|
5088
4459
|
});
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
4460
|
+
method.init();
|
|
4461
|
+
(method as any).protocolV2LatestFinalFeatures = {
|
|
4462
|
+
major_version: 3,
|
|
4463
|
+
minor_version: 0,
|
|
4464
|
+
patch_version: 0,
|
|
4465
|
+
};
|
|
4466
|
+
(method as any).protocolV2FinalStatusVerified = true;
|
|
5093
4467
|
|
|
5094
|
-
|
|
5095
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5096
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5097
|
-
])
|
|
5098
|
-
).rejects.toThrow('within 480s');
|
|
4468
|
+
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
5099
4469
|
});
|
|
5100
4470
|
|
|
5101
|
-
test('
|
|
4471
|
+
test('rejects unobservable final versions after the status endpoint fallback', () => {
|
|
5102
4472
|
const method = new FirmwareUpdateV4({
|
|
5103
4473
|
id: 1,
|
|
5104
4474
|
payload: {
|
|
5105
4475
|
method: 'firmwareUpdateV4',
|
|
4476
|
+
platform: 'desktop',
|
|
4477
|
+
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4478
|
+
expectedTargetVersions: {
|
|
4479
|
+
app_v1: '1.0.0',
|
|
4480
|
+
app_v2: '2.0.0',
|
|
4481
|
+
},
|
|
5106
4482
|
},
|
|
5107
4483
|
});
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
4484
|
+
method.init();
|
|
4485
|
+
(method as any).protocolV2LatestFinalFeatures = {
|
|
4486
|
+
major_version: 3,
|
|
4487
|
+
minor_version: 0,
|
|
4488
|
+
patch_version: 0,
|
|
4489
|
+
};
|
|
5112
4490
|
|
|
5113
|
-
|
|
4491
|
+
expect(() => (method as any).assertExpectedProtocolV2Versions()).toThrow(
|
|
4492
|
+
'has no observable final version after status fallback'
|
|
4493
|
+
);
|
|
5114
4494
|
});
|
|
5115
4495
|
|
|
5116
|
-
test('
|
|
4496
|
+
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
5117
4497
|
const method = new FirmwareUpdateV4({
|
|
5118
4498
|
id: 1,
|
|
5119
4499
|
payload: {
|
|
5120
4500
|
method: 'firmwareUpdateV4',
|
|
5121
4501
|
},
|
|
5122
4502
|
});
|
|
5123
|
-
const
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
4503
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4504
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4505
|
+
message: {
|
|
4506
|
+
records: [
|
|
4507
|
+
{ target_id: 3, status: 2 },
|
|
4508
|
+
{ target_id: 4, status: 2 },
|
|
4509
|
+
],
|
|
4510
|
+
},
|
|
4511
|
+
});
|
|
4512
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4513
|
+
|
|
4514
|
+
(method as any).device = stubDevice({
|
|
4515
|
+
getCommands: () => ({ typedCall }),
|
|
4516
|
+
});
|
|
4517
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4518
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4519
|
+
method.postProgressMessage = jest.fn();
|
|
5127
4520
|
|
|
5128
|
-
await expect(
|
|
5129
|
-
|
|
4521
|
+
await expect(
|
|
4522
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4523
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
4524
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
4525
|
+
])
|
|
4526
|
+
).resolves.toBeUndefined();
|
|
4527
|
+
|
|
4528
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4529
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
4530
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
4531
|
+
'DeviceFirmwareUpdateStatus',
|
|
4532
|
+
expect.anything(),
|
|
4533
|
+
expect.anything()
|
|
5130
4534
|
);
|
|
5131
|
-
expect(
|
|
4535
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5132
4536
|
});
|
|
5133
4537
|
|
|
5134
4538
|
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
@@ -5165,7 +4569,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5165
4569
|
).toBe(true);
|
|
5166
4570
|
|
|
5167
4571
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
5168
|
-
expect(
|
|
4572
|
+
expect(() =>
|
|
5169
4573
|
(method as any).assertProtocolV2TargetStatus(
|
|
5170
4574
|
[
|
|
5171
4575
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -5173,8 +4577,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5173
4577
|
],
|
|
5174
4578
|
new Set([4, 10])
|
|
5175
4579
|
)
|
|
5176
|
-
).
|
|
5177
|
-
expect(method.postProgressMessage).
|
|
4580
|
+
).toThrow('Protocol V2 install status conflicts with target 4');
|
|
4581
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5178
4582
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5179
4583
|
|
|
5180
4584
|
expect(
|
|
@@ -5263,7 +4667,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5263
4667
|
path: true,
|
|
5264
4668
|
},
|
|
5265
4669
|
},
|
|
5266
|
-
{ timeoutMs:
|
|
4670
|
+
{ timeoutMs: 5000 }
|
|
5267
4671
|
);
|
|
5268
4672
|
});
|
|
5269
4673
|
|
|
@@ -5278,10 +4682,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5278
4682
|
const writtenPaths: string[] = [];
|
|
5279
4683
|
method.postTipMessage = jest.fn();
|
|
5280
4684
|
method.postProgressMessage = jest.fn();
|
|
5281
|
-
(method as any).
|
|
4685
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
5282
4686
|
writtenPaths.push(params.filePath);
|
|
5283
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4687
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
5284
4688
|
});
|
|
4689
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4690
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4691
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
4692
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5285
4693
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5286
4694
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5287
4695
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5321,10 +4729,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5321
4729
|
'vol0:/coprocessor.bin',
|
|
5322
4730
|
1
|
|
5323
4731
|
);
|
|
5324
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
5325
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4732
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(2);
|
|
4733
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
4734
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4735
|
+
});
|
|
4736
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5326
4737
|
targets: [
|
|
5327
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5328
4738
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5329
4739
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
5330
4740
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -5334,7 +4744,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5334
4744
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
5335
4745
|
});
|
|
5336
4746
|
|
|
5337
|
-
test('announces
|
|
4747
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
5338
4748
|
const method = new FirmwareUpdateV4({
|
|
5339
4749
|
id: 1,
|
|
5340
4750
|
payload: {
|
|
@@ -5344,11 +4754,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5344
4754
|
|
|
5345
4755
|
method.postTipMessage = jest.fn();
|
|
5346
4756
|
method.postProgressMessage = jest.fn();
|
|
5347
|
-
(method as any).
|
|
4757
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5348
4758
|
.fn()
|
|
5349
4759
|
.mockImplementation(params =>
|
|
5350
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4760
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5351
4761
|
);
|
|
4762
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4763
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4764
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5352
4765
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5353
4766
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5354
4767
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5368,21 +4781,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5368
4781
|
{
|
|
5369
4782
|
fileName: 'application_p1.bin',
|
|
5370
4783
|
binary: new Uint8Array([3]).buffer,
|
|
5371
|
-
targetId:
|
|
4784
|
+
targetId: 4,
|
|
5372
4785
|
},
|
|
5373
4786
|
],
|
|
5374
4787
|
});
|
|
5375
4788
|
|
|
5376
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4789
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
5377
4790
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
5378
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
5379
|
-
expect(
|
|
4791
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
4792
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
4793
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5380
4794
|
1,
|
|
5381
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4795
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
5382
4796
|
);
|
|
5383
|
-
expect((method as any).
|
|
4797
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5384
4798
|
2,
|
|
5385
|
-
expect.objectContaining({ processedSize:
|
|
4799
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
5386
4800
|
);
|
|
5387
4801
|
});
|
|
5388
4802
|
|
|
@@ -5396,7 +4810,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5396
4810
|
|
|
5397
4811
|
method.postTipMessage = jest.fn();
|
|
5398
4812
|
method.postProgressMessage = jest.fn();
|
|
5399
|
-
(method as any).
|
|
4813
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4814
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5400
4815
|
(method as any).device = stubDevice({
|
|
5401
4816
|
getCommands: () => ({
|
|
5402
4817
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -5442,11 +4857,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5442
4857
|
|
|
5443
4858
|
method.postTipMessage = jest.fn();
|
|
5444
4859
|
method.postProgressMessage = jest.fn();
|
|
5445
|
-
(method as any).
|
|
4860
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5446
4861
|
.fn()
|
|
5447
4862
|
.mockImplementation(params =>
|
|
5448
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4863
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5449
4864
|
);
|
|
4865
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4866
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4867
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5450
4868
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5451
4869
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5452
4870
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5661,42 +5079,33 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5661
5079
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5662
5080
|
});
|
|
5663
5081
|
|
|
5664
|
-
test('
|
|
5082
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
5665
5083
|
const method = new FirmwareUpdateV4({
|
|
5666
5084
|
id: 1,
|
|
5667
5085
|
payload: {
|
|
5668
5086
|
method: 'firmwareUpdateV4',
|
|
5669
5087
|
platform: 'web',
|
|
5088
|
+
targetsToUpdate: ['resource'],
|
|
5670
5089
|
},
|
|
5671
5090
|
});
|
|
5672
5091
|
method.init();
|
|
5673
5092
|
|
|
5674
|
-
const getSysResourceBinarySpy = jest
|
|
5675
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5676
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5677
5093
|
const getFirmwareLatestReleaseSpy = jest
|
|
5678
5094
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5679
5095
|
.mockReturnValue({
|
|
5680
5096
|
required: false,
|
|
5681
5097
|
version: [1, 0, 0],
|
|
5682
|
-
url: '
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
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',
|
|
5098
|
+
url: '',
|
|
5099
|
+
resourceBundles: [
|
|
5100
|
+
{
|
|
5101
|
+
name: 'images',
|
|
5102
|
+
url: 'https://example.com/images.okpkg',
|
|
5103
|
+
devicePath: 'VOL0:/resource/images/images.okpkg',
|
|
5104
|
+
version: [2, 0, 0],
|
|
5105
|
+
payloadHash: 'ab'.repeat(64),
|
|
5106
|
+
headerHash: 'cd'.repeat(64),
|
|
5698
5107
|
},
|
|
5699
|
-
|
|
5108
|
+
],
|
|
5700
5109
|
fingerprint: '',
|
|
5701
5110
|
changelog: {
|
|
5702
5111
|
'zh-CN': '',
|
|
@@ -5704,15 +5113,346 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5704
5113
|
},
|
|
5705
5114
|
});
|
|
5706
5115
|
|
|
5707
|
-
const
|
|
5116
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
5708
5117
|
deviceType: 'pro2',
|
|
5709
5118
|
firmwareVersion: '1.0.0',
|
|
5710
|
-
bootloaderVersion: '1.0.0',
|
|
5711
5119
|
capabilities: [],
|
|
5712
5120
|
});
|
|
5713
5121
|
|
|
5714
|
-
expect(
|
|
5715
|
-
|
|
5122
|
+
expect(bundles).toEqual([
|
|
5123
|
+
{
|
|
5124
|
+
name: 'images',
|
|
5125
|
+
binary: expect.any(ArrayBuffer),
|
|
5126
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5127
|
+
url: 'https://example.com/images.okpkg',
|
|
5128
|
+
version: [2, 0, 0],
|
|
5129
|
+
payloadHash: 'ab'.repeat(64),
|
|
5130
|
+
headerHash: 'cd'.repeat(64),
|
|
5131
|
+
},
|
|
5132
|
+
]);
|
|
5133
|
+
expect(bundles[0].binary.byteLength).toBe(0);
|
|
5134
|
+
|
|
5135
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5136
|
+
});
|
|
5137
|
+
|
|
5138
|
+
test('rejects remote RESC bundle paths before download or bootloader entry', 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 getSysResourceBinarySpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
5151
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5152
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5153
|
+
.mockReturnValue({
|
|
5154
|
+
required: false,
|
|
5155
|
+
version: [1, 0, 0],
|
|
5156
|
+
url: '',
|
|
5157
|
+
resourceBundles: [
|
|
5158
|
+
{
|
|
5159
|
+
name: 'images',
|
|
5160
|
+
url: 'https://example.com/images.okpkg',
|
|
5161
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5162
|
+
},
|
|
5163
|
+
],
|
|
5164
|
+
fingerprint: '',
|
|
5165
|
+
changelog: {
|
|
5166
|
+
'zh-CN': '',
|
|
5167
|
+
'en-US': '',
|
|
5168
|
+
},
|
|
5169
|
+
});
|
|
5170
|
+
|
|
5171
|
+
(method as any).device = stubDevice({
|
|
5172
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5173
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5174
|
+
});
|
|
5175
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5176
|
+
bootloaderBinary: null,
|
|
5177
|
+
fwBinaryMap: [],
|
|
5178
|
+
});
|
|
5179
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5180
|
+
method.postTipMessage = jest.fn();
|
|
5181
|
+
|
|
5182
|
+
await expect(method.run()).rejects.toThrow('resourceBundles[0].devicePath');
|
|
5183
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5184
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5185
|
+
|
|
5186
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5187
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5188
|
+
});
|
|
5189
|
+
|
|
5190
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
5191
|
+
const method = new FirmwareUpdateV4({
|
|
5192
|
+
id: 1,
|
|
5193
|
+
payload: {
|
|
5194
|
+
method: 'firmwareUpdateV4',
|
|
5195
|
+
},
|
|
5196
|
+
});
|
|
5197
|
+
const openPreparedSource = jest.fn();
|
|
5198
|
+
(method as any).params = {
|
|
5199
|
+
targetsToUpdate: ['resource'],
|
|
5200
|
+
resourceBundleArtifacts: [
|
|
5201
|
+
{
|
|
5202
|
+
name: 'images',
|
|
5203
|
+
artifact: {
|
|
5204
|
+
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5205
|
+
size: 1,
|
|
5206
|
+
sha256: 'a'.repeat(64),
|
|
5207
|
+
},
|
|
5208
|
+
},
|
|
5209
|
+
],
|
|
5210
|
+
};
|
|
5211
|
+
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5212
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5213
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5214
|
+
.mockReturnValue({
|
|
5215
|
+
required: false,
|
|
5216
|
+
version: [1, 0, 0],
|
|
5217
|
+
url: '',
|
|
5218
|
+
resourceBundles: [
|
|
5219
|
+
{
|
|
5220
|
+
name: 'images',
|
|
5221
|
+
url: 'https://example.com/images.okpkg',
|
|
5222
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5223
|
+
},
|
|
5224
|
+
],
|
|
5225
|
+
fingerprint: '',
|
|
5226
|
+
changelog: {
|
|
5227
|
+
'zh-CN': '',
|
|
5228
|
+
'en-US': '',
|
|
5229
|
+
},
|
|
5230
|
+
});
|
|
5231
|
+
|
|
5232
|
+
await expect(
|
|
5233
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5234
|
+
deviceType: 'pro2',
|
|
5235
|
+
})
|
|
5236
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5237
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5238
|
+
|
|
5239
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5240
|
+
});
|
|
5241
|
+
|
|
5242
|
+
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5243
|
+
const method = new FirmwareUpdateV4({
|
|
5244
|
+
id: 1,
|
|
5245
|
+
payload: {
|
|
5246
|
+
method: 'firmwareUpdateV4',
|
|
5247
|
+
platform: 'web',
|
|
5248
|
+
targetsToUpdate: ['resource'],
|
|
5249
|
+
},
|
|
5250
|
+
});
|
|
5251
|
+
method.init();
|
|
5252
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5253
|
+
|
|
5254
|
+
const order: string[] = [];
|
|
5255
|
+
const resourceBinary = buildOkppHeader().buffer as ArrayBuffer;
|
|
5256
|
+
const getSysResourceBinarySpy = jest
|
|
5257
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5258
|
+
.mockImplementation(() => {
|
|
5259
|
+
order.push('download');
|
|
5260
|
+
return Promise.resolve({ binary: resourceBinary });
|
|
5261
|
+
});
|
|
5262
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5263
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5264
|
+
.mockReturnValue({
|
|
5265
|
+
required: false,
|
|
5266
|
+
version: [1, 0, 0],
|
|
5267
|
+
url: '',
|
|
5268
|
+
resourceBundles: [
|
|
5269
|
+
{
|
|
5270
|
+
name: 'images',
|
|
5271
|
+
url: 'https://example.com/images.okpkg',
|
|
5272
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
5273
|
+
},
|
|
5274
|
+
],
|
|
5275
|
+
fingerprint: '',
|
|
5276
|
+
changelog: {
|
|
5277
|
+
'zh-CN': '',
|
|
5278
|
+
'en-US': '',
|
|
5279
|
+
},
|
|
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().mockImplementation(() => {
|
|
5291
|
+
order.push('bootloader');
|
|
5292
|
+
return Promise.resolve(true);
|
|
5293
|
+
});
|
|
5294
|
+
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
5295
|
+
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
5296
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
5297
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5298
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5299
|
+
bootloaderVersion: '1.0.0',
|
|
5300
|
+
bleVersion: '0.0.0',
|
|
5301
|
+
firmwareVersion: '1.0.0',
|
|
5302
|
+
});
|
|
5303
|
+
method.postTipMessage = jest.fn();
|
|
5304
|
+
|
|
5305
|
+
await method.run();
|
|
5306
|
+
|
|
5307
|
+
expect(order).toEqual(['download', 'bootloader']);
|
|
5308
|
+
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
5309
|
+
bootloaderBinary: null,
|
|
5310
|
+
fwBinaryMap: [],
|
|
5311
|
+
resourceBundles: [
|
|
5312
|
+
{
|
|
5313
|
+
name: 'images',
|
|
5314
|
+
binary: resourceBinary,
|
|
5315
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5316
|
+
url: 'https://example.com/images.okpkg',
|
|
5317
|
+
},
|
|
5318
|
+
],
|
|
5319
|
+
});
|
|
5320
|
+
|
|
5321
|
+
executeProtocolV2UpdateSpy.mockRestore();
|
|
5322
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5323
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5324
|
+
});
|
|
5325
|
+
|
|
5326
|
+
test('rejects a remote RESC bundle without an OKPP header when metadata is absent', async () => {
|
|
5327
|
+
const method = new FirmwareUpdateV4({
|
|
5328
|
+
id: 1,
|
|
5329
|
+
payload: {
|
|
5330
|
+
method: 'firmwareUpdateV4',
|
|
5331
|
+
},
|
|
5332
|
+
});
|
|
5333
|
+
method.init();
|
|
5334
|
+
const getSysResourceBinarySpy = jest
|
|
5335
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5336
|
+
.mockResolvedValue({ binary: new Uint8Array([1, 2, 3]).buffer });
|
|
5337
|
+
|
|
5338
|
+
await expect(
|
|
5339
|
+
(method as any).materializeProtocolV2ResourceBundles([
|
|
5340
|
+
{
|
|
5341
|
+
name: 'images',
|
|
5342
|
+
binary: new ArrayBuffer(0),
|
|
5343
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5344
|
+
url: 'https://example.com/images.okpkg',
|
|
5345
|
+
},
|
|
5346
|
+
])
|
|
5347
|
+
).rejects.toThrow('Invalid Protocol V2 RESC bundle header: images');
|
|
5348
|
+
|
|
5349
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5350
|
+
});
|
|
5351
|
+
|
|
5352
|
+
test('rejects an empty remote RESC bundle before entering bootloader', async () => {
|
|
5353
|
+
const method = new FirmwareUpdateV4({
|
|
5354
|
+
id: 1,
|
|
5355
|
+
payload: {
|
|
5356
|
+
method: 'firmwareUpdateV4',
|
|
5357
|
+
platform: 'web',
|
|
5358
|
+
targetsToUpdate: ['resource'],
|
|
5359
|
+
},
|
|
5360
|
+
});
|
|
5361
|
+
method.init();
|
|
5362
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5363
|
+
|
|
5364
|
+
const getSysResourceBinarySpy = jest
|
|
5365
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5366
|
+
.mockResolvedValue({ binary: new ArrayBuffer(0) });
|
|
5367
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5368
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5369
|
+
.mockReturnValue({
|
|
5370
|
+
required: false,
|
|
5371
|
+
version: [1, 0, 0],
|
|
5372
|
+
url: '',
|
|
5373
|
+
resourceBundles: [
|
|
5374
|
+
{
|
|
5375
|
+
name: 'images',
|
|
5376
|
+
url: 'https://example.com/images.okpkg',
|
|
5377
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5378
|
+
},
|
|
5379
|
+
],
|
|
5380
|
+
fingerprint: '',
|
|
5381
|
+
changelog: {
|
|
5382
|
+
'zh-CN': '',
|
|
5383
|
+
'en-US': '',
|
|
5384
|
+
},
|
|
5385
|
+
});
|
|
5386
|
+
(method as any).device = stubDevice({
|
|
5387
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5388
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5389
|
+
});
|
|
5390
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5391
|
+
bootloaderBinary: null,
|
|
5392
|
+
fwBinaryMap: [],
|
|
5393
|
+
});
|
|
5394
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5395
|
+
method.postTipMessage = jest.fn();
|
|
5396
|
+
|
|
5397
|
+
await expect(method.run()).rejects.toThrow('Protocol V2 RESC bundle is empty: images');
|
|
5398
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5399
|
+
|
|
5400
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5401
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5402
|
+
});
|
|
5403
|
+
|
|
5404
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5405
|
+
const method = new FirmwareUpdateV4({
|
|
5406
|
+
id: 1,
|
|
5407
|
+
payload: {
|
|
5408
|
+
method: 'firmwareUpdateV4',
|
|
5409
|
+
platform: 'web',
|
|
5410
|
+
},
|
|
5411
|
+
});
|
|
5412
|
+
method.init();
|
|
5413
|
+
|
|
5414
|
+
const getSysResourceBinarySpy = jest
|
|
5415
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5416
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5417
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5418
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5419
|
+
.mockReturnValue({
|
|
5420
|
+
required: false,
|
|
5421
|
+
version: [1, 0, 0],
|
|
5422
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5423
|
+
bootloaderVersion: [1, 0, 0],
|
|
5424
|
+
upgradeType: 'payload-package-set',
|
|
5425
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5426
|
+
components: {
|
|
5427
|
+
bootloader: {
|
|
5428
|
+
target: 'BOOTLOADER',
|
|
5429
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5430
|
+
},
|
|
5431
|
+
applicationP1: {
|
|
5432
|
+
target: 'APPLICATION_P1',
|
|
5433
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5434
|
+
},
|
|
5435
|
+
applicationP2: {
|
|
5436
|
+
target: 'APPLICATION_P2',
|
|
5437
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
5438
|
+
},
|
|
5439
|
+
},
|
|
5440
|
+
fingerprint: '',
|
|
5441
|
+
changelog: {
|
|
5442
|
+
'zh-CN': '',
|
|
5443
|
+
'en-US': '',
|
|
5444
|
+
},
|
|
5445
|
+
});
|
|
5446
|
+
|
|
5447
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5448
|
+
deviceType: 'pro2',
|
|
5449
|
+
firmwareVersion: '1.0.0',
|
|
5450
|
+
bootloaderVersion: '1.0.0',
|
|
5451
|
+
capabilities: [],
|
|
5452
|
+
});
|
|
5453
|
+
|
|
5454
|
+
expect(remoteBinaries).toEqual({
|
|
5455
|
+
bootloaderBinary: null,
|
|
5716
5456
|
fwBinaryMap: [],
|
|
5717
5457
|
installItems: [],
|
|
5718
5458
|
});
|
|
@@ -5761,7 +5501,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5761
5501
|
|
|
5762
5502
|
await method.run();
|
|
5763
5503
|
|
|
5764
|
-
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5765
5504
|
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5766
5505
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5767
5506
|
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
@@ -5769,36 +5508,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5769
5508
|
});
|
|
5770
5509
|
});
|
|
5771
5510
|
|
|
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
|
-
|
|
5802
5511
|
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5803
5512
|
const method = new FirmwareUpdateV4({
|
|
5804
5513
|
id: 1,
|
|
@@ -5835,14 +5544,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5835
5544
|
});
|
|
5836
5545
|
});
|
|
5837
5546
|
|
|
5838
|
-
test('treats manual resource
|
|
5547
|
+
test('treats manual resource bundles as explicit payload without remote firmware auto-fill', async () => {
|
|
5839
5548
|
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
5840
5549
|
const method = new FirmwareUpdateV4({
|
|
5841
5550
|
id: 1,
|
|
5842
5551
|
payload: {
|
|
5843
5552
|
method: 'firmwareUpdateV4',
|
|
5844
5553
|
platform: 'web',
|
|
5845
|
-
|
|
5554
|
+
resourceBundleFiles: [
|
|
5846
5555
|
{
|
|
5847
5556
|
binary: resourceBundle,
|
|
5848
5557
|
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
@@ -5890,7 +5599,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5890
5599
|
payload: {
|
|
5891
5600
|
method: 'firmwareUpdateV4',
|
|
5892
5601
|
platform: 'web',
|
|
5893
|
-
|
|
5602
|
+
resourceBundleFiles: [
|
|
5894
5603
|
{
|
|
5895
5604
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
5896
5605
|
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
@@ -5909,7 +5618,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5909
5618
|
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5910
5619
|
method.postTipMessage = jest.fn();
|
|
5911
5620
|
|
|
5912
|
-
await expect(method.run()).rejects.toThrow('
|
|
5621
|
+
await expect(method.run()).rejects.toThrow('resourceBundleFiles[0].devicePath');
|
|
5913
5622
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5914
5623
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5915
5624
|
});
|
|
@@ -5924,7 +5633,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5924
5633
|
|
|
5925
5634
|
method.postTipMessage = jest.fn();
|
|
5926
5635
|
method.postProgressMessage = jest.fn();
|
|
5927
|
-
(method as any).
|
|
5636
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5637
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5638
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5639
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5928
5640
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5929
5641
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5930
5642
|
|
|
@@ -5940,7 +5652,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5940
5652
|
fwBinaryMap: [],
|
|
5941
5653
|
});
|
|
5942
5654
|
|
|
5943
|
-
expect((method as any).
|
|
5655
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5944
5656
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5945
5657
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5946
5658
|
});
|
|
@@ -5968,17 +5680,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5968
5680
|
|
|
5969
5681
|
(method as any).device = stubDevice({
|
|
5970
5682
|
getCommands: () => ({ typedCall }),
|
|
5971
|
-
getCurrentDeviceType: () => 'pro2',
|
|
5972
|
-
toMessageObject: () => ({ connectId: 'firmware-device' }),
|
|
5973
5683
|
});
|
|
5974
|
-
method.
|
|
5684
|
+
method.postProgressMessage = jest.fn();
|
|
5975
5685
|
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
filePath: 'vol1:firmware.bin',
|
|
5979
|
-
processedSize: 0,
|
|
5980
|
-
totalSize: 4097,
|
|
5686
|
+
const source = await openFirmwareByteSource({
|
|
5687
|
+
binary: new Uint8Array(4097).buffer,
|
|
5981
5688
|
});
|
|
5689
|
+
try {
|
|
5690
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5691
|
+
source,
|
|
5692
|
+
filePath: 'vol1:firmware.bin',
|
|
5693
|
+
processedSize: 0,
|
|
5694
|
+
totalSize: 4097,
|
|
5695
|
+
});
|
|
5696
|
+
} finally {
|
|
5697
|
+
await source?.close();
|
|
5698
|
+
}
|
|
5982
5699
|
|
|
5983
5700
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5984
5701
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5986,17 +5703,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5986
5703
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
5987
5704
|
expect(writePayloads.every(payload => payload.append === false)).toBe(true);
|
|
5988
5705
|
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
|
-
});
|
|
6000
5706
|
});
|
|
6001
5707
|
|
|
6002
5708
|
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
@@ -6046,12 +5752,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6046
5752
|
method.postProgressMessage = jest.fn();
|
|
6047
5753
|
|
|
6048
5754
|
try {
|
|
6049
|
-
|
|
6050
|
-
|
|
5755
|
+
const source = await openFirmwareByteSource({
|
|
5756
|
+
binary: new Uint8Array(8001).buffer,
|
|
5757
|
+
});
|
|
5758
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5759
|
+
source,
|
|
6051
5760
|
filePath: 'vol0:/firmware.bin',
|
|
6052
5761
|
processedSize: 0,
|
|
6053
5762
|
totalSize: 8001,
|
|
6054
5763
|
});
|
|
5764
|
+
await source?.close();
|
|
6055
5765
|
} finally {
|
|
6056
5766
|
setTimeoutSpy.mockRestore();
|
|
6057
5767
|
}
|
|
@@ -6086,16 +5796,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6086
5796
|
});
|
|
6087
5797
|
method.postProgressMessage = jest.fn();
|
|
6088
5798
|
method.postTipMessage = jest.fn();
|
|
5799
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
6089
5800
|
|
|
5801
|
+
const source = await openFirmwareByteSource({
|
|
5802
|
+
binary: new Uint8Array(4097).buffer,
|
|
5803
|
+
});
|
|
6090
5804
|
await expect(
|
|
6091
|
-
(method as any).
|
|
6092
|
-
|
|
5805
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
5806
|
+
source,
|
|
6093
5807
|
filePath: 'vol0:/firmware.bin',
|
|
6094
5808
|
processedSize: 0,
|
|
6095
5809
|
totalSize: 4097,
|
|
6096
5810
|
})
|
|
6097
5811
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
6098
|
-
|
|
5812
|
+
await source?.close();
|
|
5813
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6099
5814
|
});
|
|
6100
5815
|
|
|
6101
5816
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -6128,21 +5843,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6128
5843
|
});
|
|
6129
5844
|
method.postProgressMessage = jest.fn();
|
|
6130
5845
|
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
6134
|
-
processedSize: 0,
|
|
6135
|
-
totalSize: 1801,
|
|
5846
|
+
const source = await openFirmwareByteSource({
|
|
5847
|
+
binary: new Uint8Array(1801).buffer,
|
|
6136
5848
|
});
|
|
5849
|
+
try {
|
|
5850
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5851
|
+
source,
|
|
5852
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
5853
|
+
processedSize: 0,
|
|
5854
|
+
totalSize: 1801,
|
|
5855
|
+
});
|
|
5856
|
+
} finally {
|
|
5857
|
+
await source?.close();
|
|
5858
|
+
}
|
|
6137
5859
|
|
|
6138
5860
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
6139
5861
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
6140
5862
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
6141
5863
|
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
|
-
]);
|
|
6146
5864
|
});
|
|
6147
5865
|
|
|
6148
5866
|
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
@@ -6254,9 +5972,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6254
5972
|
const typedCall = jest
|
|
6255
5973
|
.fn()
|
|
6256
5974
|
.mockRejectedValue(
|
|
6257
|
-
|
|
6258
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6259
|
-
'NotFoundError'
|
|
5975
|
+
Object.assign(
|
|
5976
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
5977
|
+
{ name: 'NotFoundError' }
|
|
6260
5978
|
)
|
|
6261
5979
|
);
|
|
6262
5980
|
|
|
@@ -6310,7 +6028,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6310
6028
|
expect(typedCall).not.toHaveBeenCalled();
|
|
6311
6029
|
});
|
|
6312
6030
|
|
|
6313
|
-
test.each([0,
|
|
6031
|
+
test.each([0, 2])(
|
|
6314
6032
|
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
6315
6033
|
async targetId => {
|
|
6316
6034
|
const typedCall = jest.fn();
|
|
@@ -6343,8 +6061,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6343
6061
|
targets: [
|
|
6344
6062
|
{
|
|
6345
6063
|
target_id: undefined,
|
|
6346
|
-
targetId:
|
|
6347
|
-
path: 'vol0:
|
|
6064
|
+
targetId: 1,
|
|
6065
|
+
path: 'vol0:resource.crate.okpkg',
|
|
6348
6066
|
},
|
|
6349
6067
|
],
|
|
6350
6068
|
} as any,
|
|
@@ -6356,7 +6074,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6356
6074
|
|
|
6357
6075
|
await method.run();
|
|
6358
6076
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
6359
|
-
targets: [{ target_id:
|
|
6077
|
+
targets: [{ target_id: 1, path: 'vol0:resource.crate.okpkg' }],
|
|
6360
6078
|
});
|
|
6361
6079
|
});
|
|
6362
6080
|
|
|
@@ -6421,63 +6139,20 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6421
6139
|
});
|
|
6422
6140
|
|
|
6423
6141
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
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}`);
|
|
6142
|
+
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
6143
|
+
const method = new FirmwareUpdateV4({
|
|
6144
|
+
id: 1,
|
|
6145
|
+
payload: {
|
|
6146
|
+
method: 'firmwareUpdateV4',
|
|
6147
|
+
platform: 'native',
|
|
6148
|
+
expectedDeviceId: 'persisted-device-id',
|
|
6149
|
+
},
|
|
6479
6150
|
});
|
|
6480
|
-
|
|
6151
|
+
|
|
6152
|
+
method.init();
|
|
6153
|
+
|
|
6154
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6155
|
+
});
|
|
6481
6156
|
|
|
6482
6157
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6483
6158
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6488,15 +6163,15 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6488
6163
|
).not.toThrow();
|
|
6489
6164
|
});
|
|
6490
6165
|
|
|
6491
|
-
test('
|
|
6492
|
-
expect(() =>
|
|
6493
|
-
|
|
6494
|
-
)
|
|
6495
|
-
expect(() =>
|
|
6496
|
-
|
|
6497
|
-
)
|
|
6166
|
+
test('fails closed when the physical serial is unavailable', () => {
|
|
6167
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
6168
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6169
|
+
);
|
|
6170
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).toThrow(
|
|
6171
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6172
|
+
);
|
|
6498
6173
|
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
6499
|
-
|
|
6174
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6500
6175
|
);
|
|
6501
6176
|
});
|
|
6502
6177
|
|
|
@@ -6515,7 +6190,6 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6515
6190
|
},
|
|
6516
6191
|
});
|
|
6517
6192
|
(method as any).device = stubDevice({
|
|
6518
|
-
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6519
6193
|
features: { deviceId: 'wallet-derived-id' },
|
|
6520
6194
|
getCommands: () => ({ typedCall }),
|
|
6521
6195
|
});
|
|
@@ -6531,7 +6205,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6531
6205
|
);
|
|
6532
6206
|
});
|
|
6533
6207
|
|
|
6534
|
-
test('
|
|
6208
|
+
test('rejects firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6535
6209
|
const method = new FirmwareUpdateV4({
|
|
6536
6210
|
id: 1,
|
|
6537
6211
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6541,204 +6215,42 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6541
6215
|
message: { protocol_version: 1, hw: {} },
|
|
6542
6216
|
});
|
|
6543
6217
|
(method as any).device = stubDevice({
|
|
6544
|
-
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6545
6218
|
getCommands: () => ({ typedCall }),
|
|
6546
6219
|
});
|
|
6547
6220
|
|
|
6548
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6549
|
-
|
|
6550
|
-
|
|
6221
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).rejects.toMatchObject({
|
|
6222
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
6223
|
+
});
|
|
6551
6224
|
});
|
|
6552
6225
|
|
|
6553
|
-
test('uses
|
|
6226
|
+
test('uses the BLE read limit when reading an existing firmware bundle header', async () => {
|
|
6554
6227
|
const method = new FirmwareUpdateV4({
|
|
6555
6228
|
id: 1,
|
|
6556
6229
|
payload: {
|
|
6557
6230
|
method: 'firmwareUpdateV4',
|
|
6558
|
-
platform: '
|
|
6559
|
-
|
|
6231
|
+
platform: 'native',
|
|
6232
|
+
chunkSize: 1800,
|
|
6560
6233
|
},
|
|
6561
6234
|
});
|
|
6562
6235
|
method.init();
|
|
6563
|
-
const
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
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'] },
|
|
6236
|
+
const readChunkLengths: number[] = [];
|
|
6237
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6238
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6239
|
+
return Promise.resolve({ message: { exist: true, directory: false, size: 0x52a0 } });
|
|
6240
|
+
}
|
|
6241
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6242
|
+
readChunkLengths.push(request.chunk_len);
|
|
6243
|
+
return Promise.resolve({ message: { data: new Uint8Array(request.chunk_len) } });
|
|
6244
|
+
}
|
|
6245
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6631
6246
|
});
|
|
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']);
|
|
6643
6247
|
(method as any).device = stubDevice({
|
|
6644
6248
|
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
|
-
},
|
|
6736
6249
|
});
|
|
6737
|
-
method.init();
|
|
6738
6250
|
|
|
6739
|
-
expect((
|
|
6740
|
-
|
|
6741
|
-
);
|
|
6251
|
+
await expect((method as any).readProtocolV2DeviceFileHeader('bundle.okpp')).resolves.toBeNull();
|
|
6252
|
+
expect(readChunkLengths.length).toBeGreaterThan(1);
|
|
6253
|
+
expect(Math.max(...readChunkLengths)).toBe(900);
|
|
6742
6254
|
});
|
|
6743
6255
|
});
|
|
6744
6256
|
|
|
@@ -6865,245 +6377,326 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6865
6377
|
});
|
|
6866
6378
|
});
|
|
6867
6379
|
|
|
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();
|
|
6380
|
+
test.each([
|
|
6381
|
+
['deviceInfoGet', DeviceInfoGet],
|
|
6382
|
+
['deviceStatusGet', DeviceStatusGet],
|
|
6383
|
+
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6384
|
+
const method = new MethodClass({
|
|
6385
|
+
id: 1,
|
|
6386
|
+
payload: { method: _name },
|
|
6387
|
+
} as any);
|
|
6388
|
+
method.init();
|
|
6389
|
+
|
|
6390
|
+
expect(method.unlockPolicy).toBe('none');
|
|
6391
|
+
});
|
|
6392
|
+
|
|
6393
|
+
test('unlocks and retries an opted-in Protocol V2 method once', async () => {
|
|
6394
|
+
const calls: string[] = [];
|
|
6395
|
+
const method = {
|
|
6396
|
+
name: 'testBusinessMethod',
|
|
6397
|
+
unlockPolicy: 'retry-on-locked',
|
|
6398
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6399
|
+
run: jest
|
|
6400
|
+
.fn()
|
|
6401
|
+
.mockImplementationOnce(() => {
|
|
6402
|
+
calls.push('run-1');
|
|
6403
|
+
return Promise.reject(deviceLockedError());
|
|
6404
|
+
})
|
|
6405
|
+
.mockImplementationOnce(() => {
|
|
6406
|
+
calls.push('run-2');
|
|
6407
|
+
return Promise.resolve({ message: 'ok' });
|
|
6408
|
+
}),
|
|
6409
|
+
};
|
|
6410
|
+
const device = {
|
|
6411
|
+
isProtocolV2: () => true,
|
|
6412
|
+
unlockDevice: jest.fn(() => {
|
|
6413
|
+
calls.push('unlock');
|
|
6414
|
+
return Promise.resolve();
|
|
6415
|
+
}),
|
|
6416
|
+
};
|
|
6417
|
+
const uiCoordinator = {
|
|
6418
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6419
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6420
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6421
|
+
};
|
|
6422
|
+
|
|
6423
|
+
await expect(
|
|
6424
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6425
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6426
|
+
expect(calls).toEqual([
|
|
6427
|
+
'method-prompt',
|
|
6428
|
+
'run-1',
|
|
6429
|
+
'unlock-prompt',
|
|
6430
|
+
'unlock',
|
|
6431
|
+
'method-prompt',
|
|
6432
|
+
'run-2',
|
|
6433
|
+
]);
|
|
6434
|
+
});
|
|
6435
|
+
|
|
6436
|
+
test('restores the expected hidden-wallet session before retrying after unlock', async () => {
|
|
6437
|
+
const calls: string[] = [];
|
|
6438
|
+
const method = {
|
|
6439
|
+
name: 'evmSignMessage',
|
|
6440
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6441
|
+
useDevicePassphraseState: true,
|
|
6442
|
+
unlockPolicy: 'retry-on-locked',
|
|
6443
|
+
run: jest
|
|
6444
|
+
.fn()
|
|
6445
|
+
.mockImplementationOnce(() => {
|
|
6446
|
+
calls.push('run-1');
|
|
6447
|
+
return Promise.reject(deviceLockedError());
|
|
6448
|
+
})
|
|
6449
|
+
.mockImplementationOnce(() => {
|
|
6450
|
+
calls.push('run-2');
|
|
6451
|
+
return Promise.resolve({ message: 'ok' });
|
|
6452
|
+
}),
|
|
6453
|
+
};
|
|
6454
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6455
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6456
|
+
calls.push('negotiate-session');
|
|
6457
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6458
|
+
}
|
|
6459
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6460
|
+
calls.push('resume-hidden-session');
|
|
6461
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6462
|
+
return Promise.resolve({
|
|
6463
|
+
message: {
|
|
6464
|
+
session_id: 'hidden-session',
|
|
6465
|
+
btc_test_address: 'hidden-state',
|
|
6466
|
+
},
|
|
6467
|
+
});
|
|
6468
|
+
}
|
|
6469
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6470
|
+
});
|
|
6471
|
+
const device = {
|
|
6472
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
6473
|
+
passphraseState: 'hidden-state',
|
|
6474
|
+
commands: { typedCall },
|
|
6475
|
+
isProtocolV2: () => true,
|
|
6476
|
+
unlockDevice: jest.fn(() => {
|
|
6477
|
+
calls.push('unlock');
|
|
6478
|
+
return Promise.resolve();
|
|
6479
|
+
}),
|
|
6480
|
+
getCurrentPassphraseProtection: () => true,
|
|
6481
|
+
getInternalState: () => 'hidden-session',
|
|
6482
|
+
clearInternalState: jest.fn(),
|
|
6483
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6484
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6485
|
+
};
|
|
6486
|
+
|
|
6487
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6488
|
+
message: 'ok',
|
|
6489
|
+
});
|
|
6490
|
+
expect(calls).toEqual([
|
|
6491
|
+
'run-1',
|
|
6492
|
+
'unlock',
|
|
6493
|
+
'negotiate-session',
|
|
6494
|
+
'resume-hidden-session',
|
|
6495
|
+
'validate-hidden-session',
|
|
6496
|
+
'run-2',
|
|
6497
|
+
]);
|
|
6498
|
+
});
|
|
6499
|
+
|
|
6500
|
+
test('does not replay a business method when the hidden-wallet session cannot be restored after unlock', async () => {
|
|
6501
|
+
const initialError = deviceLockedError();
|
|
6502
|
+
const restoreError = new Error('Invalid wallet session');
|
|
6503
|
+
const method = {
|
|
6504
|
+
name: 'evmSignMessage',
|
|
6505
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6506
|
+
useDevicePassphraseState: true,
|
|
6507
|
+
unlockPolicy: 'retry-on-locked',
|
|
6508
|
+
run: jest.fn().mockRejectedValueOnce(initialError),
|
|
6509
|
+
};
|
|
6510
|
+
const typedCall = jest.fn((requestType: string) => {
|
|
6511
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6512
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6513
|
+
}
|
|
6514
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6515
|
+
return Promise.reject(restoreError);
|
|
6516
|
+
}
|
|
6517
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6518
|
+
});
|
|
6519
|
+
const device = {
|
|
6520
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
6521
|
+
passphraseState: 'hidden-state',
|
|
6522
|
+
commands: { typedCall },
|
|
6523
|
+
isProtocolV2: () => true,
|
|
6524
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6525
|
+
getCurrentPassphraseProtection: () => true,
|
|
6526
|
+
getInternalState: () => 'hidden-session',
|
|
6527
|
+
clearInternalState: jest.fn(),
|
|
6528
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6529
|
+
updateInternalState: jest.fn(),
|
|
6530
|
+
};
|
|
6877
6531
|
|
|
6878
|
-
expect(method
|
|
6532
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(restoreError);
|
|
6533
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6534
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6535
|
+
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6536
|
+
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6879
6537
|
});
|
|
6880
6538
|
|
|
6881
|
-
test('
|
|
6539
|
+
test('restores the expected hidden-wallet session after pre-unlock without selecting Attach PIN', async () => {
|
|
6882
6540
|
const calls: string[] = [];
|
|
6883
|
-
const features = { unlocked: true };
|
|
6884
6541
|
const method = {
|
|
6885
|
-
name: '
|
|
6886
|
-
|
|
6542
|
+
name: 'evmSignMessage',
|
|
6543
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6887
6544
|
useDevicePassphraseState: true,
|
|
6888
|
-
|
|
6545
|
+
unlockPolicy: 'retry-on-locked',
|
|
6889
6546
|
run: jest.fn(() => {
|
|
6890
6547
|
calls.push('run');
|
|
6891
|
-
return Promise.resolve({
|
|
6548
|
+
return Promise.resolve({ message: 'ok' });
|
|
6892
6549
|
}),
|
|
6893
6550
|
};
|
|
6551
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6552
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6553
|
+
calls.push('negotiate-session');
|
|
6554
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6555
|
+
}
|
|
6556
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6557
|
+
calls.push('resume-hidden-session');
|
|
6558
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6559
|
+
return Promise.resolve({
|
|
6560
|
+
message: {
|
|
6561
|
+
session_id: 'hidden-session',
|
|
6562
|
+
btc_test_address: 'hidden-state',
|
|
6563
|
+
},
|
|
6564
|
+
});
|
|
6565
|
+
}
|
|
6566
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6567
|
+
});
|
|
6568
|
+
const features = {
|
|
6569
|
+
unlocked: false,
|
|
6570
|
+
unlockedAttachPin: true,
|
|
6571
|
+
passphraseProtection: true,
|
|
6572
|
+
};
|
|
6894
6573
|
const device = {
|
|
6895
6574
|
features,
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
calls.push('status');
|
|
6899
|
-
return Promise.resolve({ message: { unlocked: true } });
|
|
6900
|
-
}),
|
|
6901
|
-
},
|
|
6575
|
+
passphraseState: 'hidden-state',
|
|
6576
|
+
commands: { typedCall },
|
|
6902
6577
|
isProtocolV2: () => true,
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
features.
|
|
6907
|
-
return
|
|
6578
|
+
unlockDevice: jest.fn(() => {
|
|
6579
|
+
calls.push('unlock-main');
|
|
6580
|
+
features.unlocked = true;
|
|
6581
|
+
features.unlockedAttachPin = false;
|
|
6582
|
+
return Promise.resolve();
|
|
6908
6583
|
}),
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6584
|
+
getCurrentPassphraseProtection: () => true,
|
|
6585
|
+
getInternalState: () => 'hidden-session',
|
|
6586
|
+
clearInternalState: jest.fn(),
|
|
6587
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6588
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6914
6589
|
};
|
|
6915
6590
|
|
|
6916
|
-
await expect(
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
).
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6591
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6592
|
+
message: 'ok',
|
|
6593
|
+
});
|
|
6594
|
+
expect(device.unlockDevice).toHaveBeenCalledWith();
|
|
6595
|
+
expect(calls).toEqual([
|
|
6596
|
+
'unlock-main',
|
|
6597
|
+
'negotiate-session',
|
|
6598
|
+
'resume-hidden-session',
|
|
6599
|
+
'validate-hidden-session',
|
|
6600
|
+
'run',
|
|
6601
|
+
]);
|
|
6926
6602
|
});
|
|
6927
6603
|
|
|
6928
|
-
test('
|
|
6929
|
-
const calls: string[] = [];
|
|
6930
|
-
const identityError = new Error('Unexpected device');
|
|
6604
|
+
test('does not restore a hidden-wallet session for a standard-wallet pre-unlock', async () => {
|
|
6931
6605
|
const method = {
|
|
6932
|
-
name: '
|
|
6933
|
-
|
|
6606
|
+
name: 'evmGetAddress',
|
|
6607
|
+
payload: { useEmptyPassphrase: true },
|
|
6934
6608
|
useDevicePassphraseState: true,
|
|
6935
|
-
|
|
6609
|
+
unlockPolicy: 'retry-on-locked',
|
|
6610
|
+
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6936
6611
|
};
|
|
6937
|
-
const features = { unlocked: false };
|
|
6938
6612
|
const device = {
|
|
6939
|
-
features,
|
|
6940
|
-
|
|
6941
|
-
typedCall: jest.fn(() => {
|
|
6942
|
-
calls.push('status');
|
|
6943
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6944
|
-
}),
|
|
6945
|
-
},
|
|
6613
|
+
features: { unlocked: false },
|
|
6614
|
+
passphraseState: 'stale-hidden-state',
|
|
6946
6615
|
isProtocolV2: () => true,
|
|
6947
|
-
|
|
6948
|
-
isRomloader: () => false,
|
|
6949
|
-
updateProtocolV2Status: jest.fn(() => features),
|
|
6950
|
-
unlockDevice: jest.fn(),
|
|
6616
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6951
6617
|
};
|
|
6952
6618
|
|
|
6953
|
-
await expect(
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
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();
|
|
6619
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6620
|
+
address: 'standard-wallet-address',
|
|
6621
|
+
});
|
|
6622
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6623
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6965
6624
|
});
|
|
6966
6625
|
|
|
6967
|
-
test('unlocks before
|
|
6626
|
+
test('refreshes status and unlocks before a protected Protocol V2 method', async () => {
|
|
6968
6627
|
const calls: string[] = [];
|
|
6969
|
-
const features = { unlocked: true };
|
|
6970
6628
|
const method = {
|
|
6971
|
-
name: '
|
|
6629
|
+
name: 'deviceSettingsPageShow',
|
|
6972
6630
|
unlockPolicy: 'unlock-before-run',
|
|
6973
|
-
protocolV2UiInteraction: { reason: '
|
|
6631
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6974
6632
|
run: jest.fn(() => {
|
|
6975
6633
|
calls.push('run');
|
|
6976
|
-
return Promise.resolve({
|
|
6634
|
+
return Promise.resolve({ message: 'ok' });
|
|
6977
6635
|
}),
|
|
6978
6636
|
};
|
|
6637
|
+
const features = { unlocked: true };
|
|
6979
6638
|
const device = {
|
|
6980
6639
|
features,
|
|
6981
6640
|
commands: {
|
|
6982
6641
|
typedCall: jest.fn(() => {
|
|
6983
|
-
calls.push('status');
|
|
6642
|
+
calls.push('refresh-status');
|
|
6984
6643
|
return Promise.resolve({ message: { unlocked: false } });
|
|
6985
6644
|
}),
|
|
6986
6645
|
},
|
|
6987
6646
|
isProtocolV2: () => true,
|
|
6988
|
-
isBootloader: () => false,
|
|
6989
|
-
isRomloader: () => false,
|
|
6990
6647
|
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6991
|
-
features.unlocked = status.unlocked
|
|
6648
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6992
6649
|
return features;
|
|
6993
6650
|
}),
|
|
6994
6651
|
unlockDevice: jest.fn(() => {
|
|
6995
6652
|
calls.push('unlock');
|
|
6996
6653
|
features.unlocked = true;
|
|
6997
|
-
return Promise.resolve(
|
|
6654
|
+
return Promise.resolve();
|
|
6998
6655
|
}),
|
|
6999
6656
|
};
|
|
7000
6657
|
const uiCoordinator = {
|
|
7001
6658
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
7002
|
-
enterUnlockInteraction: jest.fn(() =>
|
|
7003
|
-
|
|
7004
|
-
return undefined;
|
|
7005
|
-
}),
|
|
6659
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6660
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
7006
6661
|
};
|
|
7007
6662
|
|
|
7008
6663
|
await expect(
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
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);
|
|
6664
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6665
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6666
|
+
expect(calls).toEqual(['refresh-status', 'unlock-prompt', 'unlock', 'method-prompt', 'run']);
|
|
6667
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
7027
6668
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6669
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6670
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
7028
6671
|
});
|
|
7029
6672
|
|
|
7030
|
-
test('
|
|
7031
|
-
const
|
|
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();
|
|
6673
|
+
test('uses refreshed unlocked status instead of a stale locked cache', async () => {
|
|
6674
|
+
const features = { unlocked: false };
|
|
7055
6675
|
const method = {
|
|
7056
|
-
name: '
|
|
6676
|
+
name: 'deviceSettings',
|
|
7057
6677
|
unlockPolicy: 'unlock-before-run',
|
|
7058
|
-
run: jest.fn().
|
|
6678
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
7059
6679
|
};
|
|
7060
|
-
const features = { unlocked: true };
|
|
7061
6680
|
const device = {
|
|
7062
6681
|
features,
|
|
7063
|
-
commands: {
|
|
6682
|
+
commands: {
|
|
6683
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6684
|
+
},
|
|
7064
6685
|
isProtocolV2: () => true,
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
6686
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6687
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6688
|
+
return features;
|
|
6689
|
+
}),
|
|
7068
6690
|
unlockDevice: jest.fn(),
|
|
7069
6691
|
};
|
|
7070
6692
|
|
|
7071
|
-
await expect(
|
|
6693
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6694
|
+
message: 'ok',
|
|
6695
|
+
});
|
|
7072
6696
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7073
6697
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7074
6698
|
});
|
|
7075
6699
|
|
|
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
|
-
|
|
7107
6700
|
test.each(['bootloader', 'romloader'] as const)(
|
|
7108
6701
|
'skips DeviceStatus and unlock in Protocol V2 %s mode',
|
|
7109
6702
|
async mode => {
|
|
@@ -7121,7 +6714,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7121
6714
|
unlockDevice: jest.fn(),
|
|
7122
6715
|
};
|
|
7123
6716
|
|
|
7124
|
-
await expect(
|
|
6717
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
7125
6718
|
message: 'updating',
|
|
7126
6719
|
});
|
|
7127
6720
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -7130,30 +6723,157 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7130
6723
|
}
|
|
7131
6724
|
);
|
|
7132
6725
|
|
|
7133
|
-
test('
|
|
6726
|
+
test('infers a signing interaction before running a Protocol V2 business method', async () => {
|
|
6727
|
+
const method = {
|
|
6728
|
+
name: 'evmSignMessage',
|
|
6729
|
+
params: { message: 'not-exposed-in-event' },
|
|
6730
|
+
payload: {},
|
|
6731
|
+
unlockPolicy: 'retry-on-locked',
|
|
6732
|
+
run: jest.fn().mockResolvedValue({ signature: 'test' }),
|
|
6733
|
+
};
|
|
6734
|
+
const device = {
|
|
6735
|
+
isProtocolV2: () => true,
|
|
6736
|
+
unlockDevice: jest.fn(),
|
|
6737
|
+
};
|
|
6738
|
+
const uiCoordinator = {
|
|
6739
|
+
enterMethodInteraction: jest.fn(),
|
|
6740
|
+
enterUnlockInteraction: jest.fn(),
|
|
6741
|
+
resumeMethodInteraction: jest.fn(),
|
|
6742
|
+
};
|
|
6743
|
+
|
|
6744
|
+
await runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any);
|
|
6745
|
+
|
|
6746
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledWith({
|
|
6747
|
+
request: 'button',
|
|
6748
|
+
source: 'method-lifecycle',
|
|
6749
|
+
reason: 'signing-confirmation',
|
|
6750
|
+
completion: 'operation-completed',
|
|
6751
|
+
deviceOnly: true,
|
|
6752
|
+
operation: 'evmSignMessage',
|
|
6753
|
+
});
|
|
6754
|
+
});
|
|
6755
|
+
|
|
6756
|
+
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
7134
6757
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
7135
|
-
[false, '
|
|
6758
|
+
[false, 'retry-on-locked'],
|
|
7136
6759
|
[true, 'none'],
|
|
7137
6760
|
] as const) {
|
|
6761
|
+
const error = deviceLockedError();
|
|
7138
6762
|
const method = {
|
|
7139
6763
|
unlockPolicy,
|
|
7140
|
-
|
|
7141
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6764
|
+
run: jest.fn().mockRejectedValue(error),
|
|
7142
6765
|
};
|
|
7143
6766
|
const device = {
|
|
7144
|
-
commands: { typedCall: jest.fn() },
|
|
7145
6767
|
isProtocolV2: () => isProtocolV2,
|
|
7146
6768
|
unlockDevice: jest.fn(),
|
|
7147
6769
|
};
|
|
7148
6770
|
|
|
7149
|
-
await expect(
|
|
7150
|
-
message: 'ok',
|
|
7151
|
-
});
|
|
7152
|
-
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6771
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
7153
6772
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7154
6773
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7155
6774
|
}
|
|
7156
6775
|
});
|
|
6776
|
+
|
|
6777
|
+
test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
|
|
6778
|
+
const method = {
|
|
6779
|
+
name: 'uploadPortfolio',
|
|
6780
|
+
unlockPolicy: 'none',
|
|
6781
|
+
protocolV2UiMode: 'none',
|
|
6782
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6783
|
+
};
|
|
6784
|
+
const device = {
|
|
6785
|
+
features: { unlocked: false },
|
|
6786
|
+
isProtocolV2: () => true,
|
|
6787
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6788
|
+
};
|
|
6789
|
+
const uiCoordinator = {
|
|
6790
|
+
enterMethodInteraction: jest.fn(),
|
|
6791
|
+
enterUnlockInteraction: jest.fn(),
|
|
6792
|
+
resumeMethodInteraction: jest.fn(),
|
|
6793
|
+
};
|
|
6794
|
+
|
|
6795
|
+
await expect(
|
|
6796
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6797
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6798
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6799
|
+
expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
|
|
6800
|
+
expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
|
|
6801
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6802
|
+
});
|
|
6803
|
+
|
|
6804
|
+
test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
|
|
6805
|
+
const error = deviceLockedError();
|
|
6806
|
+
const method = {
|
|
6807
|
+
name: 'uploadPortfolio',
|
|
6808
|
+
unlockPolicy: 'none',
|
|
6809
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6810
|
+
};
|
|
6811
|
+
const device = {
|
|
6812
|
+
features: { unlocked: true },
|
|
6813
|
+
isProtocolV2: () => true,
|
|
6814
|
+
unlockDevice: jest.fn(),
|
|
6815
|
+
};
|
|
6816
|
+
|
|
6817
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6818
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6819
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6820
|
+
});
|
|
6821
|
+
|
|
6822
|
+
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
6823
|
+
const initialError = deviceLockedError();
|
|
6824
|
+
const unlockError = new Error('PIN cancelled');
|
|
6825
|
+
const unlockFailMethod = {
|
|
6826
|
+
unlockPolicy: 'retry-on-locked',
|
|
6827
|
+
run: jest.fn().mockRejectedValue(initialError),
|
|
6828
|
+
};
|
|
6829
|
+
const unlockFailDevice = {
|
|
6830
|
+
isProtocolV2: () => true,
|
|
6831
|
+
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
6832
|
+
};
|
|
6833
|
+
const unlockFailCoordinator = {
|
|
6834
|
+
enterMethodInteraction: jest.fn(),
|
|
6835
|
+
enterUnlockInteraction: jest.fn(),
|
|
6836
|
+
resumeMethodInteraction: jest.fn(),
|
|
6837
|
+
};
|
|
6838
|
+
|
|
6839
|
+
await expect(
|
|
6840
|
+
runMethodWithUnlockRetry(
|
|
6841
|
+
unlockFailMethod as any,
|
|
6842
|
+
unlockFailDevice as any,
|
|
6843
|
+
unlockFailCoordinator as any
|
|
6844
|
+
)
|
|
6845
|
+
).rejects.toBe(unlockError);
|
|
6846
|
+
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
6847
|
+
expect(unlockFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6848
|
+
expect(unlockFailCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6849
|
+
|
|
6850
|
+
const retryError = deviceLockedError();
|
|
6851
|
+
const retryFailMethod = {
|
|
6852
|
+
unlockPolicy: 'retry-on-locked',
|
|
6853
|
+
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
6854
|
+
};
|
|
6855
|
+
const retryFailDevice = {
|
|
6856
|
+
isProtocolV2: () => true,
|
|
6857
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6858
|
+
};
|
|
6859
|
+
const retryFailCoordinator = {
|
|
6860
|
+
enterMethodInteraction: jest.fn(),
|
|
6861
|
+
enterUnlockInteraction: jest.fn(),
|
|
6862
|
+
resumeMethodInteraction: jest.fn(),
|
|
6863
|
+
};
|
|
6864
|
+
|
|
6865
|
+
await expect(
|
|
6866
|
+
runMethodWithUnlockRetry(
|
|
6867
|
+
retryFailMethod as any,
|
|
6868
|
+
retryFailDevice as any,
|
|
6869
|
+
retryFailCoordinator as any
|
|
6870
|
+
)
|
|
6871
|
+
).rejects.toBe(retryError);
|
|
6872
|
+
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
6873
|
+
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6874
|
+
expect(retryFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6875
|
+
expect(retryFailCoordinator.resumeMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6876
|
+
});
|
|
7157
6877
|
});
|
|
7158
6878
|
|
|
7159
6879
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -7285,9 +7005,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7285
7005
|
|
|
7286
7006
|
await method.run();
|
|
7287
7007
|
|
|
7288
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7289
|
-
eventless_wallet_session: true,
|
|
7290
|
-
});
|
|
7008
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
7291
7009
|
});
|
|
7292
7010
|
|
|
7293
7011
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -7363,29 +7081,6 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7363
7081
|
|
|
7364
7082
|
await expect(method.run()).resolves.toBe(features);
|
|
7365
7083
|
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
|
-
});
|
|
7389
7084
|
});
|
|
7390
7085
|
|
|
7391
7086
|
test.each([
|