@onekeyfe/hd-core 1.2.0-alpha.67 → 1.2.0-alpha.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
- package/__tests__/DeviceCommands.test.ts +59 -13
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +226 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-settings.test.ts +39 -18
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +13 -1
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +11 -1
- package/__tests__/deviceUploadNft.test.ts +316 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +33 -1
- package/__tests__/protocol-v2-resources.test.ts +343 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
- package/__tests__/protocol-v2.test.ts +1586 -1213
- package/__tests__/protocolV2FileWrite.test.ts +193 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +3 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -10
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +15 -27
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +2 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +6 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +9 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +29 -6
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +2 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +243 -247
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3135 -3785
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -70
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +38 -21
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +78 -64
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -298
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +645 -1018
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +71 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
- package/src/api/device/DeviceSettings.ts +5 -4
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -125
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +252 -63
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +213 -111
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +113 -41
- package/src/data-manager/TransportManager.ts +6 -0
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +274 -49
- package/src/device/DeviceCommands.ts +31 -4
- package/src/device/DeviceConnector.ts +33 -13
- package/src/device/DevicePool.ts +21 -7
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +394 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +3 -5
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +6 -78
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +15 -3
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +61 -22
- package/src/utils/deviceFeaturesCompat.ts +5 -6
- package/src/utils/deviceFeaturesUtils.ts +14 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/device-update-bootloader.test.ts +0 -87
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -231
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
DeviceSessionPinType,
|
|
5
5
|
DeviceSettingsPage,
|
|
6
6
|
DeviceType,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
10
11
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -34,7 +35,10 @@ import ProtocolInfoRequest from '../src/api/protocol-v2/ProtocolInfoRequest';
|
|
|
34
35
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
35
36
|
import EVMSignMessageEIP712 from '../src/api/evm/EVMSignMessageEIP712';
|
|
36
37
|
import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
|
|
37
|
-
import FirmwareUpdateV4, {
|
|
38
|
+
import FirmwareUpdateV4, {
|
|
39
|
+
assertProtocolV2FirmwareTargetsSupported,
|
|
40
|
+
assertProtocolV2ReconnectIdentity,
|
|
41
|
+
} from '../src/api/FirmwareUpdateV4';
|
|
38
42
|
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
39
43
|
import GetOnekeyFeatures from '../src/api/GetOnekeyFeatures';
|
|
40
44
|
import { batchGetPublickeys } from '../src/api/helpers/batchGetPublickeys';
|
|
@@ -61,6 +65,7 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
65
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
66
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
67
|
import { DevicePool } from '../src/device/DevicePool';
|
|
68
|
+
import { DEVICE } from '../src/events';
|
|
64
69
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
65
70
|
import {
|
|
66
71
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -74,11 +79,15 @@ import {
|
|
|
74
79
|
requestProtocolV2ProtocolInfo,
|
|
75
80
|
supportsProtocolV2Message,
|
|
76
81
|
} from '../src/protocols/protocol-v2/features';
|
|
82
|
+
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
77
83
|
import {
|
|
78
84
|
getProtocolV2WalletSession,
|
|
79
85
|
refreshProtocolV2DeviceStatus,
|
|
80
86
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
81
|
-
import {
|
|
87
|
+
import {
|
|
88
|
+
createProtocolV2UnlockContext,
|
|
89
|
+
runMethodWithUnlockPolicy,
|
|
90
|
+
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
82
91
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
83
92
|
import {
|
|
84
93
|
buildProtocolV1FeaturesPayload,
|
|
@@ -92,7 +101,6 @@ import {
|
|
|
92
101
|
getMethodVersionRange,
|
|
93
102
|
} from '../src/utils';
|
|
94
103
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
-
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
96
104
|
import {
|
|
97
105
|
getPassphraseState,
|
|
98
106
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -241,7 +249,7 @@ describe('UploadPortfolio', () => {
|
|
|
241
249
|
(method as any).device = device;
|
|
242
250
|
|
|
243
251
|
method.init();
|
|
244
|
-
await
|
|
252
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
245
253
|
|
|
246
254
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
247
255
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -249,7 +257,10 @@ describe('UploadPortfolio', () => {
|
|
|
249
257
|
'FilesystemFileWrite',
|
|
250
258
|
'FilesystemFile',
|
|
251
259
|
expect.any(Object),
|
|
252
|
-
{
|
|
260
|
+
expect.objectContaining({
|
|
261
|
+
timeoutMs: undefined,
|
|
262
|
+
onWriteCompleted: expect.any(Function),
|
|
263
|
+
})
|
|
253
264
|
);
|
|
254
265
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'PortfolioUpdate', 'Success', {});
|
|
255
266
|
});
|
|
@@ -292,7 +303,10 @@ describe('UploadPortfolio', () => {
|
|
|
292
303
|
append: false,
|
|
293
304
|
ui_percentage: 100,
|
|
294
305
|
},
|
|
295
|
-
{
|
|
306
|
+
expect.objectContaining({
|
|
307
|
+
timeoutMs: undefined,
|
|
308
|
+
onWriteCompleted: expect.any(Function),
|
|
309
|
+
})
|
|
296
310
|
);
|
|
297
311
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'PortfolioUpdate', 'Success', {});
|
|
298
312
|
expect(method.postMessage).not.toHaveBeenCalled();
|
|
@@ -417,14 +431,16 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
417
431
|
}
|
|
418
432
|
return d.features;
|
|
419
433
|
};
|
|
434
|
+
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
435
|
+
requestProtocolV2ProtocolInfo({
|
|
436
|
+
commands: d.getCommands?.() ?? d.commands,
|
|
437
|
+
timeoutMs,
|
|
438
|
+
});
|
|
420
439
|
d.probeProtocolV2RuntimeState ??= async (
|
|
421
440
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
422
441
|
timeoutMs?: number
|
|
423
442
|
) => {
|
|
424
|
-
const protocolInfo = await
|
|
425
|
-
commands: d.getCommands(),
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
443
|
+
const protocolInfo = await d.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
428
444
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
429
445
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
430
446
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -582,7 +598,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
582
598
|
} as any);
|
|
583
599
|
const protocolV2 = buildProtocolV2FeaturesPayload({
|
|
584
600
|
deviceInfo: {
|
|
585
|
-
hw: { serial_no: 'P2-001' },
|
|
601
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'P2-001' },
|
|
586
602
|
coprocessor: { bt_adv_name: 'Pro 2 BLE' },
|
|
587
603
|
},
|
|
588
604
|
deviceStatus: {
|
|
@@ -781,6 +797,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
781
797
|
if (request === 'DeviceStatusGet') {
|
|
782
798
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
783
799
|
}
|
|
800
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
801
|
+
return { type: 'Success', message: {} };
|
|
802
|
+
}
|
|
784
803
|
if (request === 'DeviceSessionGet') {
|
|
785
804
|
return {
|
|
786
805
|
type: 'DeviceSession',
|
|
@@ -805,7 +824,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
824
|
).resolves.toEqual({
|
|
806
825
|
passphraseState: 'state-1',
|
|
807
826
|
newSession: 'session-1',
|
|
808
|
-
unlockedAttachPin:
|
|
827
|
+
unlockedAttachPin: false,
|
|
809
828
|
resumed: false,
|
|
810
829
|
});
|
|
811
830
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -814,7 +833,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
814
833
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
815
834
|
type: DeviceSessionPinType.Main,
|
|
816
835
|
});
|
|
817
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
836
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
837
|
+
passphrase: '',
|
|
838
|
+
on_device: false,
|
|
839
|
+
});
|
|
840
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
841
|
+
seed_domains: [],
|
|
842
|
+
});
|
|
818
843
|
device.passphraseState = 'state-1';
|
|
819
844
|
expect(device.getInternalState()).toBe('session-1');
|
|
820
845
|
});
|
|
@@ -847,6 +872,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
847
872
|
},
|
|
848
873
|
};
|
|
849
874
|
}
|
|
875
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
850
876
|
if (request === 'DeviceSessionGet') {
|
|
851
877
|
return {
|
|
852
878
|
message: {
|
|
@@ -863,9 +889,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
889
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
864
890
|
|
|
865
891
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
866
|
-
expect(
|
|
892
|
+
expect(
|
|
893
|
+
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
894
|
+
).toHaveLength(1);
|
|
895
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
896
|
+
seed_domains: [],
|
|
897
|
+
});
|
|
867
898
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
868
899
|
session_id: 'standard-session',
|
|
900
|
+
btc_test_address: 'standard-state',
|
|
901
|
+
seed_domains: [],
|
|
869
902
|
});
|
|
870
903
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
871
904
|
passphraseState: 'standard-state',
|
|
@@ -907,6 +940,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
907
940
|
},
|
|
908
941
|
};
|
|
909
942
|
}
|
|
943
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
910
944
|
if (request === 'DeviceSessionAskPin') {
|
|
911
945
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
912
946
|
}
|
|
@@ -1001,6 +1035,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1001
1035
|
if (request === 'DeviceStatusGet') {
|
|
1002
1036
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1003
1037
|
}
|
|
1038
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1004
1039
|
if (request === 'DeviceSessionGet') {
|
|
1005
1040
|
sessionGetCalls += 1;
|
|
1006
1041
|
return {
|
|
@@ -1227,7 +1262,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1227
1262
|
passphrase: 'host hidden wallet',
|
|
1228
1263
|
on_device: false,
|
|
1229
1264
|
});
|
|
1230
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1265
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1266
|
+
seed_domains: [],
|
|
1267
|
+
});
|
|
1231
1268
|
});
|
|
1232
1269
|
|
|
1233
1270
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1335,6 +1372,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1335
1372
|
|
|
1336
1373
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1337
1374
|
session_id: 'session-a',
|
|
1375
|
+
btc_test_address: 'state-a',
|
|
1376
|
+
seed_domains: [],
|
|
1338
1377
|
});
|
|
1339
1378
|
expect(device.getInternalState()).toBe('session-b');
|
|
1340
1379
|
});
|
|
@@ -1402,7 +1441,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1402
1441
|
});
|
|
1403
1442
|
expect(typedCall.mock.calls).toEqual([
|
|
1404
1443
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1405
|
-
[
|
|
1444
|
+
[
|
|
1445
|
+
'DeviceSessionGet',
|
|
1446
|
+
'DeviceSession',
|
|
1447
|
+
{
|
|
1448
|
+
session_id: 'session-a',
|
|
1449
|
+
btc_test_address: 'state-a',
|
|
1450
|
+
seed_domains: [],
|
|
1451
|
+
},
|
|
1452
|
+
],
|
|
1406
1453
|
]);
|
|
1407
1454
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1408
1455
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1450,7 +1497,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1450
1497
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1451
1498
|
],
|
|
1452
1499
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1453
|
-
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1500
|
+
['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
|
|
1454
1501
|
]);
|
|
1455
1502
|
});
|
|
1456
1503
|
|
|
@@ -1553,6 +1600,73 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1600
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1601
|
});
|
|
1555
1602
|
|
|
1603
|
+
test.each([false, true])(
|
|
1604
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1605
|
+
async lockFails => {
|
|
1606
|
+
const deviceId = 'attach-mismatch-device';
|
|
1607
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1608
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1609
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1610
|
+
{
|
|
1611
|
+
status: {
|
|
1612
|
+
device_id: deviceId,
|
|
1613
|
+
unlocked: true,
|
|
1614
|
+
passphrase_enabled: true,
|
|
1615
|
+
attach_to_pin_enabled: true,
|
|
1616
|
+
unlocked_by_attach_to_pin: false,
|
|
1617
|
+
},
|
|
1618
|
+
}
|
|
1619
|
+
);
|
|
1620
|
+
device.passphraseState = 'expected-state';
|
|
1621
|
+
|
|
1622
|
+
const typedCall = jest.fn((request: string) => {
|
|
1623
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1624
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1625
|
+
if (request === 'DeviceStatusGet') {
|
|
1626
|
+
return {
|
|
1627
|
+
message: {
|
|
1628
|
+
device_id: deviceId,
|
|
1629
|
+
unlocked: true,
|
|
1630
|
+
passphrase_enabled: true,
|
|
1631
|
+
attach_to_pin_enabled: true,
|
|
1632
|
+
unlocked_by_attach_to_pin: true,
|
|
1633
|
+
},
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
if (request === 'DeviceSessionGet') {
|
|
1637
|
+
return {
|
|
1638
|
+
message: {
|
|
1639
|
+
btc_test_address: 'wrong-attach-state',
|
|
1640
|
+
session_id: 'wrong-attach-session',
|
|
1641
|
+
},
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
if (request === 'LockDevice') {
|
|
1645
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1646
|
+
return { message: { message: 'Device locked' } };
|
|
1647
|
+
}
|
|
1648
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1649
|
+
});
|
|
1650
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1651
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1652
|
+
|
|
1653
|
+
await expect(
|
|
1654
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1655
|
+
).rejects.toMatchObject({
|
|
1656
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1660
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1661
|
+
btc_test_address: 'expected-state',
|
|
1662
|
+
seed_domains: [],
|
|
1663
|
+
});
|
|
1664
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1665
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1666
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1667
|
+
}
|
|
1668
|
+
);
|
|
1669
|
+
|
|
1556
1670
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1671
|
const device = Device.fromDescriptor({
|
|
1558
1672
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1723,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1723
|
}),
|
|
1610
1724
|
{ cancelDeviceOnReject: false }
|
|
1611
1725
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1726
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1727
|
+
DeviceSessionPinType.AttachToPin,
|
|
1728
|
+
expect.objectContaining({
|
|
1729
|
+
emitUiEvent: false,
|
|
1730
|
+
interaction: expect.objectContaining({
|
|
1731
|
+
phase: 'pin',
|
|
1732
|
+
transition: 'start',
|
|
1733
|
+
protocol: 'V2',
|
|
1734
|
+
}),
|
|
1735
|
+
})
|
|
1736
|
+
);
|
|
1613
1737
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1738
|
});
|
|
1615
1739
|
|
|
@@ -1893,12 +2017,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
2017
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
2018
|
});
|
|
1895
2019
|
|
|
1896
|
-
test('
|
|
2020
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
2021
|
const typedCall = jest
|
|
1898
2022
|
.fn()
|
|
1899
2023
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
2024
|
.mockResolvedValueOnce({
|
|
1903
2025
|
message: {
|
|
1904
2026
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2048,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2048
|
await expect(
|
|
1927
2049
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2050
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2051
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2052
|
+
btc_test_address: 'expected-state',
|
|
2053
|
+
seed_domains: [],
|
|
2054
|
+
});
|
|
2055
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2056
|
});
|
|
1940
2057
|
|
|
1941
2058
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,8 +2118,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2118
|
true
|
|
2002
2119
|
);
|
|
2003
2120
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
[
|
|
2005
|
-
|
|
2121
|
+
[
|
|
2122
|
+
'DeviceSessionGet',
|
|
2123
|
+
'DeviceSession',
|
|
2124
|
+
{
|
|
2125
|
+
session_id: 'session-pro2-app',
|
|
2126
|
+
btc_test_address: 'state-pro2-app',
|
|
2127
|
+
seed_domains: [],
|
|
2128
|
+
},
|
|
2129
|
+
],
|
|
2006
2130
|
]);
|
|
2007
2131
|
});
|
|
2008
2132
|
|
|
@@ -2191,7 +2315,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2191
2315
|
passphrase: 'host hidden wallet',
|
|
2192
2316
|
on_device: false,
|
|
2193
2317
|
});
|
|
2194
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2318
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2319
|
+
seed_domains: [],
|
|
2320
|
+
});
|
|
2195
2321
|
});
|
|
2196
2322
|
|
|
2197
2323
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2269,12 +2395,83 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2395
|
);
|
|
2270
2396
|
|
|
2271
2397
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2398
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2399
|
+
btc_test_address: 'expected-state',
|
|
2400
|
+
seed_domains: [],
|
|
2401
|
+
});
|
|
2402
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2403
|
passphrase: 'host hidden wallet',
|
|
2274
2404
|
on_device: false,
|
|
2275
2405
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2406
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2407
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2408
|
+
seed_domains: [],
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
2413
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2414
|
+
const typedCall = jest.fn((request: string) => {
|
|
2415
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2416
|
+
return { message: { version: 2 } };
|
|
2417
|
+
}
|
|
2418
|
+
if (request === 'DeviceStatusGet') {
|
|
2419
|
+
return {
|
|
2420
|
+
message: {
|
|
2421
|
+
device_id: 'attach-wallet-device',
|
|
2422
|
+
unlocked: true,
|
|
2423
|
+
passphrase_enabled: true,
|
|
2424
|
+
attach_to_pin_enabled: true,
|
|
2425
|
+
unlocked_by_attach_to_pin: true,
|
|
2426
|
+
},
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
if (request === 'DeviceSessionAskPin' || request === 'DeviceSessionAskPassphrase') {
|
|
2430
|
+
return { message: { message: 'Wallet selected' } };
|
|
2431
|
+
}
|
|
2432
|
+
if (request === 'DeviceSessionGet') {
|
|
2433
|
+
return {
|
|
2434
|
+
message: {
|
|
2435
|
+
session_id: 'main-wallet-session',
|
|
2436
|
+
btc_test_address: 'main-wallet-state',
|
|
2437
|
+
},
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
if (request === 'LockDevice') {
|
|
2441
|
+
return { message: { message: 'Device locked' } };
|
|
2442
|
+
}
|
|
2443
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2444
|
+
});
|
|
2445
|
+
|
|
2446
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
2447
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2448
|
+
{
|
|
2449
|
+
status: {
|
|
2450
|
+
device_id: 'attach-wallet-device',
|
|
2451
|
+
unlocked: true,
|
|
2452
|
+
passphrase_enabled: true,
|
|
2453
|
+
attach_to_pin_enabled: true,
|
|
2454
|
+
// 模拟缓存滞后:设备状态刷新后才确认当前由 Attach PIN 解锁。
|
|
2455
|
+
unlocked_by_attach_to_pin: false,
|
|
2456
|
+
},
|
|
2457
|
+
}
|
|
2458
|
+
);
|
|
2459
|
+
(device as any).commands = { typedCall };
|
|
2460
|
+
const clearInternalState = jest.spyOn(device, 'clearInternalState');
|
|
2461
|
+
|
|
2462
|
+
await expect(device.checkPassphraseStateSafety(undefined, true, false)).rejects.toMatchObject({
|
|
2463
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2467
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
2468
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2469
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2470
|
+
'DeviceSessionAskPassphrase',
|
|
2471
|
+
'Success',
|
|
2472
|
+
expect.anything()
|
|
2473
|
+
);
|
|
2474
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
2278
2475
|
});
|
|
2279
2476
|
|
|
2280
2477
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2307,10 +2504,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2307
2504
|
await expect(
|
|
2308
2505
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2309
2506
|
).resolves.toBe(true);
|
|
2310
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2507
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
2311
2508
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2509
|
eventless_wallet_session: true,
|
|
2313
2510
|
});
|
|
2511
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2512
|
+
seed_domains: [],
|
|
2513
|
+
});
|
|
2514
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2515
|
+
passphrase: '',
|
|
2516
|
+
on_device: false,
|
|
2517
|
+
});
|
|
2518
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2519
|
+
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2520
|
+
2
|
|
2521
|
+
);
|
|
2522
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2314
2523
|
});
|
|
2315
2524
|
|
|
2316
2525
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2381,7 +2590,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2381
2590
|
uuid: 'CACHED-SERIAL',
|
|
2382
2591
|
deviceId: null,
|
|
2383
2592
|
bleName: 'Cached BLE',
|
|
2384
|
-
name: 'Cached
|
|
2593
|
+
name: 'Cached BLE',
|
|
2385
2594
|
label: 'Cached Label',
|
|
2386
2595
|
deviceType: 'pro2',
|
|
2387
2596
|
});
|
|
@@ -2408,7 +2617,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2408
2617
|
expect(device.features?.label).toBeNull();
|
|
2409
2618
|
});
|
|
2410
2619
|
|
|
2411
|
-
test('
|
|
2620
|
+
test('uses the BLE name for discovery while preserving the Protocol V2 label', () => {
|
|
2412
2621
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2413
2622
|
(device as any).features = {
|
|
2414
2623
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
@@ -2416,9 +2625,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2416
2625
|
bleName: 'Pro2 6136',
|
|
2417
2626
|
};
|
|
2418
2627
|
|
|
2628
|
+
expect(device.getCurrentDisplayName()).toBe('My Pro 2');
|
|
2419
2629
|
expect(device.toMessageObject()).toMatchObject({
|
|
2420
2630
|
bleName: 'Pro2 6136',
|
|
2421
|
-
name: '
|
|
2631
|
+
name: 'Pro2 6136',
|
|
2422
2632
|
label: 'My Pro 2',
|
|
2423
2633
|
});
|
|
2424
2634
|
});
|
|
@@ -2472,10 +2682,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2472
2682
|
hw: true,
|
|
2473
2683
|
fw: true,
|
|
2474
2684
|
coprocessor: true,
|
|
2475
|
-
se1: true,
|
|
2476
|
-
se2: true,
|
|
2477
|
-
se3: true,
|
|
2478
|
-
se4: true,
|
|
2479
2685
|
},
|
|
2480
2686
|
types: {
|
|
2481
2687
|
version: true,
|
|
@@ -2499,8 +2705,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2499
2705
|
).rejects.toThrow('DeviceInfo not supported');
|
|
2500
2706
|
});
|
|
2501
2707
|
|
|
2502
|
-
test('does not inherit Pro or
|
|
2503
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2708
|
+
test('does not inherit Pro or model_touch ranges for Protocol V2 devices', () => {
|
|
2709
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2710
|
+
hw: { Device_type: DeviceType.PRO2 },
|
|
2711
|
+
});
|
|
2504
2712
|
const checkedTypes: string[] = [];
|
|
2505
2713
|
|
|
2506
2714
|
const versionRange = getMethodVersionRange(features, type => {
|
|
@@ -2512,11 +2720,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2512
2720
|
});
|
|
2513
2721
|
|
|
2514
2722
|
expect(versionRange).toBeUndefined();
|
|
2515
|
-
expect(checkedTypes).toEqual(['pro2']);
|
|
2723
|
+
expect(checkedTypes).toEqual(['pro2', 'model_pro2']);
|
|
2516
2724
|
});
|
|
2517
2725
|
|
|
2518
2726
|
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
2519
|
-
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any
|
|
2727
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2728
|
+
hw: { Device_type: DeviceType.PRO2 },
|
|
2729
|
+
});
|
|
2520
2730
|
|
|
2521
2731
|
expect(
|
|
2522
2732
|
getFirmwareUpdateField({
|
|
@@ -2667,10 +2877,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2667
2877
|
hw: true,
|
|
2668
2878
|
fw: true,
|
|
2669
2879
|
coprocessor: true,
|
|
2670
|
-
se1: true,
|
|
2671
|
-
se2: true,
|
|
2672
|
-
se3: true,
|
|
2673
|
-
se4: true,
|
|
2674
2880
|
},
|
|
2675
2881
|
types: {
|
|
2676
2882
|
version: true,
|
|
@@ -2681,7 +2887,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2887
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2888
|
}
|
|
2683
2889
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2890
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2891
|
+
eventless_wallet_session: true,
|
|
2892
|
+
});
|
|
2685
2893
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2894
|
});
|
|
2687
2895
|
|
|
@@ -2719,7 +2927,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2927
|
}),
|
|
2720
2928
|
expect.anything()
|
|
2721
2929
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2930
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2931
|
+
eventless_wallet_session: true,
|
|
2932
|
+
});
|
|
2723
2933
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2934
|
expect(device.features).toMatchObject({
|
|
2725
2935
|
deviceId: null,
|
|
@@ -2985,10 +3195,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3195
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3196
|
},
|
|
2987
3197
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3198
|
.mockResolvedValueOnce({
|
|
2993
3199
|
type: 'DeviceStatus',
|
|
2994
3200
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3216,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3216
|
firmwareVersion: '1.2.4',
|
|
3011
3217
|
passphraseProtection: null,
|
|
3012
3218
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3219
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3220
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3221
|
4,
|
|
3016
3222
|
'DeviceInfoGet',
|
|
@@ -3020,10 +3226,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3020
3226
|
hw: true,
|
|
3021
3227
|
fw: true,
|
|
3022
3228
|
coprocessor: true,
|
|
3023
|
-
se1: true,
|
|
3024
|
-
se2: true,
|
|
3025
|
-
se3: true,
|
|
3026
|
-
se4: true,
|
|
3027
3229
|
},
|
|
3028
3230
|
types: {
|
|
3029
3231
|
version: true,
|
|
@@ -3124,9 +3326,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3326
|
status: { init_states: true },
|
|
3125
3327
|
}
|
|
3126
3328
|
);
|
|
3329
|
+
const pinOnDevice = jest.fn();
|
|
3330
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3331
|
|
|
3128
3332
|
const features = await device.unlockDevice();
|
|
3129
3333
|
|
|
3334
|
+
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3335
|
+
device,
|
|
3336
|
+
DeviceSessionPinType.Main,
|
|
3337
|
+
expect.objectContaining({
|
|
3338
|
+
source: 'unlock-coordinator',
|
|
3339
|
+
reason: 'device-unlock',
|
|
3340
|
+
deviceOnly: true,
|
|
3341
|
+
})
|
|
3342
|
+
);
|
|
3343
|
+
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3344
|
+
typedCall.mock.invocationCallOrder[0]
|
|
3345
|
+
);
|
|
3130
3346
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3347
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3348
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3388,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3388
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3389
|
});
|
|
3174
3390
|
(device as any).commands = { typedCall };
|
|
3391
|
+
const pinOnDevice = jest.fn();
|
|
3392
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3393
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3394
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3395
|
|
|
3396
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3397
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3398
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3399
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3417,71 +3636,77 @@ describe('API compatibility handling', () => {
|
|
|
3417
3636
|
});
|
|
3418
3637
|
});
|
|
3419
3638
|
|
|
3420
|
-
test('
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3639
|
+
test.each(['pro2', 'neo'] as const)(
|
|
3640
|
+
'allows %s Solana signing methods through Protocol V2 version checks',
|
|
3641
|
+
deviceType => {
|
|
3642
|
+
const features = {
|
|
3643
|
+
deviceType,
|
|
3644
|
+
} as Features;
|
|
3424
3645
|
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3646
|
+
const solSignMessageMethod = new SolSignMessage({
|
|
3647
|
+
id: 1,
|
|
3648
|
+
payload: {
|
|
3649
|
+
method: 'solSignMessage',
|
|
3650
|
+
path: "m/44'/501'/0'/0'",
|
|
3651
|
+
messageHex: '48656c6c6f',
|
|
3652
|
+
},
|
|
3653
|
+
});
|
|
3654
|
+
const solSignOffchainMessageMethod = new SolSignOffchainMessage({
|
|
3655
|
+
id: 2,
|
|
3656
|
+
payload: {
|
|
3657
|
+
method: 'solSignOffchainMessage',
|
|
3658
|
+
path: "m/44'/501'/0'/0'",
|
|
3659
|
+
messageHex: '48656c6c6f',
|
|
3660
|
+
},
|
|
3661
|
+
});
|
|
3662
|
+
const solSignTransactionMethod = new SolSignTransaction({
|
|
3663
|
+
id: 3,
|
|
3664
|
+
payload: {
|
|
3665
|
+
method: 'solSignTransaction',
|
|
3666
|
+
path: "m/44'/501'/0'/0'",
|
|
3667
|
+
rawTx: '00',
|
|
3668
|
+
},
|
|
3669
|
+
});
|
|
3670
|
+
const solSignVersionedTransactionMethod = new SolSignTransaction({
|
|
3671
|
+
id: 4,
|
|
3672
|
+
payload: {
|
|
3673
|
+
method: 'solSignTransaction',
|
|
3674
|
+
path: "m/44'/501'/0'/0'",
|
|
3675
|
+
rawTx: '80',
|
|
3676
|
+
},
|
|
3677
|
+
});
|
|
3457
3678
|
|
|
3458
|
-
|
|
3459
|
-
|
|
3679
|
+
solSignTransactionMethod.init();
|
|
3680
|
+
solSignVersionedTransactionMethod.init();
|
|
3460
3681
|
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3682
|
+
expect(
|
|
3683
|
+
getMethodVersionRange(features, type => solSignMessageMethod.getVersionRange()[type])
|
|
3684
|
+
).toEqual({
|
|
3685
|
+
min: '0.0.0',
|
|
3686
|
+
});
|
|
3687
|
+
expect(
|
|
3688
|
+
getMethodVersionRange(
|
|
3689
|
+
features,
|
|
3690
|
+
type => solSignOffchainMessageMethod.getVersionRange()[type]
|
|
3691
|
+
)
|
|
3692
|
+
).toEqual({
|
|
3693
|
+
min: '0.0.0',
|
|
3694
|
+
});
|
|
3695
|
+
expect(
|
|
3696
|
+
getMethodVersionRange(features, type => solSignTransactionMethod.getVersionRange()[type])
|
|
3697
|
+
).toEqual({
|
|
3698
|
+
min: '0.0.0',
|
|
3699
|
+
});
|
|
3700
|
+
expect(
|
|
3701
|
+
getMethodVersionRange(
|
|
3702
|
+
features,
|
|
3703
|
+
type => solSignVersionedTransactionMethod.getVersionRange()[type]
|
|
3704
|
+
)
|
|
3705
|
+
).toEqual({
|
|
3706
|
+
min: '0.0.0',
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
);
|
|
3485
3710
|
|
|
3486
3711
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
3487
3712
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
@@ -3577,6 +3802,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3802
|
|
|
3578
3803
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3804
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3805
|
+
let forceReloadDataSpy: jest.SpyInstance;
|
|
3806
|
+
|
|
3807
|
+
beforeEach(() => {
|
|
3808
|
+
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3809
|
+
});
|
|
3810
|
+
|
|
3811
|
+
afterEach(() => {
|
|
3812
|
+
jest.restoreAllMocks();
|
|
3813
|
+
});
|
|
3580
3814
|
|
|
3581
3815
|
const buildOkppHeader = ({
|
|
3582
3816
|
type = 'RESC',
|
|
@@ -3701,7 +3935,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3701
3935
|
specific: true,
|
|
3702
3936
|
},
|
|
3703
3937
|
},
|
|
3704
|
-
{ timeoutMs:
|
|
3938
|
+
{ timeoutMs: 30000 }
|
|
3705
3939
|
);
|
|
3706
3940
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3707
3941
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3717,7 +3951,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3951
|
});
|
|
3718
3952
|
});
|
|
3719
3953
|
|
|
3720
|
-
test('
|
|
3954
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3955
|
const method = new FirmwareUpdateV4({
|
|
3722
3956
|
id: 1,
|
|
3723
3957
|
payload: {
|
|
@@ -3752,7 +3986,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3986
|
|
|
3753
3987
|
await method.run();
|
|
3754
3988
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3989
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3990
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3991
|
fwBinaryMap: [
|
|
3758
3992
|
{
|
|
@@ -3763,19 +3997,95 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3763
3997
|
],
|
|
3764
3998
|
bootloaderBinary: null,
|
|
3765
3999
|
});
|
|
4000
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3766
4001
|
});
|
|
3767
4002
|
|
|
3768
|
-
test('
|
|
4003
|
+
test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
|
|
3769
4004
|
const method = new FirmwareUpdateV4({
|
|
3770
4005
|
id: 1,
|
|
3771
4006
|
payload: {
|
|
3772
4007
|
method: 'firmwareUpdateV4',
|
|
4008
|
+
platform: 'web',
|
|
4009
|
+
targetsToUpdate: ['resource'],
|
|
3773
4010
|
},
|
|
3774
4011
|
});
|
|
4012
|
+
method.init();
|
|
4013
|
+
|
|
3775
4014
|
(method as any).device = stubDevice({
|
|
3776
|
-
originalDescriptor: { id: 'usb-id', path: '
|
|
3777
|
-
features: {
|
|
3778
|
-
|
|
4015
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4016
|
+
features: {
|
|
4017
|
+
deviceType: 'pro2',
|
|
4018
|
+
firmwareVersion: '1.0.0',
|
|
4019
|
+
mode: 'normal',
|
|
4020
|
+
bootloaderMode: false,
|
|
4021
|
+
capabilities: [],
|
|
4022
|
+
},
|
|
4023
|
+
isBootloader: () => false,
|
|
4024
|
+
isRomloader: () => false,
|
|
4025
|
+
});
|
|
4026
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4027
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4028
|
+
bootloaderBinary: null,
|
|
4029
|
+
fwBinaryMap: [],
|
|
4030
|
+
installItems: [],
|
|
4031
|
+
});
|
|
4032
|
+
|
|
4033
|
+
const order: string[] = [];
|
|
4034
|
+
(method as any).prepareProtocolV2BootResources = jest.fn().mockImplementation(() => {
|
|
4035
|
+
order.push('prepare-startup-resources');
|
|
4036
|
+
return Promise.resolve([]);
|
|
4037
|
+
});
|
|
4038
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4039
|
+
order.push('enter-bootloader');
|
|
4040
|
+
return Promise.resolve(true);
|
|
4041
|
+
});
|
|
4042
|
+
(method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
|
|
4043
|
+
order.push('prepare-resources');
|
|
4044
|
+
return Promise.resolve([
|
|
4045
|
+
{
|
|
4046
|
+
name: 'images.okpkg',
|
|
4047
|
+
binary: new Uint8Array([1]).buffer,
|
|
4048
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
4049
|
+
},
|
|
4050
|
+
]);
|
|
4051
|
+
});
|
|
4052
|
+
(method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
|
|
4053
|
+
order.push('execute-update');
|
|
4054
|
+
return Promise.resolve();
|
|
4055
|
+
});
|
|
4056
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4057
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
4058
|
+
bootloaderVersion: '1.0.0',
|
|
4059
|
+
bleVersion: '1.0.0',
|
|
4060
|
+
firmwareVersion: '1.0.0',
|
|
4061
|
+
});
|
|
4062
|
+
method.postTipMessage = jest.fn();
|
|
4063
|
+
|
|
4064
|
+
await method.run();
|
|
4065
|
+
|
|
4066
|
+
expect(order).toEqual([
|
|
4067
|
+
'prepare-startup-resources',
|
|
4068
|
+
'enter-bootloader',
|
|
4069
|
+
'prepare-resources',
|
|
4070
|
+
'execute-update',
|
|
4071
|
+
]);
|
|
4072
|
+
});
|
|
4073
|
+
|
|
4074
|
+
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
4075
|
+
const method = new FirmwareUpdateV4({
|
|
4076
|
+
id: 1,
|
|
4077
|
+
payload: {
|
|
4078
|
+
method: 'firmwareUpdateV4',
|
|
4079
|
+
},
|
|
4080
|
+
});
|
|
4081
|
+
(method as any).device = stubDevice({
|
|
4082
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4083
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
4084
|
+
isBootloader: () => false,
|
|
4085
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4086
|
+
mode: 'bootloader',
|
|
4087
|
+
bootloaderMode: true,
|
|
4088
|
+
}),
|
|
3779
4089
|
});
|
|
3780
4090
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
3781
4091
|
message: 'Device rebooted successfully',
|
|
@@ -3803,6 +4113,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3803
4113
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3804
4114
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
3805
4115
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4116
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
3806
4117
|
method.postTipMessage = jest.fn();
|
|
3807
4118
|
|
|
3808
4119
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
@@ -3814,6 +4125,66 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3814
4125
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3815
4126
|
});
|
|
3816
4127
|
|
|
4128
|
+
test('announces bootloader success only after Protocol V2 bootloader mode is confirmed', async () => {
|
|
4129
|
+
const method = new FirmwareUpdateV4({
|
|
4130
|
+
id: 1,
|
|
4131
|
+
payload: {
|
|
4132
|
+
method: 'firmwareUpdateV4',
|
|
4133
|
+
},
|
|
4134
|
+
});
|
|
4135
|
+
(method as any).device = stubDevice({
|
|
4136
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
4137
|
+
isBootloader: () => false,
|
|
4138
|
+
isRomloader: () => false,
|
|
4139
|
+
});
|
|
4140
|
+
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4141
|
+
message: 'Device rebooted successfully',
|
|
4142
|
+
});
|
|
4143
|
+
|
|
4144
|
+
const tipMessages: string[] = [];
|
|
4145
|
+
method.postTipMessage = jest.fn((message: string) => {
|
|
4146
|
+
tipMessages.push(message);
|
|
4147
|
+
});
|
|
4148
|
+
let successWasAnnouncedBeforeConfirmation = false;
|
|
4149
|
+
(method as any).waitForProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4150
|
+
successWasAnnouncedBeforeConfirmation = tipMessages.includes('GoToBootloaderSuccess');
|
|
4151
|
+
return Promise.resolve({ mode: 'bootloader' });
|
|
4152
|
+
});
|
|
4153
|
+
|
|
4154
|
+
await (method as any).enterProtocolV2BootloaderMode();
|
|
4155
|
+
|
|
4156
|
+
expect(successWasAnnouncedBeforeConfirmation).toBe(false);
|
|
4157
|
+
expect(tipMessages).toEqual(['AutoRebootToBootloader', 'GoToBootloaderSuccess']);
|
|
4158
|
+
});
|
|
4159
|
+
|
|
4160
|
+
test('keeps Protocol V2 bootloader active before firmware transfer', async () => {
|
|
4161
|
+
const method = new FirmwareUpdateV4({
|
|
4162
|
+
id: 1,
|
|
4163
|
+
payload: {
|
|
4164
|
+
method: 'firmwareUpdateV4',
|
|
4165
|
+
},
|
|
4166
|
+
});
|
|
4167
|
+
(method as any).device = stubDevice({
|
|
4168
|
+
originalDescriptor: { id: 'usb-id', path: 'bootloader-path', protocolType: 'V2' },
|
|
4169
|
+
features: {
|
|
4170
|
+
deviceType: 'pro2',
|
|
4171
|
+
mode: 'bootloader',
|
|
4172
|
+
bootloaderMode: true,
|
|
4173
|
+
capabilities: [],
|
|
4174
|
+
},
|
|
4175
|
+
isBootloader: () => true,
|
|
4176
|
+
isRomloader: () => false,
|
|
4177
|
+
});
|
|
4178
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4179
|
+
method.postTipMessage = jest.fn();
|
|
4180
|
+
|
|
4181
|
+
await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
|
|
4182
|
+
|
|
4183
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4184
|
+
expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
4185
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
4186
|
+
});
|
|
4187
|
+
|
|
3817
4188
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
3818
4189
|
const method = new FirmwareUpdateV4({
|
|
3819
4190
|
id: 1,
|
|
@@ -3866,6 +4237,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
4237
|
},
|
|
3867
4238
|
});
|
|
3868
4239
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4240
|
+
const prepareResourceBundles = jest
|
|
4241
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4242
|
+
.mockResolvedValue(undefined);
|
|
3869
4243
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
4244
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
4245
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +4252,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
4252
|
|
|
3879
4253
|
await method.run();
|
|
3880
4254
|
|
|
4255
|
+
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
3881
4256
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
4257
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
4258
|
bootloaderBinary: null,
|
|
@@ -3897,6 +4272,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3897
4272
|
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
3898
4273
|
features: { bootloader_mode: false, capabilities: [] },
|
|
3899
4274
|
isBootloader: () => false,
|
|
4275
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4276
|
+
mode: 'bootloader',
|
|
4277
|
+
bootloaderMode: true,
|
|
4278
|
+
}),
|
|
3900
4279
|
});
|
|
3901
4280
|
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
3902
4281
|
if (requestType === 'DeviceInfoGet') {
|
|
@@ -3924,15 +4303,48 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3924
4303
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3925
4304
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
3926
4305
|
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4306
|
+
(method as any).protocolV2ExpectedPath = 'app-path';
|
|
3927
4307
|
|
|
3928
4308
|
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
3929
4309
|
|
|
3930
4310
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
4311
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4312
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
4313
|
+
});
|
|
4314
|
+
|
|
4315
|
+
test('polls again when Protocol V2 bootloader serial is temporarily unavailable', async () => {
|
|
4316
|
+
const method = new FirmwareUpdateV4({
|
|
4317
|
+
id: 1,
|
|
4318
|
+
payload: {
|
|
4319
|
+
method: 'firmwareUpdateV4',
|
|
4320
|
+
},
|
|
4321
|
+
});
|
|
4322
|
+
const typedCall = jest
|
|
4323
|
+
.fn()
|
|
4324
|
+
.mockResolvedValueOnce({
|
|
4325
|
+
type: 'DeviceInfo',
|
|
4326
|
+
message: { protocol_version: 1, hw: {} },
|
|
4327
|
+
})
|
|
4328
|
+
.mockResolvedValueOnce({
|
|
4329
|
+
type: 'DeviceInfo',
|
|
4330
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
4331
|
+
});
|
|
4332
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4333
|
+
(method as any).device = stubDevice({
|
|
4334
|
+
originalDescriptor: { id: 'ble-id', protocolType: 'V2' },
|
|
4335
|
+
getCommands: () => ({ typedCall }),
|
|
4336
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4337
|
+
mode: 'bootloader',
|
|
4338
|
+
bootloaderMode: true,
|
|
4339
|
+
}),
|
|
4340
|
+
});
|
|
4341
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4342
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4343
|
+
|
|
4344
|
+
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
4345
|
+
|
|
4346
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
3931
4347
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
3932
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3933
|
-
'DeviceInfoGet',
|
|
3934
|
-
'ProtocolInfoRequest',
|
|
3935
|
-
]);
|
|
3936
4348
|
});
|
|
3937
4349
|
|
|
3938
4350
|
test('does not run generic initialize during Protocol V2 USB firmware reconnect', async () => {
|
|
@@ -3960,12 +4372,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3960
4372
|
getCommands: () => commands,
|
|
3961
4373
|
mainId: 'usb-path',
|
|
3962
4374
|
});
|
|
3963
|
-
const cachedDevice = {
|
|
4375
|
+
const cachedDevice = {
|
|
4376
|
+
getConnectId: () => 'usb-path',
|
|
4377
|
+
getCurrentSerialNo: () => '',
|
|
4378
|
+
};
|
|
3964
4379
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
3965
4380
|
devices: {},
|
|
3966
4381
|
deviceList: [cachedDevice],
|
|
3967
4382
|
} as any);
|
|
3968
4383
|
(method as any).device = device;
|
|
4384
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
3969
4385
|
|
|
3970
4386
|
try {
|
|
3971
4387
|
await (method as any).reconnectProtocolV2Device();
|
|
@@ -3977,133 +4393,335 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3977
4393
|
expect(initialize).not.toHaveBeenCalled();
|
|
3978
4394
|
});
|
|
3979
4395
|
|
|
3980
|
-
test('
|
|
4396
|
+
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
3981
4397
|
const method = new FirmwareUpdateV4({
|
|
3982
4398
|
id: 1,
|
|
3983
4399
|
payload: {
|
|
3984
4400
|
method: 'firmwareUpdateV4',
|
|
3985
4401
|
},
|
|
3986
4402
|
});
|
|
3987
|
-
const commands = { disposed:
|
|
3988
|
-
const
|
|
4403
|
+
const commands = { disposed: true, mainId: '' };
|
|
4404
|
+
const updateFromCache = jest.fn();
|
|
3989
4405
|
const device = stubDevice({
|
|
3990
|
-
originalDescriptor: {
|
|
3991
|
-
id: 'usb-id',
|
|
3992
|
-
path: 'usb-path',
|
|
3993
|
-
session: 'usb-session',
|
|
3994
|
-
protocolType: 'V2',
|
|
3995
|
-
},
|
|
4406
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
3996
4407
|
deviceConnector: {
|
|
3997
4408
|
enumerate: jest.fn().mockResolvedValue({
|
|
3998
4409
|
descriptors: [
|
|
3999
|
-
{
|
|
4000
|
-
|
|
4001
|
-
path: 'usb-path',
|
|
4002
|
-
session: 'usb-session',
|
|
4003
|
-
protocolType: 'V2',
|
|
4004
|
-
},
|
|
4410
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4411
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4005
4412
|
],
|
|
4006
4413
|
}),
|
|
4007
4414
|
},
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
acquire,
|
|
4415
|
+
updateFromCache,
|
|
4416
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4417
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4012
4418
|
commands,
|
|
4013
4419
|
getCommands: () => commands,
|
|
4014
|
-
mainId: 'usb-
|
|
4420
|
+
mainId: 'old-usb-path',
|
|
4015
4421
|
});
|
|
4422
|
+
const otherDevice = {
|
|
4423
|
+
getConnectId: () => 'usb-a',
|
|
4424
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4425
|
+
};
|
|
4426
|
+
const expectedDevice = {
|
|
4427
|
+
getConnectId: () => 'usb-b',
|
|
4428
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4429
|
+
};
|
|
4016
4430
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4017
4431
|
devices: {},
|
|
4018
|
-
deviceList: [
|
|
4432
|
+
deviceList: [otherDevice, expectedDevice],
|
|
4019
4433
|
} as any);
|
|
4020
4434
|
(method as any).device = device;
|
|
4435
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4436
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4021
4437
|
|
|
4022
4438
|
try {
|
|
4023
4439
|
await (method as any).reconnectProtocolV2Device();
|
|
4024
|
-
await (method as any).reconnectProtocolV2Device();
|
|
4025
4440
|
} finally {
|
|
4026
4441
|
getDevices.mockRestore();
|
|
4027
4442
|
}
|
|
4028
4443
|
|
|
4029
|
-
expect(
|
|
4030
|
-
expect(device.hasDeviceAcquire).toHaveBeenCalledTimes(2);
|
|
4444
|
+
expect(updateFromCache).toHaveBeenCalledWith(expectedDevice);
|
|
4031
4445
|
});
|
|
4032
4446
|
|
|
4033
|
-
test('
|
|
4447
|
+
test('rejects ambiguous Protocol V2 USB reconnect candidates without an identity match', async () => {
|
|
4034
4448
|
const method = new FirmwareUpdateV4({
|
|
4035
4449
|
id: 1,
|
|
4036
4450
|
payload: {
|
|
4037
4451
|
method: 'firmwareUpdateV4',
|
|
4038
4452
|
},
|
|
4039
4453
|
});
|
|
4040
|
-
const
|
|
4041
|
-
const
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4454
|
+
const updateFromCache = jest.fn();
|
|
4455
|
+
const device = stubDevice({
|
|
4456
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4457
|
+
deviceConnector: {
|
|
4458
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4459
|
+
descriptors: [
|
|
4460
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4461
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4462
|
+
],
|
|
4463
|
+
}),
|
|
4046
4464
|
},
|
|
4465
|
+
updateFromCache,
|
|
4047
4466
|
});
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4467
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4468
|
+
devices: {},
|
|
4469
|
+
deviceList: [
|
|
4470
|
+
{
|
|
4471
|
+
getConnectId: () => 'usb-a',
|
|
4472
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4473
|
+
},
|
|
4474
|
+
{
|
|
4475
|
+
getConnectId: () => 'usb-b',
|
|
4476
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4477
|
+
},
|
|
4478
|
+
],
|
|
4479
|
+
} as any);
|
|
4480
|
+
(method as any).device = device;
|
|
4481
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-C';
|
|
4058
4482
|
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4483
|
+
try {
|
|
4484
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4485
|
+
} finally {
|
|
4486
|
+
getDevices.mockRestore();
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4063
4490
|
});
|
|
4064
4491
|
|
|
4065
|
-
test('
|
|
4492
|
+
test('allows a Protocol V2 USB reconnect candidate without a serial only when its path matches', async () => {
|
|
4066
4493
|
const method = new FirmwareUpdateV4({
|
|
4067
4494
|
id: 1,
|
|
4068
4495
|
payload: {
|
|
4069
4496
|
method: 'firmwareUpdateV4',
|
|
4070
4497
|
},
|
|
4071
4498
|
});
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4499
|
+
const commands = { disposed: true, mainId: '' };
|
|
4500
|
+
const updateFromCache = jest.fn();
|
|
4501
|
+
const device = stubDevice({
|
|
4502
|
+
originalDescriptor: { id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4503
|
+
deviceConnector: {
|
|
4504
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4505
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4506
|
+
}),
|
|
4507
|
+
},
|
|
4508
|
+
updateFromCache,
|
|
4509
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4510
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4511
|
+
commands,
|
|
4512
|
+
getCommands: () => commands,
|
|
4513
|
+
mainId: 'old-usb-path',
|
|
4077
4514
|
});
|
|
4515
|
+
const candidate = {
|
|
4516
|
+
getConnectId: () => 'usb-a',
|
|
4517
|
+
getCurrentSerialNo: () => '',
|
|
4518
|
+
};
|
|
4519
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4520
|
+
devices: {},
|
|
4521
|
+
deviceList: [candidate],
|
|
4522
|
+
} as any);
|
|
4523
|
+
(method as any).device = device;
|
|
4524
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-A';
|
|
4525
|
+
(method as any).protocolV2ExpectedPath = 'usb-a';
|
|
4078
4526
|
|
|
4079
|
-
|
|
4527
|
+
try {
|
|
4528
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4529
|
+
} finally {
|
|
4530
|
+
getDevices.mockRestore();
|
|
4531
|
+
}
|
|
4080
4532
|
|
|
4081
|
-
expect(
|
|
4082
|
-
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4083
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4533
|
+
expect(updateFromCache).toHaveBeenCalledWith(candidate);
|
|
4084
4534
|
});
|
|
4085
4535
|
|
|
4086
|
-
test('
|
|
4536
|
+
test('rejects a single Protocol V2 USB reconnect candidate with a different physical serial', async () => {
|
|
4087
4537
|
const method = new FirmwareUpdateV4({
|
|
4088
4538
|
id: 1,
|
|
4089
4539
|
payload: {
|
|
4090
4540
|
method: 'firmwareUpdateV4',
|
|
4091
4541
|
},
|
|
4092
4542
|
});
|
|
4093
|
-
const
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
4104
|
-
message: 'Device rebooted successfully',
|
|
4543
|
+
const updateFromCache = jest.fn();
|
|
4544
|
+
const device = stubDevice({
|
|
4545
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4546
|
+
deviceConnector: {
|
|
4547
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4548
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4549
|
+
}),
|
|
4550
|
+
},
|
|
4551
|
+
updateFromCache,
|
|
4105
4552
|
});
|
|
4106
|
-
|
|
4553
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4554
|
+
devices: {},
|
|
4555
|
+
deviceList: [
|
|
4556
|
+
{
|
|
4557
|
+
getConnectId: () => 'usb-a',
|
|
4558
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4559
|
+
},
|
|
4560
|
+
],
|
|
4561
|
+
} as any);
|
|
4562
|
+
(method as any).device = device;
|
|
4563
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4564
|
+
|
|
4565
|
+
try {
|
|
4566
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4567
|
+
} finally {
|
|
4568
|
+
getDevices.mockRestore();
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4572
|
+
});
|
|
4573
|
+
|
|
4574
|
+
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4575
|
+
const method = new FirmwareUpdateV4({
|
|
4576
|
+
id: 1,
|
|
4577
|
+
payload: {
|
|
4578
|
+
method: 'firmwareUpdateV4',
|
|
4579
|
+
},
|
|
4580
|
+
});
|
|
4581
|
+
const commands = { disposed: false, mainId: 'usb-session' };
|
|
4582
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
4583
|
+
const device = stubDevice({
|
|
4584
|
+
originalDescriptor: {
|
|
4585
|
+
id: 'usb-id',
|
|
4586
|
+
path: 'usb-path',
|
|
4587
|
+
session: 'usb-session',
|
|
4588
|
+
protocolType: 'V2',
|
|
4589
|
+
},
|
|
4590
|
+
deviceConnector: {
|
|
4591
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4592
|
+
descriptors: [
|
|
4593
|
+
{
|
|
4594
|
+
id: 'usb-id',
|
|
4595
|
+
path: 'usb-path',
|
|
4596
|
+
session: 'usb-session',
|
|
4597
|
+
protocolType: 'V2',
|
|
4598
|
+
},
|
|
4599
|
+
],
|
|
4600
|
+
}),
|
|
4601
|
+
},
|
|
4602
|
+
updateDescriptor: jest.fn(),
|
|
4603
|
+
updateFromCache: jest.fn(),
|
|
4604
|
+
hasDeviceAcquire: jest.fn(() => true),
|
|
4605
|
+
acquire,
|
|
4606
|
+
commands,
|
|
4607
|
+
getCommands: () => commands,
|
|
4608
|
+
mainId: 'usb-session',
|
|
4609
|
+
});
|
|
4610
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4611
|
+
devices: {},
|
|
4612
|
+
deviceList: [{ getConnectId: () => 'usb-path' }],
|
|
4613
|
+
} as any);
|
|
4614
|
+
(method as any).device = device;
|
|
4615
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4616
|
+
|
|
4617
|
+
try {
|
|
4618
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4619
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4620
|
+
} finally {
|
|
4621
|
+
getDevices.mockRestore();
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
expect(acquire).not.toHaveBeenCalled();
|
|
4625
|
+
expect(device.hasDeviceAcquire).toHaveBeenCalledTimes(2);
|
|
4626
|
+
});
|
|
4627
|
+
|
|
4628
|
+
test('stops final reconnect polling immediately when firmware changes device identity', async () => {
|
|
4629
|
+
const method = new FirmwareUpdateV4({
|
|
4630
|
+
id: 1,
|
|
4631
|
+
payload: {
|
|
4632
|
+
method: 'firmwareUpdateV4',
|
|
4633
|
+
},
|
|
4634
|
+
});
|
|
4635
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4636
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4637
|
+
type: 'DeviceInfo',
|
|
4638
|
+
message: {
|
|
4639
|
+
hw: { serial_no: 'PR2SERIAL' },
|
|
4640
|
+
fw: { application: { version: '1.0.0' } },
|
|
4641
|
+
},
|
|
4642
|
+
});
|
|
4643
|
+
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
4644
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4645
|
+
(method as any).device = stubDevice({
|
|
4646
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4647
|
+
getCommands: () => ({ typedCall }),
|
|
4648
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4649
|
+
deviceId: 'changed-device',
|
|
4650
|
+
mode: 'normal',
|
|
4651
|
+
bootloaderMode: false,
|
|
4652
|
+
}),
|
|
4653
|
+
});
|
|
4654
|
+
|
|
4655
|
+
await expect((method as any).waitForProtocolV2ReconnectAndFeatures(60_000)).rejects.toThrow(
|
|
4656
|
+
'identity mismatch'
|
|
4657
|
+
);
|
|
4658
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4659
|
+
});
|
|
4660
|
+
|
|
4661
|
+
test('reboots Protocol V2 firmware flow back to normal without legacy switch-firmware prompt', async () => {
|
|
4662
|
+
const method = new FirmwareUpdateV4({
|
|
4663
|
+
id: 1,
|
|
4664
|
+
payload: {
|
|
4665
|
+
method: 'firmwareUpdateV4',
|
|
4666
|
+
},
|
|
4667
|
+
});
|
|
4668
|
+
method.postTipMessage = jest.fn();
|
|
4669
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4670
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4671
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4672
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4673
|
+
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4674
|
+
message: 'Device rebooted successfully',
|
|
4675
|
+
});
|
|
4676
|
+
|
|
4677
|
+
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
4678
|
+
|
|
4679
|
+
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4680
|
+
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4681
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4682
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4683
|
+
});
|
|
4684
|
+
|
|
4685
|
+
test('skips a second Normal reboot after Pro2 already returned to App mode', async () => {
|
|
4686
|
+
const method = new FirmwareUpdateV4({
|
|
4687
|
+
id: 1,
|
|
4688
|
+
payload: {
|
|
4689
|
+
method: 'firmwareUpdateV4',
|
|
4690
|
+
},
|
|
4691
|
+
});
|
|
4692
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4693
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4694
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4695
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4696
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4697
|
+
|
|
4698
|
+
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
4699
|
+
|
|
4700
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4701
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4702
|
+
});
|
|
4703
|
+
|
|
4704
|
+
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4705
|
+
const method = new FirmwareUpdateV4({
|
|
4706
|
+
id: 1,
|
|
4707
|
+
payload: {
|
|
4708
|
+
method: 'firmwareUpdateV4',
|
|
4709
|
+
},
|
|
4710
|
+
});
|
|
4711
|
+
const typedCall = jest
|
|
4712
|
+
.fn()
|
|
4713
|
+
.mockRejectedValue(
|
|
4714
|
+
new Error("The operation couldn't be completed. (MultiplatformBleAdapter.RxError error 6.)")
|
|
4715
|
+
);
|
|
4716
|
+
|
|
4717
|
+
(method as any).device = stubDevice({
|
|
4718
|
+
getCommands: () => ({ typedCall }),
|
|
4719
|
+
});
|
|
4720
|
+
|
|
4721
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
4722
|
+
message: 'Device rebooted successfully',
|
|
4723
|
+
});
|
|
4724
|
+
});
|
|
4107
4725
|
|
|
4108
4726
|
test('treats direct disconnect during Protocol V2 normal reboot as expected', async () => {
|
|
4109
4727
|
const method = new FirmwareUpdateV4({
|
|
@@ -4125,6 +4743,27 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4125
4743
|
});
|
|
4126
4744
|
});
|
|
4127
4745
|
|
|
4746
|
+
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
4747
|
+
const method = new FirmwareUpdateV4({
|
|
4748
|
+
id: 1,
|
|
4749
|
+
payload: {
|
|
4750
|
+
method: 'firmwareUpdateV4',
|
|
4751
|
+
},
|
|
4752
|
+
});
|
|
4753
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
4754
|
+
|
|
4755
|
+
(method as any).device = stubDevice({
|
|
4756
|
+
getCommands: () => ({ typedCall }),
|
|
4757
|
+
});
|
|
4758
|
+
|
|
4759
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Bootloader)).resolves.toEqual({
|
|
4760
|
+
message: 'Device rebooted successfully',
|
|
4761
|
+
});
|
|
4762
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
4763
|
+
DeviceRebootType.Bootloader
|
|
4764
|
+
);
|
|
4765
|
+
});
|
|
4766
|
+
|
|
4128
4767
|
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4129
4768
|
const method = new FirmwareUpdateV4({
|
|
4130
4769
|
id: 1,
|
|
@@ -4135,9 +4774,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4774
|
const typedCall = jest
|
|
4136
4775
|
.fn()
|
|
4137
4776
|
.mockRejectedValue(
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4777
|
+
new DOMException(
|
|
4778
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4779
|
+
'NotFoundError'
|
|
4141
4780
|
)
|
|
4142
4781
|
);
|
|
4143
4782
|
|
|
@@ -4215,7 +4854,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4215
4854
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4216
4855
|
]);
|
|
4217
4856
|
|
|
4218
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4857
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
4219
4858
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4220
4859
|
'DeviceFirmwareUpdateStatusGet',
|
|
4221
4860
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4253,6 +4892,46 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4253
4892
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4254
4893
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4255
4894
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4895
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4896
|
+
});
|
|
4897
|
+
|
|
4898
|
+
test('accepts an empty firmware status dump only after confirming normal mode', async () => {
|
|
4899
|
+
const method = new FirmwareUpdateV4({
|
|
4900
|
+
id: 1,
|
|
4901
|
+
payload: {
|
|
4902
|
+
method: 'firmwareUpdateV4',
|
|
4903
|
+
},
|
|
4904
|
+
});
|
|
4905
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4906
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4907
|
+
message: { records: [] },
|
|
4908
|
+
});
|
|
4909
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4910
|
+
let now = 0;
|
|
4911
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4912
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4913
|
+
now += 31 * 1000;
|
|
4914
|
+
callback();
|
|
4915
|
+
return 0 as any;
|
|
4916
|
+
}) as typeof setTimeout);
|
|
4917
|
+
|
|
4918
|
+
(method as any).device = stubDevice({
|
|
4919
|
+
getCommands: () => ({ typedCall }),
|
|
4920
|
+
});
|
|
4921
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4922
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4923
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4924
|
+
method.postProgressMessage = jest.fn();
|
|
4925
|
+
|
|
4926
|
+
await expect(
|
|
4927
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4928
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4929
|
+
])
|
|
4930
|
+
).resolves.toBeUndefined();
|
|
4931
|
+
|
|
4932
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4933
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4934
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4256
4935
|
});
|
|
4257
4936
|
|
|
4258
4937
|
test.each([
|
|
@@ -4318,54 +4997,65 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4997
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4998
|
});
|
|
4320
4999
|
|
|
4321
|
-
test('
|
|
5000
|
+
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4322
5001
|
const method = new FirmwareUpdateV4({
|
|
4323
5002
|
id: 1,
|
|
4324
5003
|
payload: {
|
|
4325
5004
|
method: 'firmwareUpdateV4',
|
|
4326
|
-
platform: 'desktop',
|
|
4327
|
-
expectedTargetVersions: {
|
|
4328
|
-
app_v1: '2.0.0',
|
|
4329
|
-
},
|
|
4330
5005
|
},
|
|
4331
5006
|
});
|
|
4332
|
-
|
|
5007
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5008
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5009
|
+
message: {
|
|
5010
|
+
records: [
|
|
5011
|
+
{ target_id: 3, status: 2 },
|
|
5012
|
+
{ target_id: 4, status: 2 },
|
|
5013
|
+
],
|
|
5014
|
+
},
|
|
5015
|
+
});
|
|
5016
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5017
|
+
|
|
5018
|
+
(method as any).device = stubDevice({
|
|
5019
|
+
getCommands: () => ({ typedCall }),
|
|
5020
|
+
});
|
|
5021
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5022
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4333
5023
|
method.postProgressMessage = jest.fn();
|
|
4334
5024
|
|
|
4335
|
-
expect(
|
|
4336
|
-
(method as any).
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
)
|
|
4340
|
-
).
|
|
5025
|
+
await expect(
|
|
5026
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5027
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
5028
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
5029
|
+
])
|
|
5030
|
+
).resolves.toBeUndefined();
|
|
5031
|
+
|
|
5032
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
5033
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5034
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5035
|
+
'DeviceFirmwareUpdateStatus',
|
|
5036
|
+
expect.anything(),
|
|
5037
|
+
expect.anything()
|
|
5038
|
+
);
|
|
5039
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4341
5040
|
});
|
|
4342
5041
|
|
|
4343
|
-
test('fails
|
|
5042
|
+
test('fails clearly when a requested target stays absent from the full status dump', async () => {
|
|
4344
5043
|
const method = new FirmwareUpdateV4({
|
|
4345
5044
|
id: 1,
|
|
4346
5045
|
payload: {
|
|
4347
5046
|
method: 'firmwareUpdateV4',
|
|
4348
|
-
platform: 'desktop',
|
|
4349
|
-
expectedTargetVersions: {
|
|
4350
|
-
app_v1: '2.0.0',
|
|
4351
|
-
},
|
|
4352
5047
|
},
|
|
4353
5048
|
});
|
|
4354
|
-
method.init();
|
|
4355
5049
|
const typedCall = jest.fn().mockResolvedValue({
|
|
4356
5050
|
type: 'DeviceFirmwareUpdateStatus',
|
|
4357
5051
|
message: {
|
|
4358
|
-
records: [{ target_id: 4, status: 2
|
|
5052
|
+
records: [{ target_id: 4, status: 2 }],
|
|
4359
5053
|
},
|
|
4360
5054
|
});
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
.mockReturnValue(300_001);
|
|
4366
|
-
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
4367
|
-
callback: () => void
|
|
4368
|
-
) => {
|
|
5055
|
+
let now = 0;
|
|
5056
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5057
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5058
|
+
now += 31 * 1000;
|
|
4369
5059
|
callback();
|
|
4370
5060
|
return 0 as any;
|
|
4371
5061
|
}) as typeof setTimeout);
|
|
@@ -4375,45 +5065,52 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4375
5065
|
});
|
|
4376
5066
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4377
5067
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5068
|
+
method.postProgressMessage = jest.fn();
|
|
4378
5069
|
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
dateNowSpy.mockRestore();
|
|
4390
|
-
setTimeoutSpy.mockRestore();
|
|
4391
|
-
}
|
|
4392
|
-
|
|
4393
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
5070
|
+
await expect(
|
|
5071
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5072
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5073
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5074
|
+
])
|
|
5075
|
+
).rejects.toMatchObject({
|
|
5076
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
5077
|
+
message: expect.stringContaining('targetIds=3'),
|
|
5078
|
+
});
|
|
5079
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4394
5080
|
});
|
|
4395
5081
|
|
|
4396
|
-
test('
|
|
5082
|
+
test('resets the missing-record grace period after a Pro2 reboot interruption', async () => {
|
|
4397
5083
|
const method = new FirmwareUpdateV4({
|
|
4398
5084
|
id: 1,
|
|
4399
5085
|
payload: {
|
|
4400
5086
|
method: 'firmwareUpdateV4',
|
|
4401
5087
|
},
|
|
4402
5088
|
});
|
|
4403
|
-
const typedCall = jest
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
.
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
5089
|
+
const typedCall = jest
|
|
5090
|
+
.fn()
|
|
5091
|
+
.mockResolvedValueOnce({
|
|
5092
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5093
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5094
|
+
})
|
|
5095
|
+
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
5096
|
+
.mockResolvedValueOnce({
|
|
5097
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5098
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5099
|
+
})
|
|
5100
|
+
.mockResolvedValueOnce({
|
|
5101
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5102
|
+
message: {
|
|
5103
|
+
records: [
|
|
5104
|
+
{ target_id: 3, status: 2 },
|
|
5105
|
+
{ target_id: 4, status: 2 },
|
|
5106
|
+
],
|
|
5107
|
+
},
|
|
5108
|
+
});
|
|
5109
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5110
|
+
let now = 0;
|
|
5111
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5112
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5113
|
+
now += 31 * 1000;
|
|
4417
5114
|
callback();
|
|
4418
5115
|
return 0 as any;
|
|
4419
5116
|
}) as typeof setTimeout);
|
|
@@ -4421,118 +5118,70 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4421
5118
|
(method as any).device = stubDevice({
|
|
4422
5119
|
getCommands: () => ({ typedCall }),
|
|
4423
5120
|
});
|
|
4424
|
-
(method as any).reconnectProtocolV2Device =
|
|
5121
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4425
5122
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5123
|
+
method.postProgressMessage = jest.fn();
|
|
4426
5124
|
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
params: {
|
|
4436
|
-
firmwareUpdateCode: 'FirmwareInstallStatusConflict',
|
|
4437
|
-
},
|
|
4438
|
-
});
|
|
4439
|
-
} finally {
|
|
4440
|
-
dateNowSpy.mockRestore();
|
|
4441
|
-
setTimeoutSpy.mockRestore();
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
5125
|
+
await expect(
|
|
5126
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5127
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5128
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5129
|
+
])
|
|
5130
|
+
).resolves.toBeUndefined();
|
|
5131
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
5132
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
4445
5133
|
});
|
|
4446
5134
|
|
|
4447
|
-
test('
|
|
5135
|
+
test('uses an eight-minute Pro2 install status window', async () => {
|
|
4448
5136
|
const method = new FirmwareUpdateV4({
|
|
4449
5137
|
id: 1,
|
|
4450
5138
|
payload: {
|
|
4451
5139
|
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
|
-
},
|
|
4458
5140
|
},
|
|
4459
5141
|
});
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
patch_version: 0,
|
|
4465
|
-
};
|
|
4466
|
-
(method as any).protocolV2FinalStatusVerified = true;
|
|
5142
|
+
jest
|
|
5143
|
+
.spyOn(Date, 'now')
|
|
5144
|
+
.mockReturnValueOnce(0)
|
|
5145
|
+
.mockReturnValue(8 * 60 * 1000);
|
|
4467
5146
|
|
|
4468
|
-
expect(
|
|
5147
|
+
await expect(
|
|
5148
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5149
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5150
|
+
])
|
|
5151
|
+
).rejects.toThrow('within 480s');
|
|
4469
5152
|
});
|
|
4470
5153
|
|
|
4471
|
-
test('
|
|
5154
|
+
test('uses a 90-second Pro2 bootloader reconnect window', async () => {
|
|
4472
5155
|
const method = new FirmwareUpdateV4({
|
|
4473
5156
|
id: 1,
|
|
4474
5157
|
payload: {
|
|
4475
5158
|
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
|
-
},
|
|
4482
5159
|
},
|
|
4483
5160
|
});
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
patch_version: 0,
|
|
4489
|
-
};
|
|
5161
|
+
jest
|
|
5162
|
+
.spyOn(Date, 'now')
|
|
5163
|
+
.mockReturnValueOnce(0)
|
|
5164
|
+
.mockReturnValue(90 * 1000);
|
|
4490
5165
|
|
|
4491
|
-
expect((
|
|
4492
|
-
'has no observable final version after status fallback'
|
|
4493
|
-
);
|
|
5166
|
+
await expect((method as any).waitForProtocolV2BootloaderMode()).rejects.toThrow('within 90s');
|
|
4494
5167
|
});
|
|
4495
5168
|
|
|
4496
|
-
test('
|
|
5169
|
+
test('uses a three-minute Pro2 final reconnect window', async () => {
|
|
4497
5170
|
const method = new FirmwareUpdateV4({
|
|
4498
5171
|
id: 1,
|
|
4499
5172
|
payload: {
|
|
4500
5173
|
method: 'firmwareUpdateV4',
|
|
4501
5174
|
},
|
|
4502
5175
|
});
|
|
4503
|
-
const
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
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();
|
|
4520
|
-
|
|
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();
|
|
5176
|
+
const waitForProtocolV2ReconnectAndFeatures = jest
|
|
5177
|
+
.fn()
|
|
5178
|
+
.mockRejectedValue(new Error('stop after capturing timeout'));
|
|
5179
|
+
(method as any).waitForProtocolV2ReconnectAndFeatures = waitForProtocolV2ReconnectAndFeatures;
|
|
4527
5180
|
|
|
4528
|
-
expect(
|
|
4529
|
-
|
|
4530
|
-
'DeviceFirmwareUpdateStatusGet',
|
|
4531
|
-
'DeviceFirmwareUpdateStatus',
|
|
4532
|
-
expect.anything(),
|
|
4533
|
-
expect.anything()
|
|
5181
|
+
await expect((method as any).waitForProtocolV2FinalFeatures()).rejects.toThrow(
|
|
5182
|
+
'stop after capturing timeout'
|
|
4534
5183
|
);
|
|
4535
|
-
expect(
|
|
5184
|
+
expect(waitForProtocolV2ReconnectAndFeatures).toHaveBeenCalledWith(3 * 60 * 1000);
|
|
4536
5185
|
});
|
|
4537
5186
|
|
|
4538
5187
|
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
@@ -4569,7 +5218,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4569
5218
|
).toBe(true);
|
|
4570
5219
|
|
|
4571
5220
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4572
|
-
expect(
|
|
5221
|
+
expect(
|
|
4573
5222
|
(method as any).assertProtocolV2TargetStatus(
|
|
4574
5223
|
[
|
|
4575
5224
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4577,8 +5226,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4577
5226
|
],
|
|
4578
5227
|
new Set([4, 10])
|
|
4579
5228
|
)
|
|
4580
|
-
).
|
|
4581
|
-
expect(method.postProgressMessage).
|
|
5229
|
+
).toBe(false);
|
|
5230
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4582
5231
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4583
5232
|
|
|
4584
5233
|
expect(
|
|
@@ -4667,7 +5316,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4667
5316
|
path: true,
|
|
4668
5317
|
},
|
|
4669
5318
|
},
|
|
4670
|
-
{ timeoutMs:
|
|
5319
|
+
{ timeoutMs: 15000 }
|
|
4671
5320
|
);
|
|
4672
5321
|
});
|
|
4673
5322
|
|
|
@@ -4682,14 +5331,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4682
5331
|
const writtenPaths: string[] = [];
|
|
4683
5332
|
method.postTipMessage = jest.fn();
|
|
4684
5333
|
method.postProgressMessage = jest.fn();
|
|
4685
|
-
(method as any).
|
|
5334
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4686
5335
|
writtenPaths.push(params.filePath);
|
|
4687
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
5336
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4688
5337
|
});
|
|
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({});
|
|
4693
5338
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4694
5339
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4695
5340
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4729,12 +5374,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4729
5374
|
'vol0:/coprocessor.bin',
|
|
4730
5375
|
1
|
|
4731
5376
|
);
|
|
4732
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
4733
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4734
|
-
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4735
|
-
});
|
|
4736
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5377
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
5378
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4737
5379
|
targets: [
|
|
5380
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4738
5381
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4739
5382
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4740
5383
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4744,7 +5387,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4744
5387
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4745
5388
|
});
|
|
4746
5389
|
|
|
4747
|
-
test('announces
|
|
5390
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4748
5391
|
const method = new FirmwareUpdateV4({
|
|
4749
5392
|
id: 1,
|
|
4750
5393
|
payload: {
|
|
@@ -4754,14 +5397,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4754
5397
|
|
|
4755
5398
|
method.postTipMessage = jest.fn();
|
|
4756
5399
|
method.postProgressMessage = jest.fn();
|
|
4757
|
-
(method as any).
|
|
5400
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4758
5401
|
.fn()
|
|
4759
5402
|
.mockImplementation(params =>
|
|
4760
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5403
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4761
5404
|
);
|
|
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({});
|
|
4765
5405
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4766
5406
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4767
5407
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4781,22 +5421,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4781
5421
|
{
|
|
4782
5422
|
fileName: 'application_p1.bin',
|
|
4783
5423
|
binary: new Uint8Array([3]).buffer,
|
|
4784
|
-
targetId:
|
|
5424
|
+
targetId: 3,
|
|
4785
5425
|
},
|
|
4786
5426
|
],
|
|
4787
5427
|
});
|
|
4788
5428
|
|
|
4789
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
5429
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4790
5430
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4791
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4792
|
-
expect(method.
|
|
4793
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5431
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5432
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4794
5433
|
1,
|
|
4795
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
5434
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4796
5435
|
);
|
|
4797
|
-
expect((method as any).
|
|
5436
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4798
5437
|
2,
|
|
4799
|
-
expect.objectContaining({ processedSize:
|
|
5438
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4800
5439
|
);
|
|
4801
5440
|
});
|
|
4802
5441
|
|
|
@@ -4810,8 +5449,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4810
5449
|
|
|
4811
5450
|
method.postTipMessage = jest.fn();
|
|
4812
5451
|
method.postProgressMessage = jest.fn();
|
|
4813
|
-
(method as any).
|
|
4814
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5452
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4815
5453
|
(method as any).device = stubDevice({
|
|
4816
5454
|
getCommands: () => ({
|
|
4817
5455
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4857,14 +5495,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4857
5495
|
|
|
4858
5496
|
method.postTipMessage = jest.fn();
|
|
4859
5497
|
method.postProgressMessage = jest.fn();
|
|
4860
|
-
(method as any).
|
|
5498
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4861
5499
|
.fn()
|
|
4862
5500
|
.mockImplementation(params =>
|
|
4863
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5501
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4864
5502
|
);
|
|
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({});
|
|
4868
5503
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4869
5504
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4870
5505
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5079,328 +5714,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5079
5714
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5080
5715
|
});
|
|
5081
5716
|
|
|
5082
|
-
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
5083
|
-
const method = new FirmwareUpdateV4({
|
|
5084
|
-
id: 1,
|
|
5085
|
-
payload: {
|
|
5086
|
-
method: 'firmwareUpdateV4',
|
|
5087
|
-
platform: 'web',
|
|
5088
|
-
targetsToUpdate: ['resource'],
|
|
5089
|
-
},
|
|
5090
|
-
});
|
|
5091
|
-
method.init();
|
|
5092
|
-
|
|
5093
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5094
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5095
|
-
.mockReturnValue({
|
|
5096
|
-
required: false,
|
|
5097
|
-
version: [1, 0, 0],
|
|
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),
|
|
5107
|
-
},
|
|
5108
|
-
],
|
|
5109
|
-
fingerprint: '',
|
|
5110
|
-
changelog: {
|
|
5111
|
-
'zh-CN': '',
|
|
5112
|
-
'en-US': '',
|
|
5113
|
-
},
|
|
5114
|
-
});
|
|
5115
|
-
|
|
5116
|
-
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
5117
|
-
deviceType: 'pro2',
|
|
5118
|
-
firmwareVersion: '1.0.0',
|
|
5119
|
-
capabilities: [],
|
|
5120
|
-
});
|
|
5121
|
-
|
|
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
5717
|
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5405
5718
|
const method = new FirmwareUpdateV4({
|
|
5406
5719
|
id: 1,
|
|
@@ -5488,24 +5801,55 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5488
5801
|
originalDescriptor: { protocolType: 'V2' },
|
|
5489
5802
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5490
5803
|
});
|
|
5491
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5492
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5493
|
-
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5494
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5495
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5496
|
-
bootloaderVersion: '1.0.0',
|
|
5497
|
-
bleVersion: '0.0.0',
|
|
5498
|
-
firmwareVersion: '1.0.0',
|
|
5499
|
-
});
|
|
5804
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5805
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5806
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5807
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5808
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5809
|
+
bootloaderVersion: '1.0.0',
|
|
5810
|
+
bleVersion: '0.0.0',
|
|
5811
|
+
firmwareVersion: '1.0.0',
|
|
5812
|
+
});
|
|
5813
|
+
method.postTipMessage = jest.fn();
|
|
5814
|
+
|
|
5815
|
+
await method.run();
|
|
5816
|
+
|
|
5817
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5818
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5819
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5820
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5821
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5822
|
+
});
|
|
5823
|
+
});
|
|
5824
|
+
|
|
5825
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5826
|
+
const method = new FirmwareUpdateV4({
|
|
5827
|
+
id: 1,
|
|
5828
|
+
payload: {
|
|
5829
|
+
method: 'firmwareUpdateV4',
|
|
5830
|
+
platform: 'web',
|
|
5831
|
+
},
|
|
5832
|
+
});
|
|
5833
|
+
method.init();
|
|
5834
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5835
|
+
(method as any).device = stubDevice({
|
|
5836
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5837
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5838
|
+
});
|
|
5839
|
+
forceReloadDataSpy.mockRejectedValue(
|
|
5840
|
+
ERRORS.TypedError(HardwareErrorCode.NetworkError, 'latest config unavailable')
|
|
5841
|
+
);
|
|
5842
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5843
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5500
5844
|
method.postTipMessage = jest.fn();
|
|
5501
5845
|
|
|
5502
|
-
await method.run()
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5506
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5507
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5846
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
5847
|
+
errorCode: HardwareErrorCode.NetworkError,
|
|
5848
|
+
message: 'latest config unavailable',
|
|
5508
5849
|
});
|
|
5850
|
+
|
|
5851
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5852
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5509
5853
|
});
|
|
5510
5854
|
|
|
5511
5855
|
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
@@ -5544,14 +5888,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5544
5888
|
});
|
|
5545
5889
|
});
|
|
5546
5890
|
|
|
5547
|
-
test('treats manual resource
|
|
5891
|
+
test('treats manual resource files as explicit payload without remote firmware auto-fill', async () => {
|
|
5548
5892
|
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
5549
5893
|
const method = new FirmwareUpdateV4({
|
|
5550
5894
|
id: 1,
|
|
5551
5895
|
payload: {
|
|
5552
5896
|
method: 'firmwareUpdateV4',
|
|
5553
5897
|
platform: 'web',
|
|
5554
|
-
|
|
5898
|
+
resourceFiles: [
|
|
5555
5899
|
{
|
|
5556
5900
|
binary: resourceBundle,
|
|
5557
5901
|
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
@@ -5599,7 +5943,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5599
5943
|
payload: {
|
|
5600
5944
|
method: 'firmwareUpdateV4',
|
|
5601
5945
|
platform: 'web',
|
|
5602
|
-
|
|
5946
|
+
resourceFiles: [
|
|
5603
5947
|
{
|
|
5604
5948
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
5605
5949
|
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
@@ -5618,7 +5962,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5618
5962
|
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5619
5963
|
method.postTipMessage = jest.fn();
|
|
5620
5964
|
|
|
5621
|
-
await expect(method.run()).rejects.toThrow('
|
|
5965
|
+
await expect(method.run()).rejects.toThrow('resourceFiles[0].devicePath');
|
|
5622
5966
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5623
5967
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5624
5968
|
});
|
|
@@ -5633,10 +5977,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5633
5977
|
|
|
5634
5978
|
method.postTipMessage = jest.fn();
|
|
5635
5979
|
method.postProgressMessage = jest.fn();
|
|
5636
|
-
(method as any).
|
|
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);
|
|
5980
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5640
5981
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5641
5982
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5642
5983
|
|
|
@@ -5652,7 +5993,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5652
5993
|
fwBinaryMap: [],
|
|
5653
5994
|
});
|
|
5654
5995
|
|
|
5655
|
-
expect((method as any).
|
|
5996
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5656
5997
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5657
5998
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5658
5999
|
});
|
|
@@ -5680,22 +6021,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5680
6021
|
|
|
5681
6022
|
(method as any).device = stubDevice({
|
|
5682
6023
|
getCommands: () => ({ typedCall }),
|
|
6024
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6025
|
+
toMessageObject: () => ({ connectId: 'firmware-device' }),
|
|
5683
6026
|
});
|
|
5684
|
-
method.
|
|
6027
|
+
method.postMessage = jest.fn();
|
|
5685
6028
|
|
|
5686
|
-
|
|
5687
|
-
|
|
6029
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6030
|
+
payload: new Uint8Array(4097).buffer,
|
|
6031
|
+
filePath: 'vol1:firmware.bin',
|
|
6032
|
+
processedSize: 0,
|
|
6033
|
+
totalSize: 4097,
|
|
5688
6034
|
});
|
|
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
|
-
}
|
|
5699
6035
|
|
|
5700
6036
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5701
6037
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5703,6 +6039,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5703
6039
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
5704
6040
|
expect(writePayloads.every(payload => payload.append === false)).toBe(true);
|
|
5705
6041
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
6042
|
+
expect(method.postMessage).toHaveBeenLastCalledWith({
|
|
6043
|
+
event: 'UI_EVENT',
|
|
6044
|
+
type: UI_REQUEST.FIRMWARE_PROGRESS,
|
|
6045
|
+
payload: expect.objectContaining({
|
|
6046
|
+
progress: 99,
|
|
6047
|
+
progressType: 'transferData',
|
|
6048
|
+
transferredBytes: 4097,
|
|
6049
|
+
totalBytes: 4097,
|
|
6050
|
+
elapsedMs: expect.any(Number),
|
|
6051
|
+
}),
|
|
6052
|
+
});
|
|
5706
6053
|
});
|
|
5707
6054
|
|
|
5708
6055
|
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
@@ -5752,16 +6099,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5752
6099
|
method.postProgressMessage = jest.fn();
|
|
5753
6100
|
|
|
5754
6101
|
try {
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
});
|
|
5758
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5759
|
-
source,
|
|
6102
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6103
|
+
payload: new Uint8Array(8001).buffer,
|
|
5760
6104
|
filePath: 'vol0:/firmware.bin',
|
|
5761
6105
|
processedSize: 0,
|
|
5762
6106
|
totalSize: 8001,
|
|
5763
6107
|
});
|
|
5764
|
-
await source?.close();
|
|
5765
6108
|
} finally {
|
|
5766
6109
|
setTimeoutSpy.mockRestore();
|
|
5767
6110
|
}
|
|
@@ -5796,21 +6139,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5796
6139
|
});
|
|
5797
6140
|
method.postProgressMessage = jest.fn();
|
|
5798
6141
|
method.postTipMessage = jest.fn();
|
|
5799
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5800
6142
|
|
|
5801
|
-
const source = await openFirmwareByteSource({
|
|
5802
|
-
binary: new Uint8Array(4097).buffer,
|
|
5803
|
-
});
|
|
5804
6143
|
await expect(
|
|
5805
|
-
(method as any).
|
|
5806
|
-
|
|
6144
|
+
(method as any).protocolV2WriteWholeFile({
|
|
6145
|
+
payload: new Uint8Array(4097).buffer,
|
|
5807
6146
|
filePath: 'vol0:/firmware.bin',
|
|
5808
6147
|
processedSize: 0,
|
|
5809
6148
|
totalSize: 4097,
|
|
5810
6149
|
})
|
|
5811
6150
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5812
|
-
|
|
5813
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6151
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5814
6152
|
});
|
|
5815
6153
|
|
|
5816
6154
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5843,24 +6181,27 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5843
6181
|
});
|
|
5844
6182
|
method.postProgressMessage = jest.fn();
|
|
5845
6183
|
|
|
5846
|
-
|
|
5847
|
-
|
|
6184
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6185
|
+
payload: new Uint8Array(1801).buffer,
|
|
6186
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
6187
|
+
processedSize: 0,
|
|
6188
|
+
totalSize: 1801,
|
|
5848
6189
|
});
|
|
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
|
-
}
|
|
5859
6190
|
|
|
5860
6191
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5861
6192
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
5862
6193
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
5863
6194
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
6195
|
+
expect(typedCall.mock.calls.map(call => call[3])).toEqual([
|
|
6196
|
+
expect.objectContaining({
|
|
6197
|
+
writeWithResponse: false,
|
|
6198
|
+
onWriteCompleted: expect.any(Function),
|
|
6199
|
+
}),
|
|
6200
|
+
expect.objectContaining({
|
|
6201
|
+
writeWithResponse: false,
|
|
6202
|
+
onWriteCompleted: expect.any(Function),
|
|
6203
|
+
}),
|
|
6204
|
+
]);
|
|
5864
6205
|
});
|
|
5865
6206
|
|
|
5866
6207
|
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
@@ -5972,9 +6313,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5972
6313
|
const typedCall = jest
|
|
5973
6314
|
.fn()
|
|
5974
6315
|
.mockRejectedValue(
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
6316
|
+
new DOMException(
|
|
6317
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6318
|
+
'NotFoundError'
|
|
5978
6319
|
)
|
|
5979
6320
|
);
|
|
5980
6321
|
|
|
@@ -6028,7 +6369,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6028
6369
|
expect(typedCall).not.toHaveBeenCalled();
|
|
6029
6370
|
});
|
|
6030
6371
|
|
|
6031
|
-
test.each([0, 2])(
|
|
6372
|
+
test.each([0, 1, 2])(
|
|
6032
6373
|
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
6033
6374
|
async targetId => {
|
|
6034
6375
|
const typedCall = jest.fn();
|
|
@@ -6061,8 +6402,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6061
6402
|
targets: [
|
|
6062
6403
|
{
|
|
6063
6404
|
target_id: undefined,
|
|
6064
|
-
targetId:
|
|
6065
|
-
path: 'vol0:
|
|
6405
|
+
targetId: 3,
|
|
6406
|
+
path: 'vol0:bootloader.okpkg',
|
|
6066
6407
|
},
|
|
6067
6408
|
],
|
|
6068
6409
|
} as any,
|
|
@@ -6074,7 +6415,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6074
6415
|
|
|
6075
6416
|
await method.run();
|
|
6076
6417
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
6077
|
-
targets: [{ target_id:
|
|
6418
|
+
targets: [{ target_id: 3, path: 'vol0:bootloader.okpkg' }],
|
|
6078
6419
|
});
|
|
6079
6420
|
});
|
|
6080
6421
|
|
|
@@ -6139,20 +6480,63 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6139
6480
|
});
|
|
6140
6481
|
|
|
6141
6482
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6483
|
+
const createResourceFilesystemTypedCall = (
|
|
6484
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6485
|
+
missingTypes: string[] = []
|
|
6486
|
+
) => {
|
|
6487
|
+
const missing = new Set(missingTypes);
|
|
6488
|
+
const resourceByPath = new Map(
|
|
6489
|
+
stable.map(resource => [
|
|
6490
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
6491
|
+
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6492
|
+
],
|
|
6493
|
+
resource,
|
|
6494
|
+
])
|
|
6495
|
+
);
|
|
6496
|
+
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6497
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
6498
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6499
|
+
}
|
|
6500
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6501
|
+
const resource = resourceByPath.get(payload.path);
|
|
6502
|
+
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6503
|
+
return {
|
|
6504
|
+
message: {
|
|
6505
|
+
exist: exists,
|
|
6506
|
+
directory: false,
|
|
6507
|
+
size: exists ? resource?.size : 0,
|
|
6508
|
+
},
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6511
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6512
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
6513
|
+
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6514
|
+
const header = new Uint8Array(0x52a0);
|
|
6515
|
+
const view = new DataView(header.buffer);
|
|
6516
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6517
|
+
header[index] = char.charCodeAt(0);
|
|
6518
|
+
});
|
|
6519
|
+
'RESC'.split('').forEach((char, index) => {
|
|
6520
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6521
|
+
});
|
|
6522
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6523
|
+
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6524
|
+
header[0x240 + index] = Number.parseInt(
|
|
6525
|
+
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6526
|
+
16
|
|
6527
|
+
);
|
|
6528
|
+
}
|
|
6529
|
+
const offset = Number(payload.file.offset);
|
|
6530
|
+
const chunkLength = Number(payload.chunk_len);
|
|
6531
|
+
return {
|
|
6532
|
+
message: {
|
|
6533
|
+
data: header.slice(offset, offset + chunkLength),
|
|
6534
|
+
},
|
|
6535
|
+
};
|
|
6536
|
+
}
|
|
6537
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6150
6538
|
});
|
|
6151
|
-
|
|
6152
|
-
method.init();
|
|
6153
|
-
|
|
6154
|
-
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6155
|
-
});
|
|
6539
|
+
};
|
|
6156
6540
|
|
|
6157
6541
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6158
6542
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6163,15 +6547,15 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6163
6547
|
).not.toThrow();
|
|
6164
6548
|
});
|
|
6165
6549
|
|
|
6166
|
-
test('
|
|
6167
|
-
expect(() =>
|
|
6168
|
-
|
|
6169
|
-
);
|
|
6170
|
-
expect(() =>
|
|
6171
|
-
|
|
6172
|
-
);
|
|
6550
|
+
test('requires the same descriptor path when either physical serial is unavailable', () => {
|
|
6551
|
+
expect(() =>
|
|
6552
|
+
assertProtocolV2ReconnectIdentity('expected-serial', undefined, 'usb-path', 'usb-path')
|
|
6553
|
+
).not.toThrow();
|
|
6554
|
+
expect(() =>
|
|
6555
|
+
assertProtocolV2ReconnectIdentity(undefined, 'actual-serial', 'usb-path', 'other-path')
|
|
6556
|
+
).toThrow('identity unavailable');
|
|
6173
6557
|
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
6174
|
-
|
|
6558
|
+
'identity unavailable'
|
|
6175
6559
|
);
|
|
6176
6560
|
});
|
|
6177
6561
|
|
|
@@ -6190,6 +6574,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6190
6574
|
},
|
|
6191
6575
|
});
|
|
6192
6576
|
(method as any).device = stubDevice({
|
|
6577
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6193
6578
|
features: { deviceId: 'wallet-derived-id' },
|
|
6194
6579
|
getCommands: () => ({ typedCall }),
|
|
6195
6580
|
});
|
|
@@ -6205,7 +6590,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6205
6590
|
);
|
|
6206
6591
|
});
|
|
6207
6592
|
|
|
6208
|
-
test('
|
|
6593
|
+
test('allows firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6209
6594
|
const method = new FirmwareUpdateV4({
|
|
6210
6595
|
id: 1,
|
|
6211
6596
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6215,42 +6600,204 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6215
6600
|
message: { protocol_version: 1, hw: {} },
|
|
6216
6601
|
});
|
|
6217
6602
|
(method as any).device = stubDevice({
|
|
6603
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
6218
6604
|
getCommands: () => ({ typedCall }),
|
|
6219
6605
|
});
|
|
6220
6606
|
|
|
6221
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6222
|
-
|
|
6223
|
-
|
|
6607
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).resolves.toBeUndefined();
|
|
6608
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
6609
|
+
expect((method as any).protocolV2ExpectedPath).toBeDefined();
|
|
6224
6610
|
});
|
|
6225
6611
|
|
|
6226
|
-
test('uses
|
|
6612
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6227
6613
|
const method = new FirmwareUpdateV4({
|
|
6228
6614
|
id: 1,
|
|
6229
6615
|
payload: {
|
|
6230
6616
|
method: 'firmwareUpdateV4',
|
|
6231
|
-
platform: '
|
|
6232
|
-
|
|
6617
|
+
platform: 'web',
|
|
6618
|
+
targetsToUpdate: ['resource'],
|
|
6233
6619
|
},
|
|
6234
6620
|
});
|
|
6235
6621
|
method.init();
|
|
6236
|
-
const
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6622
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6623
|
+
(type, index) => ({
|
|
6624
|
+
type,
|
|
6625
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6626
|
+
size: 0x52a0 + index + 1,
|
|
6627
|
+
fileHash: 'a'.repeat(64),
|
|
6628
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6629
|
+
})
|
|
6630
|
+
);
|
|
6631
|
+
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
6632
|
+
(method as any).device = stubDevice({
|
|
6633
|
+
getCommands: () => ({ typedCall }),
|
|
6634
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6635
|
+
});
|
|
6636
|
+
const resourcesSpy = jest
|
|
6637
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6638
|
+
.mockReturnValue(stable as any);
|
|
6639
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6640
|
+
|
|
6641
|
+
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
6642
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6643
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
6644
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
6645
|
+
resourcesSpy.mockRestore();
|
|
6646
|
+
});
|
|
6647
|
+
|
|
6648
|
+
test('downloads only changed resources from loader inventory', async () => {
|
|
6649
|
+
const method = new FirmwareUpdateV4({
|
|
6650
|
+
id: 1,
|
|
6651
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6652
|
+
});
|
|
6653
|
+
method.init();
|
|
6654
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6655
|
+
(type, index) => ({
|
|
6656
|
+
type,
|
|
6657
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6658
|
+
size: 0x52a0 + index + 1,
|
|
6659
|
+
fileHash: 'a'.repeat(64),
|
|
6660
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6661
|
+
})
|
|
6662
|
+
);
|
|
6663
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6664
|
+
(method as any).device = stubDevice({
|
|
6665
|
+
getCommands: () => ({ typedCall }),
|
|
6666
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6667
|
+
});
|
|
6668
|
+
const resourcesSpy = jest
|
|
6669
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6670
|
+
.mockReturnValue(stable as any);
|
|
6671
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6672
|
+
Promise.resolve({
|
|
6673
|
+
name: `${resource.type}.okpkg`,
|
|
6674
|
+
binary: new ArrayBuffer(resource.size),
|
|
6675
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6676
|
+
})
|
|
6677
|
+
);
|
|
6678
|
+
|
|
6679
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6680
|
+
|
|
6681
|
+
expect(bundles).toHaveLength(1);
|
|
6682
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6683
|
+
resourcesSpy.mockRestore();
|
|
6684
|
+
});
|
|
6685
|
+
|
|
6686
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
6687
|
+
const method = new FirmwareUpdateV4({
|
|
6688
|
+
id: 1,
|
|
6689
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6246
6690
|
});
|
|
6691
|
+
method.init();
|
|
6692
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6693
|
+
(type, index) => ({
|
|
6694
|
+
type,
|
|
6695
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6696
|
+
size: 0x52a0 + index + 1,
|
|
6697
|
+
fileHash: 'a'.repeat(64),
|
|
6698
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6699
|
+
})
|
|
6700
|
+
);
|
|
6701
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6247
6702
|
(method as any).device = stubDevice({
|
|
6248
6703
|
getCommands: () => ({ typedCall }),
|
|
6704
|
+
getCurrentDeviceType: () => 'pro2',
|
|
6705
|
+
});
|
|
6706
|
+
const resourcesSpy = jest
|
|
6707
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6708
|
+
.mockReturnValue(stable as any);
|
|
6709
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6710
|
+
Promise.resolve({
|
|
6711
|
+
name: `${resource.type}.okpkg`,
|
|
6712
|
+
binary: new ArrayBuffer(resource.size),
|
|
6713
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6714
|
+
})
|
|
6715
|
+
);
|
|
6716
|
+
|
|
6717
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6718
|
+
|
|
6719
|
+
expect(bundles).toHaveLength(1);
|
|
6720
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6721
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
6722
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6723
|
+
resourcesSpy.mockRestore();
|
|
6724
|
+
});
|
|
6725
|
+
|
|
6726
|
+
test('does not resolve boot resources when no resource target is selected', async () => {
|
|
6727
|
+
const method = new FirmwareUpdateV4({
|
|
6728
|
+
id: 1,
|
|
6729
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
6730
|
+
});
|
|
6731
|
+
method.init();
|
|
6732
|
+
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6733
|
+
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
6734
|
+
|
|
6735
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
6736
|
+
|
|
6737
|
+
expect(configSpy).not.toHaveBeenCalled();
|
|
6738
|
+
expect(downloadSpy).not.toHaveBeenCalled();
|
|
6739
|
+
});
|
|
6740
|
+
|
|
6741
|
+
test('includes startup resources in the complete resource target', async () => {
|
|
6742
|
+
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
6743
|
+
const binary = bytes.buffer as ArrayBuffer;
|
|
6744
|
+
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
6745
|
+
const resource = {
|
|
6746
|
+
required: false as const,
|
|
6747
|
+
target: 'RES' as const,
|
|
6748
|
+
files: [
|
|
6749
|
+
{
|
|
6750
|
+
name: 'bootloader_crest.bin',
|
|
6751
|
+
url: 'https://example.com/bootloader_crest.bin',
|
|
6752
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6753
|
+
size: bytes.byteLength,
|
|
6754
|
+
fileHash,
|
|
6755
|
+
},
|
|
6756
|
+
],
|
|
6757
|
+
};
|
|
6758
|
+
const method = new FirmwareUpdateV4({
|
|
6759
|
+
id: 1,
|
|
6760
|
+
payload: {
|
|
6761
|
+
method: 'firmwareUpdateV4',
|
|
6762
|
+
platform: 'web',
|
|
6763
|
+
targetsToUpdate: ['resource'],
|
|
6764
|
+
},
|
|
6765
|
+
});
|
|
6766
|
+
method.init();
|
|
6767
|
+
(method as any).device = stubDevice({ getCurrentDeviceType: () => 'pro2' });
|
|
6768
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
6769
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
6770
|
+
|
|
6771
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual([
|
|
6772
|
+
{
|
|
6773
|
+
name: 'bootloader_crest.bin',
|
|
6774
|
+
binary,
|
|
6775
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6776
|
+
},
|
|
6777
|
+
]);
|
|
6778
|
+
});
|
|
6779
|
+
|
|
6780
|
+
test('rejects a manually supplied resource file when its manifest hash does not match', () => {
|
|
6781
|
+
const method = new FirmwareUpdateV4({
|
|
6782
|
+
id: 1,
|
|
6783
|
+
payload: {
|
|
6784
|
+
method: 'firmwareUpdateV4',
|
|
6785
|
+
platform: 'web',
|
|
6786
|
+
resourceFiles: [
|
|
6787
|
+
{
|
|
6788
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
6789
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
6790
|
+
size: 3,
|
|
6791
|
+
fileHash: '00'.repeat(32),
|
|
6792
|
+
},
|
|
6793
|
+
],
|
|
6794
|
+
},
|
|
6249
6795
|
});
|
|
6796
|
+
method.init();
|
|
6250
6797
|
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6798
|
+
expect(() => (method as any).prepareExplicitProtocolV2ResourceFiles()).toThrow(
|
|
6799
|
+
'SHA-256 mismatch'
|
|
6800
|
+
);
|
|
6254
6801
|
});
|
|
6255
6802
|
});
|
|
6256
6803
|
|
|
@@ -6373,330 +6920,249 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6373
6920
|
reason: 'device-management',
|
|
6374
6921
|
completion: 'operation-completed',
|
|
6375
6922
|
deviceOnly: true,
|
|
6376
|
-
operation: 'change-label',
|
|
6377
|
-
});
|
|
6378
|
-
});
|
|
6379
|
-
|
|
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
|
-
]);
|
|
6923
|
+
operation: 'change-label',
|
|
6924
|
+
});
|
|
6498
6925
|
});
|
|
6499
6926
|
|
|
6500
|
-
test(
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
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
|
-
};
|
|
6927
|
+
test.each([
|
|
6928
|
+
['deviceInfoGet', DeviceInfoGet],
|
|
6929
|
+
['deviceStatusGet', DeviceStatusGet],
|
|
6930
|
+
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6931
|
+
const method = new MethodClass({
|
|
6932
|
+
id: 1,
|
|
6933
|
+
payload: { method: _name },
|
|
6934
|
+
} as any);
|
|
6935
|
+
method.init();
|
|
6531
6936
|
|
|
6532
|
-
|
|
6533
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6534
|
-
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6535
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6536
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6937
|
+
expect(method.unlockPolicy).toBe('none');
|
|
6537
6938
|
});
|
|
6538
6939
|
|
|
6539
|
-
test('
|
|
6940
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6540
6941
|
const calls: string[] = [];
|
|
6942
|
+
const features = { unlocked: true };
|
|
6541
6943
|
const method = {
|
|
6542
|
-
name: '
|
|
6543
|
-
|
|
6944
|
+
name: 'confluxSignMessageCIP23',
|
|
6945
|
+
unlockPolicy: 'none',
|
|
6544
6946
|
useDevicePassphraseState: true,
|
|
6545
|
-
|
|
6947
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6546
6948
|
run: jest.fn(() => {
|
|
6547
6949
|
calls.push('run');
|
|
6548
|
-
return Promise.resolve({
|
|
6950
|
+
return Promise.resolve({ address: '0x1' });
|
|
6549
6951
|
}),
|
|
6550
6952
|
};
|
|
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
|
-
};
|
|
6573
6953
|
const device = {
|
|
6574
6954
|
features,
|
|
6575
|
-
|
|
6576
|
-
|
|
6955
|
+
commands: {
|
|
6956
|
+
typedCall: jest.fn(() => {
|
|
6957
|
+
calls.push('status');
|
|
6958
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6959
|
+
}),
|
|
6960
|
+
},
|
|
6577
6961
|
isProtocolV2: () => true,
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
features.
|
|
6582
|
-
return
|
|
6962
|
+
isBootloader: () => false,
|
|
6963
|
+
isRomloader: () => false,
|
|
6964
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6965
|
+
features.unlocked = status.unlocked === true;
|
|
6966
|
+
return features;
|
|
6583
6967
|
}),
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6968
|
+
unlockDevice: jest.fn(),
|
|
6969
|
+
};
|
|
6970
|
+
const uiCoordinator = {
|
|
6971
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6972
|
+
enterUnlockInteraction: jest.fn(),
|
|
6589
6973
|
};
|
|
6590
6974
|
|
|
6591
|
-
await expect(
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
]);
|
|
6975
|
+
await expect(
|
|
6976
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6977
|
+
uiCoordinator: uiCoordinator as any,
|
|
6978
|
+
})
|
|
6979
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6980
|
+
|
|
6981
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6982
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6983
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6984
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6602
6985
|
});
|
|
6603
6986
|
|
|
6604
|
-
test('
|
|
6987
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6988
|
+
const calls: string[] = [];
|
|
6989
|
+
const identityError = new Error('Unexpected device');
|
|
6605
6990
|
const method = {
|
|
6606
|
-
name: '
|
|
6607
|
-
|
|
6991
|
+
name: 'evmSignMessage',
|
|
6992
|
+
unlockPolicy: 'none',
|
|
6608
6993
|
useDevicePassphraseState: true,
|
|
6609
|
-
|
|
6610
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6994
|
+
run: jest.fn(),
|
|
6611
6995
|
};
|
|
6996
|
+
const features = { unlocked: false };
|
|
6612
6997
|
const device = {
|
|
6613
|
-
features
|
|
6614
|
-
|
|
6998
|
+
features,
|
|
6999
|
+
commands: {
|
|
7000
|
+
typedCall: jest.fn(() => {
|
|
7001
|
+
calls.push('status');
|
|
7002
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
7003
|
+
}),
|
|
7004
|
+
},
|
|
6615
7005
|
isProtocolV2: () => true,
|
|
6616
|
-
|
|
7006
|
+
isBootloader: () => false,
|
|
7007
|
+
isRomloader: () => false,
|
|
7008
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7009
|
+
unlockDevice: jest.fn(),
|
|
6617
7010
|
};
|
|
6618
7011
|
|
|
6619
|
-
await expect(
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
7012
|
+
await expect(
|
|
7013
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
7014
|
+
afterStatusBeforeUnlock: () => {
|
|
7015
|
+
calls.push('identity');
|
|
7016
|
+
throw identityError;
|
|
7017
|
+
},
|
|
7018
|
+
})
|
|
7019
|
+
).rejects.toBe(identityError);
|
|
7020
|
+
|
|
7021
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
7022
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7023
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6624
7024
|
});
|
|
6625
7025
|
|
|
6626
|
-
test('
|
|
7026
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6627
7027
|
const calls: string[] = [];
|
|
7028
|
+
const features = { unlocked: true };
|
|
6628
7029
|
const method = {
|
|
6629
|
-
name: '
|
|
7030
|
+
name: 'evmSignMessage',
|
|
6630
7031
|
unlockPolicy: 'unlock-before-run',
|
|
6631
|
-
protocolV2UiInteraction: { reason: '
|
|
7032
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6632
7033
|
run: jest.fn(() => {
|
|
6633
7034
|
calls.push('run');
|
|
6634
|
-
return Promise.resolve({
|
|
7035
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6635
7036
|
}),
|
|
6636
7037
|
};
|
|
6637
|
-
const features = { unlocked: true };
|
|
6638
7038
|
const device = {
|
|
6639
7039
|
features,
|
|
6640
7040
|
commands: {
|
|
6641
7041
|
typedCall: jest.fn(() => {
|
|
6642
|
-
calls.push('
|
|
7042
|
+
calls.push('status');
|
|
6643
7043
|
return Promise.resolve({ message: { unlocked: false } });
|
|
6644
7044
|
}),
|
|
6645
7045
|
},
|
|
6646
7046
|
isProtocolV2: () => true,
|
|
7047
|
+
isBootloader: () => false,
|
|
7048
|
+
isRomloader: () => false,
|
|
6647
7049
|
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6648
|
-
features.unlocked = status.unlocked
|
|
7050
|
+
features.unlocked = status.unlocked === true;
|
|
6649
7051
|
return features;
|
|
6650
7052
|
}),
|
|
6651
7053
|
unlockDevice: jest.fn(() => {
|
|
6652
7054
|
calls.push('unlock');
|
|
6653
7055
|
features.unlocked = true;
|
|
6654
|
-
return Promise.resolve();
|
|
7056
|
+
return Promise.resolve(features);
|
|
6655
7057
|
}),
|
|
6656
7058
|
};
|
|
6657
7059
|
const uiCoordinator = {
|
|
6658
7060
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6659
|
-
enterUnlockInteraction: jest.fn(() =>
|
|
6660
|
-
|
|
7061
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
7062
|
+
calls.push('unlock-prompt');
|
|
7063
|
+
return undefined;
|
|
7064
|
+
}),
|
|
6661
7065
|
};
|
|
6662
7066
|
|
|
6663
7067
|
await expect(
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
7068
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
7069
|
+
uiCoordinator: uiCoordinator as any,
|
|
7070
|
+
prepare: () => {
|
|
7071
|
+
calls.push('wallet-session');
|
|
7072
|
+
return Promise.resolve();
|
|
7073
|
+
},
|
|
7074
|
+
})
|
|
7075
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
7076
|
+
|
|
7077
|
+
expect(calls).toEqual([
|
|
7078
|
+
'status',
|
|
7079
|
+
'unlock-prompt',
|
|
7080
|
+
'unlock',
|
|
7081
|
+
'wallet-session',
|
|
7082
|
+
'method-prompt',
|
|
7083
|
+
'run',
|
|
7084
|
+
]);
|
|
7085
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6668
7086
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6669
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6670
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6671
7087
|
});
|
|
6672
7088
|
|
|
6673
|
-
test('
|
|
6674
|
-
const features = { unlocked: false };
|
|
7089
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6675
7090
|
const method = {
|
|
6676
|
-
name: '
|
|
7091
|
+
name: 'evmGetAddress',
|
|
6677
7092
|
unlockPolicy: 'unlock-before-run',
|
|
6678
|
-
run: jest.fn()
|
|
7093
|
+
run: jest.fn(),
|
|
6679
7094
|
};
|
|
6680
7095
|
const device = {
|
|
6681
|
-
features,
|
|
6682
|
-
commands: {
|
|
6683
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6684
|
-
},
|
|
7096
|
+
features: { unlocked: undefined },
|
|
7097
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6685
7098
|
isProtocolV2: () => true,
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
}),
|
|
7099
|
+
isBootloader: () => false,
|
|
7100
|
+
isRomloader: () => false,
|
|
7101
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6690
7102
|
unlockDevice: jest.fn(),
|
|
6691
7103
|
};
|
|
6692
7104
|
|
|
6693
|
-
await expect(
|
|
6694
|
-
|
|
7105
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
7106
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6695
7107
|
});
|
|
6696
7108
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7109
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
7110
|
+
});
|
|
7111
|
+
|
|
7112
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
7113
|
+
const error = deviceLockedError();
|
|
7114
|
+
const method = {
|
|
7115
|
+
name: 'evmSignMessage',
|
|
7116
|
+
unlockPolicy: 'unlock-before-run',
|
|
7117
|
+
run: jest.fn().mockRejectedValue(error),
|
|
7118
|
+
};
|
|
7119
|
+
const features = { unlocked: true };
|
|
7120
|
+
const device = {
|
|
7121
|
+
features,
|
|
7122
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
7123
|
+
isProtocolV2: () => true,
|
|
7124
|
+
isBootloader: () => false,
|
|
7125
|
+
isRomloader: () => false,
|
|
7126
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7127
|
+
unlockDevice: jest.fn(),
|
|
7128
|
+
};
|
|
7129
|
+
|
|
7130
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
7131
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6697
7132
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6698
7133
|
});
|
|
6699
7134
|
|
|
7135
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
7136
|
+
const features = { unlocked: true };
|
|
7137
|
+
const context = createProtocolV2UnlockContext();
|
|
7138
|
+
const firstMethod = {
|
|
7139
|
+
name: 'allNetworkGetAddress',
|
|
7140
|
+
unlockPolicy: 'unlock-before-run',
|
|
7141
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
7142
|
+
};
|
|
7143
|
+
const nestedMethod = {
|
|
7144
|
+
name: 'evmGetAddress',
|
|
7145
|
+
unlockPolicy: 'unlock-before-run',
|
|
7146
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
7147
|
+
};
|
|
7148
|
+
const device = {
|
|
7149
|
+
features,
|
|
7150
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
7151
|
+
isProtocolV2: () => true,
|
|
7152
|
+
isBootloader: () => false,
|
|
7153
|
+
isRomloader: () => false,
|
|
7154
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7155
|
+
unlockDevice: jest.fn(),
|
|
7156
|
+
};
|
|
7157
|
+
|
|
7158
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
7159
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
7160
|
+
|
|
7161
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
7162
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
7163
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
7164
|
+
});
|
|
7165
|
+
|
|
6700
7166
|
test.each(['bootloader', 'romloader'] as const)(
|
|
6701
7167
|
'skips DeviceStatus and unlock in Protocol V2 %s mode',
|
|
6702
7168
|
async mode => {
|
|
@@ -6714,7 +7180,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6714
7180
|
unlockDevice: jest.fn(),
|
|
6715
7181
|
};
|
|
6716
7182
|
|
|
6717
|
-
await expect(
|
|
7183
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6718
7184
|
message: 'updating',
|
|
6719
7185
|
});
|
|
6720
7186
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6723,157 +7189,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6723
7189
|
}
|
|
6724
7190
|
);
|
|
6725
7191
|
|
|
6726
|
-
test('
|
|
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 () => {
|
|
7192
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6757
7193
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6758
|
-
[false, '
|
|
7194
|
+
[false, 'unlock-before-run'],
|
|
6759
7195
|
[true, 'none'],
|
|
6760
7196
|
] as const) {
|
|
6761
|
-
const error = deviceLockedError();
|
|
6762
7197
|
const method = {
|
|
6763
7198
|
unlockPolicy,
|
|
6764
|
-
|
|
7199
|
+
useDevicePassphraseState: false,
|
|
7200
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6765
7201
|
};
|
|
6766
7202
|
const device = {
|
|
7203
|
+
commands: { typedCall: jest.fn() },
|
|
6767
7204
|
isProtocolV2: () => isProtocolV2,
|
|
6768
7205
|
unlockDevice: jest.fn(),
|
|
6769
7206
|
};
|
|
6770
7207
|
|
|
6771
|
-
await expect(
|
|
7208
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
7209
|
+
message: 'ok',
|
|
7210
|
+
});
|
|
7211
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6772
7212
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6773
7213
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6774
7214
|
}
|
|
6775
7215
|
});
|
|
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
|
-
});
|
|
6877
7216
|
});
|
|
6878
7217
|
|
|
6879
7218
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -7005,7 +7344,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7005
7344
|
|
|
7006
7345
|
await method.run();
|
|
7007
7346
|
|
|
7008
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7347
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7348
|
+
eventless_wallet_session: true,
|
|
7349
|
+
});
|
|
7009
7350
|
});
|
|
7010
7351
|
|
|
7011
7352
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -7081,6 +7422,29 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7081
7422
|
|
|
7082
7423
|
await expect(method.run()).resolves.toBe(features);
|
|
7083
7424
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
7425
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
7426
|
+
emitUiEvent: false,
|
|
7427
|
+
});
|
|
7428
|
+
});
|
|
7429
|
+
|
|
7430
|
+
test('forwards an explicit PIN type when unlocking a Protocol V2 device', async () => {
|
|
7431
|
+
const method = new DeviceUnlock({
|
|
7432
|
+
id: 1,
|
|
7433
|
+
payload: {
|
|
7434
|
+
method: 'deviceUnlock',
|
|
7435
|
+
pinType: DeviceSessionPinType.Any,
|
|
7436
|
+
},
|
|
7437
|
+
});
|
|
7438
|
+
method.init();
|
|
7439
|
+
|
|
7440
|
+
const unlockDevice = jest.fn().mockResolvedValue({ unlocked: true });
|
|
7441
|
+
(method as any).device = { unlockDevice };
|
|
7442
|
+
|
|
7443
|
+
await method.run();
|
|
7444
|
+
|
|
7445
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
7446
|
+
emitUiEvent: false,
|
|
7447
|
+
});
|
|
7084
7448
|
});
|
|
7085
7449
|
|
|
7086
7450
|
test.each([
|
|
@@ -7339,7 +7703,10 @@ describe('Protocol V2 file write method', () => {
|
|
|
7339
7703
|
append: false,
|
|
7340
7704
|
ui_percentage: 100,
|
|
7341
7705
|
},
|
|
7342
|
-
{
|
|
7706
|
+
expect.objectContaining({
|
|
7707
|
+
timeoutMs: undefined,
|
|
7708
|
+
onWriteCompleted: expect.any(Function),
|
|
7709
|
+
})
|
|
7343
7710
|
);
|
|
7344
7711
|
expect(method.postMessage).toHaveBeenCalledWith({
|
|
7345
7712
|
event: 'UI_EVENT',
|
|
@@ -7388,7 +7755,10 @@ describe('Protocol V2 file write method', () => {
|
|
|
7388
7755
|
append: false,
|
|
7389
7756
|
ui_percentage: 0,
|
|
7390
7757
|
},
|
|
7391
|
-
{
|
|
7758
|
+
expect.objectContaining({
|
|
7759
|
+
timeoutMs: undefined,
|
|
7760
|
+
onWriteCompleted: expect.any(Function),
|
|
7761
|
+
})
|
|
7392
7762
|
);
|
|
7393
7763
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
7394
7764
|
2,
|
|
@@ -7405,7 +7775,10 @@ describe('Protocol V2 file write method', () => {
|
|
|
7405
7775
|
append: false,
|
|
7406
7776
|
ui_percentage: 100,
|
|
7407
7777
|
},
|
|
7408
|
-
{
|
|
7778
|
+
expect.objectContaining({
|
|
7779
|
+
timeoutMs: undefined,
|
|
7780
|
+
onWriteCompleted: expect.any(Function),
|
|
7781
|
+
})
|
|
7409
7782
|
);
|
|
7410
7783
|
expect(result).toMatchObject({
|
|
7411
7784
|
path: 'vol1:test.bin',
|