@onekeyfe/hd-core 1.2.0-alpha.54 → 1.2.0-alpha.55
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 +231 -0
- package/__tests__/DeviceCommands.test.ts +45 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/deviceUploadNft.test.ts +293 -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__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +286 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
- package/__tests__/protocol-v2.test.ts +1350 -1012
- 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__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +86 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +11 -25
- 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/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 +0 -9
- 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 +2 -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/KaspaSignTransaction.d.ts.map +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/stellar/StellarSignTransaction.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 +4 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -2
- package/dist/data-manager/DataManager.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/DevicePool.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 +214 -231
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2616 -3527
- 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/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 +2 -4
- 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 +2 -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.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 +22 -13
- 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/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 +54 -57
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -316
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +569 -965
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +70 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +34 -11
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -122
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +1 -12
- package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -0
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +167 -83
- package/src/core/uiPromiseRegistry.ts +41 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +71 -39
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +259 -43
- package/src/device/DeviceCommands.ts +7 -1
- package/src/device/DevicePool.ts +7 -2
- 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/resources.ts +359 -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 +118 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +2 -4
- package/src/types/api/detectDeviceConnectProtocol.ts +7 -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 +3 -76
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +3 -0
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +42 -13
- package/src/utils/deviceFeaturesCompat.ts +0 -6
- package/src/utils/deviceFeaturesUtils.ts +8 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -294
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -415
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
DeviceSessionPinType,
|
|
5
5
|
DeviceSettingsPage,
|
|
6
6
|
DeviceType,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
10
11
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -61,6 +62,7 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
62
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
63
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
64
|
import { DevicePool } from '../src/device/DevicePool';
|
|
65
|
+
import { DEVICE } from '../src/events';
|
|
64
66
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
65
67
|
import {
|
|
66
68
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -74,11 +76,15 @@ import {
|
|
|
74
76
|
requestProtocolV2ProtocolInfo,
|
|
75
77
|
supportsProtocolV2Message,
|
|
76
78
|
} from '../src/protocols/protocol-v2/features';
|
|
79
|
+
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
77
80
|
import {
|
|
78
81
|
getProtocolV2WalletSession,
|
|
79
82
|
refreshProtocolV2DeviceStatus,
|
|
80
83
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
81
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
createProtocolV2UnlockContext,
|
|
86
|
+
runMethodWithUnlockPolicy,
|
|
87
|
+
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
82
88
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
83
89
|
import {
|
|
84
90
|
buildProtocolV1FeaturesPayload,
|
|
@@ -92,7 +98,6 @@ import {
|
|
|
92
98
|
getMethodVersionRange,
|
|
93
99
|
} from '../src/utils';
|
|
94
100
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
-
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
96
101
|
import {
|
|
97
102
|
getPassphraseState,
|
|
98
103
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -241,7 +246,7 @@ describe('UploadPortfolio', () => {
|
|
|
241
246
|
(method as any).device = device;
|
|
242
247
|
|
|
243
248
|
method.init();
|
|
244
|
-
await
|
|
249
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
245
250
|
|
|
246
251
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
247
252
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -417,14 +422,16 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
417
422
|
}
|
|
418
423
|
return d.features;
|
|
419
424
|
};
|
|
425
|
+
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
426
|
+
requestProtocolV2ProtocolInfo({
|
|
427
|
+
commands: d.getCommands?.() ?? d.commands,
|
|
428
|
+
timeoutMs,
|
|
429
|
+
});
|
|
420
430
|
d.probeProtocolV2RuntimeState ??= async (
|
|
421
431
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
422
432
|
timeoutMs?: number
|
|
423
433
|
) => {
|
|
424
|
-
const protocolInfo = await
|
|
425
|
-
commands: d.getCommands(),
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
434
|
+
const protocolInfo = await d.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
428
435
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
429
436
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
430
437
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -781,6 +788,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
781
788
|
if (request === 'DeviceStatusGet') {
|
|
782
789
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
783
790
|
}
|
|
791
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
792
|
+
return { type: 'Success', message: {} };
|
|
793
|
+
}
|
|
784
794
|
if (request === 'DeviceSessionGet') {
|
|
785
795
|
return {
|
|
786
796
|
type: 'DeviceSession',
|
|
@@ -805,7 +815,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
815
|
).resolves.toEqual({
|
|
806
816
|
passphraseState: 'state-1',
|
|
807
817
|
newSession: 'session-1',
|
|
808
|
-
unlockedAttachPin:
|
|
818
|
+
unlockedAttachPin: false,
|
|
809
819
|
resumed: false,
|
|
810
820
|
});
|
|
811
821
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -814,7 +824,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
814
824
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
815
825
|
type: DeviceSessionPinType.Main,
|
|
816
826
|
});
|
|
817
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
827
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
828
|
+
passphrase: '',
|
|
829
|
+
on_device: false,
|
|
830
|
+
});
|
|
831
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
832
|
+
seed_domains: [],
|
|
833
|
+
});
|
|
818
834
|
device.passphraseState = 'state-1';
|
|
819
835
|
expect(device.getInternalState()).toBe('session-1');
|
|
820
836
|
});
|
|
@@ -847,6 +863,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
847
863
|
},
|
|
848
864
|
};
|
|
849
865
|
}
|
|
866
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
850
867
|
if (request === 'DeviceSessionGet') {
|
|
851
868
|
return {
|
|
852
869
|
message: {
|
|
@@ -863,9 +880,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
880
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
864
881
|
|
|
865
882
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
866
|
-
expect(
|
|
883
|
+
expect(
|
|
884
|
+
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
885
|
+
).toHaveLength(1);
|
|
886
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
887
|
+
seed_domains: [],
|
|
888
|
+
});
|
|
867
889
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
868
890
|
session_id: 'standard-session',
|
|
891
|
+
btc_test_address: 'standard-state',
|
|
892
|
+
seed_domains: [],
|
|
869
893
|
});
|
|
870
894
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
871
895
|
passphraseState: 'standard-state',
|
|
@@ -907,6 +931,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
907
931
|
},
|
|
908
932
|
};
|
|
909
933
|
}
|
|
934
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
910
935
|
if (request === 'DeviceSessionAskPin') {
|
|
911
936
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
912
937
|
}
|
|
@@ -1001,6 +1026,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1001
1026
|
if (request === 'DeviceStatusGet') {
|
|
1002
1027
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1003
1028
|
}
|
|
1029
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1004
1030
|
if (request === 'DeviceSessionGet') {
|
|
1005
1031
|
sessionGetCalls += 1;
|
|
1006
1032
|
return {
|
|
@@ -1227,7 +1253,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1227
1253
|
passphrase: 'host hidden wallet',
|
|
1228
1254
|
on_device: false,
|
|
1229
1255
|
});
|
|
1230
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1256
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1257
|
+
seed_domains: [],
|
|
1258
|
+
});
|
|
1231
1259
|
});
|
|
1232
1260
|
|
|
1233
1261
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1335,6 +1363,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1335
1363
|
|
|
1336
1364
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1337
1365
|
session_id: 'session-a',
|
|
1366
|
+
btc_test_address: 'state-a',
|
|
1367
|
+
seed_domains: [],
|
|
1338
1368
|
});
|
|
1339
1369
|
expect(device.getInternalState()).toBe('session-b');
|
|
1340
1370
|
});
|
|
@@ -1402,7 +1432,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1402
1432
|
});
|
|
1403
1433
|
expect(typedCall.mock.calls).toEqual([
|
|
1404
1434
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1405
|
-
[
|
|
1435
|
+
[
|
|
1436
|
+
'DeviceSessionGet',
|
|
1437
|
+
'DeviceSession',
|
|
1438
|
+
{
|
|
1439
|
+
session_id: 'session-a',
|
|
1440
|
+
btc_test_address: 'state-a',
|
|
1441
|
+
seed_domains: [],
|
|
1442
|
+
},
|
|
1443
|
+
],
|
|
1406
1444
|
]);
|
|
1407
1445
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1408
1446
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1450,7 +1488,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1450
1488
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1451
1489
|
],
|
|
1452
1490
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1453
|
-
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1491
|
+
['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
|
|
1454
1492
|
]);
|
|
1455
1493
|
});
|
|
1456
1494
|
|
|
@@ -1553,6 +1591,73 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1591
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1592
|
});
|
|
1555
1593
|
|
|
1594
|
+
test.each([false, true])(
|
|
1595
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1596
|
+
async lockFails => {
|
|
1597
|
+
const deviceId = 'attach-mismatch-device';
|
|
1598
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1599
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1600
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1601
|
+
{
|
|
1602
|
+
status: {
|
|
1603
|
+
device_id: deviceId,
|
|
1604
|
+
unlocked: true,
|
|
1605
|
+
passphrase_enabled: true,
|
|
1606
|
+
attach_to_pin_enabled: true,
|
|
1607
|
+
unlocked_by_attach_to_pin: false,
|
|
1608
|
+
},
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
device.passphraseState = 'expected-state';
|
|
1612
|
+
|
|
1613
|
+
const typedCall = jest.fn((request: string) => {
|
|
1614
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1615
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1616
|
+
if (request === 'DeviceStatusGet') {
|
|
1617
|
+
return {
|
|
1618
|
+
message: {
|
|
1619
|
+
device_id: deviceId,
|
|
1620
|
+
unlocked: true,
|
|
1621
|
+
passphrase_enabled: true,
|
|
1622
|
+
attach_to_pin_enabled: true,
|
|
1623
|
+
unlocked_by_attach_to_pin: true,
|
|
1624
|
+
},
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (request === 'DeviceSessionGet') {
|
|
1628
|
+
return {
|
|
1629
|
+
message: {
|
|
1630
|
+
btc_test_address: 'wrong-attach-state',
|
|
1631
|
+
session_id: 'wrong-attach-session',
|
|
1632
|
+
},
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (request === 'LockDevice') {
|
|
1636
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1637
|
+
return { message: { message: 'Device locked' } };
|
|
1638
|
+
}
|
|
1639
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1640
|
+
});
|
|
1641
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1642
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1643
|
+
|
|
1644
|
+
await expect(
|
|
1645
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1646
|
+
).rejects.toMatchObject({
|
|
1647
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1651
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1652
|
+
btc_test_address: 'expected-state',
|
|
1653
|
+
seed_domains: [],
|
|
1654
|
+
});
|
|
1655
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1656
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1657
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1556
1661
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1662
|
const device = Device.fromDescriptor({
|
|
1558
1663
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1714,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1714
|
}),
|
|
1610
1715
|
{ cancelDeviceOnReject: false }
|
|
1611
1716
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1717
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1718
|
+
DeviceSessionPinType.AttachToPin,
|
|
1719
|
+
expect.objectContaining({
|
|
1720
|
+
emitUiEvent: false,
|
|
1721
|
+
interaction: expect.objectContaining({
|
|
1722
|
+
phase: 'pin',
|
|
1723
|
+
transition: 'start',
|
|
1724
|
+
protocol: 'V2',
|
|
1725
|
+
}),
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1613
1728
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1729
|
});
|
|
1615
1730
|
|
|
@@ -1893,12 +2008,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
2008
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
2009
|
});
|
|
1895
2010
|
|
|
1896
|
-
test('
|
|
2011
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
2012
|
const typedCall = jest
|
|
1898
2013
|
.fn()
|
|
1899
2014
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
2015
|
.mockResolvedValueOnce({
|
|
1903
2016
|
message: {
|
|
1904
2017
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2039,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2039
|
await expect(
|
|
1927
2040
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2041
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2042
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2043
|
+
btc_test_address: 'expected-state',
|
|
2044
|
+
seed_domains: [],
|
|
2045
|
+
});
|
|
2046
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2047
|
});
|
|
1940
2048
|
|
|
1941
2049
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,8 +2109,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2109
|
true
|
|
2002
2110
|
);
|
|
2003
2111
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
[
|
|
2005
|
-
|
|
2112
|
+
[
|
|
2113
|
+
'DeviceSessionGet',
|
|
2114
|
+
'DeviceSession',
|
|
2115
|
+
{
|
|
2116
|
+
session_id: 'session-pro2-app',
|
|
2117
|
+
btc_test_address: 'state-pro2-app',
|
|
2118
|
+
seed_domains: [],
|
|
2119
|
+
},
|
|
2120
|
+
],
|
|
2006
2121
|
]);
|
|
2007
2122
|
});
|
|
2008
2123
|
|
|
@@ -2191,7 +2306,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2191
2306
|
passphrase: 'host hidden wallet',
|
|
2192
2307
|
on_device: false,
|
|
2193
2308
|
});
|
|
2194
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2309
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2310
|
+
seed_domains: [],
|
|
2311
|
+
});
|
|
2195
2312
|
});
|
|
2196
2313
|
|
|
2197
2314
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2269,12 +2386,83 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2386
|
);
|
|
2270
2387
|
|
|
2271
2388
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2390
|
+
btc_test_address: 'expected-state',
|
|
2391
|
+
seed_domains: [],
|
|
2392
|
+
});
|
|
2393
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2394
|
passphrase: 'host hidden wallet',
|
|
2274
2395
|
on_device: false,
|
|
2275
2396
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2397
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2398
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2399
|
+
seed_domains: [],
|
|
2400
|
+
});
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
2404
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2405
|
+
const typedCall = jest.fn((request: string) => {
|
|
2406
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2407
|
+
return { message: { version: 2 } };
|
|
2408
|
+
}
|
|
2409
|
+
if (request === 'DeviceStatusGet') {
|
|
2410
|
+
return {
|
|
2411
|
+
message: {
|
|
2412
|
+
device_id: 'attach-wallet-device',
|
|
2413
|
+
unlocked: true,
|
|
2414
|
+
passphrase_enabled: true,
|
|
2415
|
+
attach_to_pin_enabled: true,
|
|
2416
|
+
unlocked_by_attach_to_pin: true,
|
|
2417
|
+
},
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
if (request === 'DeviceSessionAskPin' || request === 'DeviceSessionAskPassphrase') {
|
|
2421
|
+
return { message: { message: 'Wallet selected' } };
|
|
2422
|
+
}
|
|
2423
|
+
if (request === 'DeviceSessionGet') {
|
|
2424
|
+
return {
|
|
2425
|
+
message: {
|
|
2426
|
+
session_id: 'main-wallet-session',
|
|
2427
|
+
btc_test_address: 'main-wallet-state',
|
|
2428
|
+
},
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
if (request === 'LockDevice') {
|
|
2432
|
+
return { message: { message: 'Device locked' } };
|
|
2433
|
+
}
|
|
2434
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
2438
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2439
|
+
{
|
|
2440
|
+
status: {
|
|
2441
|
+
device_id: 'attach-wallet-device',
|
|
2442
|
+
unlocked: true,
|
|
2443
|
+
passphrase_enabled: true,
|
|
2444
|
+
attach_to_pin_enabled: true,
|
|
2445
|
+
// 模拟缓存滞后:设备状态刷新后才确认当前由 Attach PIN 解锁。
|
|
2446
|
+
unlocked_by_attach_to_pin: false,
|
|
2447
|
+
},
|
|
2448
|
+
}
|
|
2449
|
+
);
|
|
2450
|
+
(device as any).commands = { typedCall };
|
|
2451
|
+
const clearInternalState = jest.spyOn(device, 'clearInternalState');
|
|
2452
|
+
|
|
2453
|
+
await expect(device.checkPassphraseStateSafety(undefined, true, false)).rejects.toMatchObject({
|
|
2454
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2458
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
2459
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2460
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2461
|
+
'DeviceSessionAskPassphrase',
|
|
2462
|
+
'Success',
|
|
2463
|
+
expect.anything()
|
|
2464
|
+
);
|
|
2465
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
2278
2466
|
});
|
|
2279
2467
|
|
|
2280
2468
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2307,10 +2495,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2307
2495
|
await expect(
|
|
2308
2496
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2309
2497
|
).resolves.toBe(true);
|
|
2310
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2498
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
2311
2499
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2500
|
eventless_wallet_session: true,
|
|
2313
2501
|
});
|
|
2502
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2503
|
+
seed_domains: [],
|
|
2504
|
+
});
|
|
2505
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2506
|
+
passphrase: '',
|
|
2507
|
+
on_device: false,
|
|
2508
|
+
});
|
|
2509
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2510
|
+
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2511
|
+
2
|
|
2512
|
+
);
|
|
2513
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2314
2514
|
});
|
|
2315
2515
|
|
|
2316
2516
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2381,7 +2581,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2381
2581
|
uuid: 'CACHED-SERIAL',
|
|
2382
2582
|
deviceId: null,
|
|
2383
2583
|
bleName: 'Cached BLE',
|
|
2384
|
-
name: 'Cached
|
|
2584
|
+
name: 'Cached BLE',
|
|
2385
2585
|
label: 'Cached Label',
|
|
2386
2586
|
deviceType: 'pro2',
|
|
2387
2587
|
});
|
|
@@ -2408,7 +2608,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2408
2608
|
expect(device.features?.label).toBeNull();
|
|
2409
2609
|
});
|
|
2410
2610
|
|
|
2411
|
-
test('
|
|
2611
|
+
test('uses the BLE name for discovery while preserving the Protocol V2 label', () => {
|
|
2412
2612
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2413
2613
|
(device as any).features = {
|
|
2414
2614
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
@@ -2416,9 +2616,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2416
2616
|
bleName: 'Pro2 6136',
|
|
2417
2617
|
};
|
|
2418
2618
|
|
|
2619
|
+
expect(device.getCurrentDisplayName()).toBe('My Pro 2');
|
|
2419
2620
|
expect(device.toMessageObject()).toMatchObject({
|
|
2420
2621
|
bleName: 'Pro2 6136',
|
|
2421
|
-
name: '
|
|
2622
|
+
name: 'Pro2 6136',
|
|
2422
2623
|
label: 'My Pro 2',
|
|
2423
2624
|
});
|
|
2424
2625
|
});
|
|
@@ -2472,10 +2673,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2472
2673
|
hw: true,
|
|
2473
2674
|
fw: true,
|
|
2474
2675
|
coprocessor: true,
|
|
2475
|
-
se1: true,
|
|
2476
|
-
se2: true,
|
|
2477
|
-
se3: true,
|
|
2478
|
-
se4: true,
|
|
2479
2676
|
},
|
|
2480
2677
|
types: {
|
|
2481
2678
|
version: true,
|
|
@@ -2667,10 +2864,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2667
2864
|
hw: true,
|
|
2668
2865
|
fw: true,
|
|
2669
2866
|
coprocessor: true,
|
|
2670
|
-
se1: true,
|
|
2671
|
-
se2: true,
|
|
2672
|
-
se3: true,
|
|
2673
|
-
se4: true,
|
|
2674
2867
|
},
|
|
2675
2868
|
types: {
|
|
2676
2869
|
version: true,
|
|
@@ -2681,7 +2874,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2874
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2875
|
}
|
|
2683
2876
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2877
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2878
|
+
eventless_wallet_session: true,
|
|
2879
|
+
});
|
|
2685
2880
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2881
|
});
|
|
2687
2882
|
|
|
@@ -2719,7 +2914,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2914
|
}),
|
|
2720
2915
|
expect.anything()
|
|
2721
2916
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2917
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2918
|
+
eventless_wallet_session: true,
|
|
2919
|
+
});
|
|
2723
2920
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2921
|
expect(device.features).toMatchObject({
|
|
2725
2922
|
deviceId: null,
|
|
@@ -2985,10 +3182,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3182
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3183
|
},
|
|
2987
3184
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3185
|
.mockResolvedValueOnce({
|
|
2993
3186
|
type: 'DeviceStatus',
|
|
2994
3187
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3203,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3203
|
firmwareVersion: '1.2.4',
|
|
3011
3204
|
passphraseProtection: null,
|
|
3012
3205
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3206
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3207
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3208
|
4,
|
|
3016
3209
|
'DeviceInfoGet',
|
|
@@ -3020,10 +3213,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3020
3213
|
hw: true,
|
|
3021
3214
|
fw: true,
|
|
3022
3215
|
coprocessor: true,
|
|
3023
|
-
se1: true,
|
|
3024
|
-
se2: true,
|
|
3025
|
-
se3: true,
|
|
3026
|
-
se4: true,
|
|
3027
3216
|
},
|
|
3028
3217
|
types: {
|
|
3029
3218
|
version: true,
|
|
@@ -3124,9 +3313,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3313
|
status: { init_states: true },
|
|
3125
3314
|
}
|
|
3126
3315
|
);
|
|
3316
|
+
const pinOnDevice = jest.fn();
|
|
3317
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3318
|
|
|
3128
3319
|
const features = await device.unlockDevice();
|
|
3129
3320
|
|
|
3321
|
+
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3322
|
+
device,
|
|
3323
|
+
DeviceSessionPinType.Main,
|
|
3324
|
+
expect.objectContaining({
|
|
3325
|
+
source: 'unlock-coordinator',
|
|
3326
|
+
reason: 'device-unlock',
|
|
3327
|
+
deviceOnly: true,
|
|
3328
|
+
})
|
|
3329
|
+
);
|
|
3330
|
+
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3331
|
+
typedCall.mock.invocationCallOrder[0]
|
|
3332
|
+
);
|
|
3130
3333
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3334
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3335
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3375,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3375
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3376
|
});
|
|
3174
3377
|
(device as any).commands = { typedCall };
|
|
3378
|
+
const pinOnDevice = jest.fn();
|
|
3379
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3380
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3381
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3382
|
|
|
3383
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3384
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3385
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3386
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3577,6 +3783,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3783
|
|
|
3578
3784
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3785
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3786
|
+
let forceReloadDataSpy: jest.SpyInstance<Promise<void>, []>;
|
|
3787
|
+
|
|
3788
|
+
beforeEach(() => {
|
|
3789
|
+
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3790
|
+
});
|
|
3791
|
+
|
|
3792
|
+
afterEach(() => {
|
|
3793
|
+
jest.restoreAllMocks();
|
|
3794
|
+
});
|
|
3580
3795
|
|
|
3581
3796
|
const buildOkppHeader = ({
|
|
3582
3797
|
type = 'RESC',
|
|
@@ -3701,7 +3916,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3701
3916
|
specific: true,
|
|
3702
3917
|
},
|
|
3703
3918
|
},
|
|
3704
|
-
{ timeoutMs:
|
|
3919
|
+
{ timeoutMs: 30000 }
|
|
3705
3920
|
);
|
|
3706
3921
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3707
3922
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3717,7 +3932,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3932
|
});
|
|
3718
3933
|
});
|
|
3719
3934
|
|
|
3720
|
-
test('
|
|
3935
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3936
|
const method = new FirmwareUpdateV4({
|
|
3722
3937
|
id: 1,
|
|
3723
3938
|
payload: {
|
|
@@ -3752,7 +3967,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3967
|
|
|
3753
3968
|
await method.run();
|
|
3754
3969
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3970
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3971
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3972
|
fwBinaryMap: [
|
|
3758
3973
|
{
|
|
@@ -3763,6 +3978,69 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3763
3978
|
],
|
|
3764
3979
|
bootloaderBinary: null,
|
|
3765
3980
|
});
|
|
3981
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3982
|
+
});
|
|
3983
|
+
|
|
3984
|
+
test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
|
|
3985
|
+
const method = new FirmwareUpdateV4({
|
|
3986
|
+
id: 1,
|
|
3987
|
+
payload: {
|
|
3988
|
+
method: 'firmwareUpdateV4',
|
|
3989
|
+
platform: 'web',
|
|
3990
|
+
targetsToUpdate: ['resource'],
|
|
3991
|
+
},
|
|
3992
|
+
});
|
|
3993
|
+
method.init();
|
|
3994
|
+
|
|
3995
|
+
(method as any).device = stubDevice({
|
|
3996
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
3997
|
+
features: {
|
|
3998
|
+
deviceType: 'pro2',
|
|
3999
|
+
firmwareVersion: '1.0.0',
|
|
4000
|
+
mode: 'normal',
|
|
4001
|
+
bootloaderMode: false,
|
|
4002
|
+
capabilities: [],
|
|
4003
|
+
},
|
|
4004
|
+
isBootloader: () => false,
|
|
4005
|
+
isRomloader: () => false,
|
|
4006
|
+
});
|
|
4007
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4008
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4009
|
+
bootloaderBinary: null,
|
|
4010
|
+
fwBinaryMap: [],
|
|
4011
|
+
installItems: [],
|
|
4012
|
+
});
|
|
4013
|
+
|
|
4014
|
+
const order: string[] = [];
|
|
4015
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4016
|
+
order.push('enter-bootloader');
|
|
4017
|
+
return Promise.resolve(true);
|
|
4018
|
+
});
|
|
4019
|
+
(method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
|
|
4020
|
+
order.push('prepare-resources');
|
|
4021
|
+
return Promise.resolve([
|
|
4022
|
+
{
|
|
4023
|
+
name: 'images.okpkg',
|
|
4024
|
+
binary: new Uint8Array([1]).buffer,
|
|
4025
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
4026
|
+
},
|
|
4027
|
+
]);
|
|
4028
|
+
});
|
|
4029
|
+
(method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
|
|
4030
|
+
order.push('execute-update');
|
|
4031
|
+
return Promise.resolve();
|
|
4032
|
+
});
|
|
4033
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4034
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
4035
|
+
bootloaderVersion: '1.0.0',
|
|
4036
|
+
bleVersion: '1.0.0',
|
|
4037
|
+
firmwareVersion: '1.0.0',
|
|
4038
|
+
});
|
|
4039
|
+
method.postTipMessage = jest.fn();
|
|
4040
|
+
|
|
4041
|
+
await method.run();
|
|
4042
|
+
|
|
4043
|
+
expect(order).toEqual(['enter-bootloader', 'prepare-resources', 'execute-update']);
|
|
3766
4044
|
});
|
|
3767
4045
|
|
|
3768
4046
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -3814,6 +4092,33 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3814
4092
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3815
4093
|
});
|
|
3816
4094
|
|
|
4095
|
+
test('keeps Protocol V2 bootloader active before firmware transfer', async () => {
|
|
4096
|
+
const method = new FirmwareUpdateV4({
|
|
4097
|
+
id: 1,
|
|
4098
|
+
payload: {
|
|
4099
|
+
method: 'firmwareUpdateV4',
|
|
4100
|
+
},
|
|
4101
|
+
});
|
|
4102
|
+
(method as any).device = stubDevice({
|
|
4103
|
+
originalDescriptor: { id: 'usb-id', path: 'bootloader-path', protocolType: 'V2' },
|
|
4104
|
+
features: {
|
|
4105
|
+
deviceType: 'pro2',
|
|
4106
|
+
mode: 'bootloader',
|
|
4107
|
+
bootloaderMode: true,
|
|
4108
|
+
capabilities: [],
|
|
4109
|
+
},
|
|
4110
|
+
isBootloader: () => true,
|
|
4111
|
+
isRomloader: () => false,
|
|
4112
|
+
});
|
|
4113
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4114
|
+
method.postTipMessage = jest.fn();
|
|
4115
|
+
|
|
4116
|
+
await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
|
|
4117
|
+
|
|
4118
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4119
|
+
expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
4120
|
+
});
|
|
4121
|
+
|
|
3817
4122
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
3818
4123
|
const method = new FirmwareUpdateV4({
|
|
3819
4124
|
id: 1,
|
|
@@ -3866,6 +4171,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
4171
|
},
|
|
3867
4172
|
});
|
|
3868
4173
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4174
|
+
const prepareResourceBundles = jest
|
|
4175
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4176
|
+
.mockResolvedValue(undefined);
|
|
3869
4177
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
4178
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
4179
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +4186,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
4186
|
|
|
3879
4187
|
await method.run();
|
|
3880
4188
|
|
|
4189
|
+
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
3881
4190
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
4191
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
4192
|
bootloaderBinary: null,
|
|
@@ -3960,7 +4269,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3960
4269
|
getCommands: () => commands,
|
|
3961
4270
|
mainId: 'usb-path',
|
|
3962
4271
|
});
|
|
3963
|
-
const cachedDevice = {
|
|
4272
|
+
const cachedDevice = {
|
|
4273
|
+
getConnectId: () => 'usb-path',
|
|
4274
|
+
getCurrentSerialNo: () => '',
|
|
4275
|
+
};
|
|
3964
4276
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
3965
4277
|
devices: {},
|
|
3966
4278
|
deviceList: [cachedDevice],
|
|
@@ -3977,17 +4289,193 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3977
4289
|
expect(initialize).not.toHaveBeenCalled();
|
|
3978
4290
|
});
|
|
3979
4291
|
|
|
3980
|
-
test('
|
|
4292
|
+
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
3981
4293
|
const method = new FirmwareUpdateV4({
|
|
3982
4294
|
id: 1,
|
|
3983
4295
|
payload: {
|
|
3984
4296
|
method: 'firmwareUpdateV4',
|
|
3985
4297
|
},
|
|
3986
4298
|
});
|
|
3987
|
-
const commands = { disposed:
|
|
3988
|
-
const
|
|
4299
|
+
const commands = { disposed: true, mainId: '' };
|
|
4300
|
+
const updateFromCache = jest.fn();
|
|
3989
4301
|
const device = stubDevice({
|
|
3990
|
-
originalDescriptor: {
|
|
4302
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4303
|
+
deviceConnector: {
|
|
4304
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4305
|
+
descriptors: [
|
|
4306
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4307
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4308
|
+
],
|
|
4309
|
+
}),
|
|
4310
|
+
},
|
|
4311
|
+
updateFromCache,
|
|
4312
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4313
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4314
|
+
commands,
|
|
4315
|
+
getCommands: () => commands,
|
|
4316
|
+
mainId: 'old-usb-path',
|
|
4317
|
+
});
|
|
4318
|
+
const otherDevice = {
|
|
4319
|
+
getConnectId: () => 'usb-a',
|
|
4320
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4321
|
+
};
|
|
4322
|
+
const expectedDevice = {
|
|
4323
|
+
getConnectId: () => 'usb-b',
|
|
4324
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4325
|
+
};
|
|
4326
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4327
|
+
devices: {},
|
|
4328
|
+
deviceList: [otherDevice, expectedDevice],
|
|
4329
|
+
} as any);
|
|
4330
|
+
(method as any).device = device;
|
|
4331
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4332
|
+
|
|
4333
|
+
try {
|
|
4334
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4335
|
+
} finally {
|
|
4336
|
+
getDevices.mockRestore();
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
expect(updateFromCache).toHaveBeenCalledWith(expectedDevice);
|
|
4340
|
+
});
|
|
4341
|
+
|
|
4342
|
+
test('rejects ambiguous Protocol V2 USB reconnect candidates without an identity match', async () => {
|
|
4343
|
+
const method = new FirmwareUpdateV4({
|
|
4344
|
+
id: 1,
|
|
4345
|
+
payload: {
|
|
4346
|
+
method: 'firmwareUpdateV4',
|
|
4347
|
+
},
|
|
4348
|
+
});
|
|
4349
|
+
const updateFromCache = jest.fn();
|
|
4350
|
+
const device = stubDevice({
|
|
4351
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4352
|
+
deviceConnector: {
|
|
4353
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4354
|
+
descriptors: [
|
|
4355
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4356
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4357
|
+
],
|
|
4358
|
+
}),
|
|
4359
|
+
},
|
|
4360
|
+
updateFromCache,
|
|
4361
|
+
});
|
|
4362
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4363
|
+
devices: {},
|
|
4364
|
+
deviceList: [
|
|
4365
|
+
{
|
|
4366
|
+
getConnectId: () => 'usb-a',
|
|
4367
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4368
|
+
},
|
|
4369
|
+
{
|
|
4370
|
+
getConnectId: () => 'usb-b',
|
|
4371
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4372
|
+
},
|
|
4373
|
+
],
|
|
4374
|
+
} as any);
|
|
4375
|
+
(method as any).device = device;
|
|
4376
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-C';
|
|
4377
|
+
|
|
4378
|
+
try {
|
|
4379
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4380
|
+
} finally {
|
|
4381
|
+
getDevices.mockRestore();
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4385
|
+
});
|
|
4386
|
+
|
|
4387
|
+
test('allows a single Protocol V2 USB reconnect candidate when its physical serial is unavailable', async () => {
|
|
4388
|
+
const method = new FirmwareUpdateV4({
|
|
4389
|
+
id: 1,
|
|
4390
|
+
payload: {
|
|
4391
|
+
method: 'firmwareUpdateV4',
|
|
4392
|
+
},
|
|
4393
|
+
});
|
|
4394
|
+
const commands = { disposed: true, mainId: '' };
|
|
4395
|
+
const updateFromCache = jest.fn();
|
|
4396
|
+
const device = stubDevice({
|
|
4397
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4398
|
+
deviceConnector: {
|
|
4399
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4400
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4401
|
+
}),
|
|
4402
|
+
},
|
|
4403
|
+
updateFromCache,
|
|
4404
|
+
hasDeviceAcquire: jest.fn(() => false),
|
|
4405
|
+
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4406
|
+
commands,
|
|
4407
|
+
getCommands: () => commands,
|
|
4408
|
+
mainId: 'old-usb-path',
|
|
4409
|
+
});
|
|
4410
|
+
const candidate = {
|
|
4411
|
+
getConnectId: () => 'usb-a',
|
|
4412
|
+
getCurrentSerialNo: () => '',
|
|
4413
|
+
};
|
|
4414
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4415
|
+
devices: {},
|
|
4416
|
+
deviceList: [candidate],
|
|
4417
|
+
} as any);
|
|
4418
|
+
(method as any).device = device;
|
|
4419
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-A';
|
|
4420
|
+
|
|
4421
|
+
try {
|
|
4422
|
+
await (method as any).reconnectProtocolV2Device();
|
|
4423
|
+
} finally {
|
|
4424
|
+
getDevices.mockRestore();
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
expect(updateFromCache).toHaveBeenCalledWith(candidate);
|
|
4428
|
+
});
|
|
4429
|
+
|
|
4430
|
+
test('rejects a single Protocol V2 USB reconnect candidate with a different physical serial', async () => {
|
|
4431
|
+
const method = new FirmwareUpdateV4({
|
|
4432
|
+
id: 1,
|
|
4433
|
+
payload: {
|
|
4434
|
+
method: 'firmwareUpdateV4',
|
|
4435
|
+
},
|
|
4436
|
+
});
|
|
4437
|
+
const updateFromCache = jest.fn();
|
|
4438
|
+
const device = stubDevice({
|
|
4439
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4440
|
+
deviceConnector: {
|
|
4441
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
4442
|
+
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4443
|
+
}),
|
|
4444
|
+
},
|
|
4445
|
+
updateFromCache,
|
|
4446
|
+
});
|
|
4447
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4448
|
+
devices: {},
|
|
4449
|
+
deviceList: [
|
|
4450
|
+
{
|
|
4451
|
+
getConnectId: () => 'usb-a',
|
|
4452
|
+
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4453
|
+
},
|
|
4454
|
+
],
|
|
4455
|
+
} as any);
|
|
4456
|
+
(method as any).device = device;
|
|
4457
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4458
|
+
|
|
4459
|
+
try {
|
|
4460
|
+
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4461
|
+
} finally {
|
|
4462
|
+
getDevices.mockRestore();
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4466
|
+
});
|
|
4467
|
+
|
|
4468
|
+
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4469
|
+
const method = new FirmwareUpdateV4({
|
|
4470
|
+
id: 1,
|
|
4471
|
+
payload: {
|
|
4472
|
+
method: 'firmwareUpdateV4',
|
|
4473
|
+
},
|
|
4474
|
+
});
|
|
4475
|
+
const commands = { disposed: false, mainId: 'usb-session' };
|
|
4476
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
4477
|
+
const device = stubDevice({
|
|
4478
|
+
originalDescriptor: {
|
|
3991
4479
|
id: 'usb-id',
|
|
3992
4480
|
path: 'usb-path',
|
|
3993
4481
|
session: 'usb-session',
|
|
@@ -4071,7 +4559,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4071
4559
|
});
|
|
4072
4560
|
method.postTipMessage = jest.fn();
|
|
4073
4561
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4074
|
-
|
|
4562
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4563
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4564
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4075
4565
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4076
4566
|
message: 'Device rebooted successfully',
|
|
4077
4567
|
});
|
|
@@ -4080,9 +4570,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4080
4570
|
|
|
4081
4571
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4082
4572
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4573
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4083
4574
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4084
4575
|
});
|
|
4085
4576
|
|
|
4577
|
+
test('skips a second Normal reboot after Pro2 already returned to App mode', async () => {
|
|
4578
|
+
const method = new FirmwareUpdateV4({
|
|
4579
|
+
id: 1,
|
|
4580
|
+
payload: {
|
|
4581
|
+
method: 'firmwareUpdateV4',
|
|
4582
|
+
},
|
|
4583
|
+
});
|
|
4584
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4585
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4586
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4587
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4588
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4589
|
+
|
|
4590
|
+
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
4591
|
+
|
|
4592
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4593
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4594
|
+
});
|
|
4595
|
+
|
|
4086
4596
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4087
4597
|
const method = new FirmwareUpdateV4({
|
|
4088
4598
|
id: 1,
|
|
@@ -4125,6 +4635,27 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4125
4635
|
});
|
|
4126
4636
|
});
|
|
4127
4637
|
|
|
4638
|
+
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
4639
|
+
const method = new FirmwareUpdateV4({
|
|
4640
|
+
id: 1,
|
|
4641
|
+
payload: {
|
|
4642
|
+
method: 'firmwareUpdateV4',
|
|
4643
|
+
},
|
|
4644
|
+
});
|
|
4645
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
4646
|
+
|
|
4647
|
+
(method as any).device = stubDevice({
|
|
4648
|
+
getCommands: () => ({ typedCall }),
|
|
4649
|
+
});
|
|
4650
|
+
|
|
4651
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Bootloader)).resolves.toEqual({
|
|
4652
|
+
message: 'Device rebooted successfully',
|
|
4653
|
+
});
|
|
4654
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
4655
|
+
DeviceRebootType.Bootloader
|
|
4656
|
+
);
|
|
4657
|
+
});
|
|
4658
|
+
|
|
4128
4659
|
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4129
4660
|
const method = new FirmwareUpdateV4({
|
|
4130
4661
|
id: 1,
|
|
@@ -4135,9 +4666,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4666
|
const typedCall = jest
|
|
4136
4667
|
.fn()
|
|
4137
4668
|
.mockRejectedValue(
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4669
|
+
new DOMException(
|
|
4670
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4671
|
+
'NotFoundError'
|
|
4141
4672
|
)
|
|
4142
4673
|
);
|
|
4143
4674
|
|
|
@@ -4215,7 +4746,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4215
4746
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4216
4747
|
]);
|
|
4217
4748
|
|
|
4218
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4749
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
4219
4750
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4220
4751
|
'DeviceFirmwareUpdateStatusGet',
|
|
4221
4752
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4253,6 +4784,46 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4253
4784
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4254
4785
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4255
4786
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4787
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4788
|
+
});
|
|
4789
|
+
|
|
4790
|
+
test('accepts an empty firmware status dump only after confirming normal mode', async () => {
|
|
4791
|
+
const method = new FirmwareUpdateV4({
|
|
4792
|
+
id: 1,
|
|
4793
|
+
payload: {
|
|
4794
|
+
method: 'firmwareUpdateV4',
|
|
4795
|
+
},
|
|
4796
|
+
});
|
|
4797
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4798
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4799
|
+
message: { records: [] },
|
|
4800
|
+
});
|
|
4801
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4802
|
+
let now = 0;
|
|
4803
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4804
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4805
|
+
now += 31 * 1000;
|
|
4806
|
+
callback();
|
|
4807
|
+
return 0 as any;
|
|
4808
|
+
}) as typeof setTimeout);
|
|
4809
|
+
|
|
4810
|
+
(method as any).device = stubDevice({
|
|
4811
|
+
getCommands: () => ({ typedCall }),
|
|
4812
|
+
});
|
|
4813
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4814
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4815
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4816
|
+
method.postProgressMessage = jest.fn();
|
|
4817
|
+
|
|
4818
|
+
await expect(
|
|
4819
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4820
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4821
|
+
])
|
|
4822
|
+
).resolves.toBeUndefined();
|
|
4823
|
+
|
|
4824
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4825
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4826
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4256
4827
|
});
|
|
4257
4828
|
|
|
4258
4829
|
test.each([
|
|
@@ -4318,77 +4889,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4889
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4890
|
});
|
|
4320
4891
|
|
|
4321
|
-
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
4322
|
-
const method = new FirmwareUpdateV4({
|
|
4323
|
-
id: 1,
|
|
4324
|
-
payload: {
|
|
4325
|
-
method: 'firmwareUpdateV4',
|
|
4326
|
-
platform: 'desktop',
|
|
4327
|
-
expectedTargetVersions: {
|
|
4328
|
-
app_v1: '2.0.0',
|
|
4329
|
-
},
|
|
4330
|
-
},
|
|
4331
|
-
});
|
|
4332
|
-
method.init();
|
|
4333
|
-
method.postProgressMessage = jest.fn();
|
|
4334
|
-
|
|
4335
|
-
expect(() =>
|
|
4336
|
-
(method as any).assertProtocolV2TargetStatus(
|
|
4337
|
-
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
4338
|
-
new Set([4])
|
|
4339
|
-
)
|
|
4340
|
-
).toThrow('expected 131072');
|
|
4341
|
-
});
|
|
4342
|
-
|
|
4343
|
-
test('does not fail a completed multi-app install when final app versions are unobservable', () => {
|
|
4344
|
-
const method = new FirmwareUpdateV4({
|
|
4345
|
-
id: 1,
|
|
4346
|
-
payload: {
|
|
4347
|
-
method: 'firmwareUpdateV4',
|
|
4348
|
-
platform: 'desktop',
|
|
4349
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4350
|
-
expectedTargetVersions: {
|
|
4351
|
-
app_v1: '1.0.0',
|
|
4352
|
-
app_v2: '2.0.0',
|
|
4353
|
-
},
|
|
4354
|
-
},
|
|
4355
|
-
});
|
|
4356
|
-
method.init();
|
|
4357
|
-
(method as any).protocolV2LatestFinalFeatures = {
|
|
4358
|
-
major_version: 3,
|
|
4359
|
-
minor_version: 0,
|
|
4360
|
-
patch_version: 0,
|
|
4361
|
-
};
|
|
4362
|
-
(method as any).protocolV2FinalStatusVerified = true;
|
|
4363
|
-
|
|
4364
|
-
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
4365
|
-
});
|
|
4366
|
-
|
|
4367
|
-
test('rejects unobservable final versions after the status endpoint fallback', () => {
|
|
4368
|
-
const method = new FirmwareUpdateV4({
|
|
4369
|
-
id: 1,
|
|
4370
|
-
payload: {
|
|
4371
|
-
method: 'firmwareUpdateV4',
|
|
4372
|
-
platform: 'desktop',
|
|
4373
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4374
|
-
expectedTargetVersions: {
|
|
4375
|
-
app_v1: '1.0.0',
|
|
4376
|
-
app_v2: '2.0.0',
|
|
4377
|
-
},
|
|
4378
|
-
},
|
|
4379
|
-
});
|
|
4380
|
-
method.init();
|
|
4381
|
-
(method as any).protocolV2LatestFinalFeatures = {
|
|
4382
|
-
major_version: 3,
|
|
4383
|
-
minor_version: 0,
|
|
4384
|
-
patch_version: 0,
|
|
4385
|
-
};
|
|
4386
|
-
|
|
4387
|
-
expect(() => (method as any).assertExpectedProtocolV2Versions()).toThrow(
|
|
4388
|
-
'has no observable final version after status fallback'
|
|
4389
|
-
);
|
|
4390
|
-
});
|
|
4391
|
-
|
|
4392
4892
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4393
4893
|
const method = new FirmwareUpdateV4({
|
|
4394
4894
|
id: 1,
|
|
@@ -4431,16 +4931,161 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4431
4931
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4432
4932
|
});
|
|
4433
4933
|
|
|
4434
|
-
test('
|
|
4934
|
+
test('fails clearly when a requested target stays absent from the full status dump', async () => {
|
|
4435
4935
|
const method = new FirmwareUpdateV4({
|
|
4436
4936
|
id: 1,
|
|
4437
4937
|
payload: {
|
|
4438
4938
|
method: 'firmwareUpdateV4',
|
|
4439
4939
|
},
|
|
4440
4940
|
});
|
|
4941
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4942
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4943
|
+
message: {
|
|
4944
|
+
records: [{ target_id: 4, status: 2 }],
|
|
4945
|
+
},
|
|
4946
|
+
});
|
|
4947
|
+
let now = 0;
|
|
4948
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4949
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4950
|
+
now += 31 * 1000;
|
|
4951
|
+
callback();
|
|
4952
|
+
return 0 as any;
|
|
4953
|
+
}) as typeof setTimeout);
|
|
4954
|
+
|
|
4955
|
+
(method as any).device = stubDevice({
|
|
4956
|
+
getCommands: () => ({ typedCall }),
|
|
4957
|
+
});
|
|
4958
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4959
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4441
4960
|
method.postProgressMessage = jest.fn();
|
|
4442
4961
|
|
|
4443
|
-
|
|
4962
|
+
await expect(
|
|
4963
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4964
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4965
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4966
|
+
])
|
|
4967
|
+
).rejects.toMatchObject({
|
|
4968
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
4969
|
+
message: expect.stringContaining('targetIds=3'),
|
|
4970
|
+
});
|
|
4971
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4972
|
+
});
|
|
4973
|
+
|
|
4974
|
+
test('resets the missing-record grace period after a Pro2 reboot interruption', async () => {
|
|
4975
|
+
const method = new FirmwareUpdateV4({
|
|
4976
|
+
id: 1,
|
|
4977
|
+
payload: {
|
|
4978
|
+
method: 'firmwareUpdateV4',
|
|
4979
|
+
},
|
|
4980
|
+
});
|
|
4981
|
+
const typedCall = jest
|
|
4982
|
+
.fn()
|
|
4983
|
+
.mockResolvedValueOnce({
|
|
4984
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4985
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
4986
|
+
})
|
|
4987
|
+
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
4988
|
+
.mockResolvedValueOnce({
|
|
4989
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4990
|
+
message: { records: [{ target_id: 4, status: 2 }] },
|
|
4991
|
+
})
|
|
4992
|
+
.mockResolvedValueOnce({
|
|
4993
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
4994
|
+
message: {
|
|
4995
|
+
records: [
|
|
4996
|
+
{ target_id: 3, status: 2 },
|
|
4997
|
+
{ target_id: 4, status: 2 },
|
|
4998
|
+
],
|
|
4999
|
+
},
|
|
5000
|
+
});
|
|
5001
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5002
|
+
let now = 0;
|
|
5003
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5004
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5005
|
+
now += 31 * 1000;
|
|
5006
|
+
callback();
|
|
5007
|
+
return 0 as any;
|
|
5008
|
+
}) as typeof setTimeout);
|
|
5009
|
+
|
|
5010
|
+
(method as any).device = stubDevice({
|
|
5011
|
+
getCommands: () => ({ typedCall }),
|
|
5012
|
+
});
|
|
5013
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5014
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5015
|
+
method.postProgressMessage = jest.fn();
|
|
5016
|
+
|
|
5017
|
+
await expect(
|
|
5018
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5019
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5020
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5021
|
+
])
|
|
5022
|
+
).resolves.toBeUndefined();
|
|
5023
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
5024
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
5025
|
+
});
|
|
5026
|
+
|
|
5027
|
+
test('uses an eight-minute Pro2 install status window', async () => {
|
|
5028
|
+
const method = new FirmwareUpdateV4({
|
|
5029
|
+
id: 1,
|
|
5030
|
+
payload: {
|
|
5031
|
+
method: 'firmwareUpdateV4',
|
|
5032
|
+
},
|
|
5033
|
+
});
|
|
5034
|
+
jest
|
|
5035
|
+
.spyOn(Date, 'now')
|
|
5036
|
+
.mockReturnValueOnce(0)
|
|
5037
|
+
.mockReturnValue(8 * 60 * 1000);
|
|
5038
|
+
|
|
5039
|
+
await expect(
|
|
5040
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5041
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5042
|
+
])
|
|
5043
|
+
).rejects.toThrow('within 480s');
|
|
5044
|
+
});
|
|
5045
|
+
|
|
5046
|
+
test('uses a 90-second Pro2 bootloader reconnect window', async () => {
|
|
5047
|
+
const method = new FirmwareUpdateV4({
|
|
5048
|
+
id: 1,
|
|
5049
|
+
payload: {
|
|
5050
|
+
method: 'firmwareUpdateV4',
|
|
5051
|
+
},
|
|
5052
|
+
});
|
|
5053
|
+
jest
|
|
5054
|
+
.spyOn(Date, 'now')
|
|
5055
|
+
.mockReturnValueOnce(0)
|
|
5056
|
+
.mockReturnValue(90 * 1000);
|
|
5057
|
+
|
|
5058
|
+
await expect((method as any).waitForProtocolV2BootloaderMode()).rejects.toThrow('within 90s');
|
|
5059
|
+
});
|
|
5060
|
+
|
|
5061
|
+
test('uses a three-minute Pro2 final reconnect window', async () => {
|
|
5062
|
+
const method = new FirmwareUpdateV4({
|
|
5063
|
+
id: 1,
|
|
5064
|
+
payload: {
|
|
5065
|
+
method: 'firmwareUpdateV4',
|
|
5066
|
+
},
|
|
5067
|
+
});
|
|
5068
|
+
const waitForProtocolV2ReconnectAndFeatures = jest
|
|
5069
|
+
.fn()
|
|
5070
|
+
.mockRejectedValue(new Error('stop after capturing timeout'));
|
|
5071
|
+
(method as any).waitForProtocolV2ReconnectAndFeatures = waitForProtocolV2ReconnectAndFeatures;
|
|
5072
|
+
|
|
5073
|
+
await expect((method as any).waitForProtocolV2FinalFeatures()).rejects.toThrow(
|
|
5074
|
+
'stop after capturing timeout'
|
|
5075
|
+
);
|
|
5076
|
+
expect(waitForProtocolV2ReconnectAndFeatures).toHaveBeenCalledWith(3 * 60 * 1000);
|
|
5077
|
+
});
|
|
5078
|
+
|
|
5079
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
5080
|
+
const method = new FirmwareUpdateV4({
|
|
5081
|
+
id: 1,
|
|
5082
|
+
payload: {
|
|
5083
|
+
method: 'firmwareUpdateV4',
|
|
5084
|
+
},
|
|
5085
|
+
});
|
|
5086
|
+
method.postProgressMessage = jest.fn();
|
|
5087
|
+
|
|
5088
|
+
const expectedTargetIds = new Set([4]);
|
|
4444
5089
|
|
|
4445
5090
|
expect(
|
|
4446
5091
|
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 2 }], expectedTargetIds)
|
|
@@ -4465,7 +5110,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4465
5110
|
).toBe(true);
|
|
4466
5111
|
|
|
4467
5112
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4468
|
-
expect(
|
|
5113
|
+
expect(
|
|
4469
5114
|
(method as any).assertProtocolV2TargetStatus(
|
|
4470
5115
|
[
|
|
4471
5116
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4473,8 +5118,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4473
5118
|
],
|
|
4474
5119
|
new Set([4, 10])
|
|
4475
5120
|
)
|
|
4476
|
-
).
|
|
4477
|
-
expect(method.postProgressMessage).
|
|
5121
|
+
).toBe(false);
|
|
5122
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4478
5123
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4479
5124
|
|
|
4480
5125
|
expect(
|
|
@@ -4563,7 +5208,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4563
5208
|
path: true,
|
|
4564
5209
|
},
|
|
4565
5210
|
},
|
|
4566
|
-
{ timeoutMs:
|
|
5211
|
+
{ timeoutMs: 15000 }
|
|
4567
5212
|
);
|
|
4568
5213
|
});
|
|
4569
5214
|
|
|
@@ -4578,14 +5223,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4578
5223
|
const writtenPaths: string[] = [];
|
|
4579
5224
|
method.postTipMessage = jest.fn();
|
|
4580
5225
|
method.postProgressMessage = jest.fn();
|
|
4581
|
-
(method as any).
|
|
5226
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4582
5227
|
writtenPaths.push(params.filePath);
|
|
4583
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
5228
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4584
5229
|
});
|
|
4585
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4586
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4587
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
4588
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4589
5230
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4590
5231
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4591
5232
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4625,12 +5266,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
5266
|
'vol0:/coprocessor.bin',
|
|
4626
5267
|
1
|
|
4627
5268
|
);
|
|
4628
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
4629
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4630
|
-
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4631
|
-
});
|
|
4632
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5269
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
5270
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4633
5271
|
targets: [
|
|
5272
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4634
5273
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4635
5274
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4636
5275
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4640,7 +5279,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4640
5279
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4641
5280
|
});
|
|
4642
5281
|
|
|
4643
|
-
test('announces
|
|
5282
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4644
5283
|
const method = new FirmwareUpdateV4({
|
|
4645
5284
|
id: 1,
|
|
4646
5285
|
payload: {
|
|
@@ -4650,14 +5289,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4650
5289
|
|
|
4651
5290
|
method.postTipMessage = jest.fn();
|
|
4652
5291
|
method.postProgressMessage = jest.fn();
|
|
4653
|
-
(method as any).
|
|
5292
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4654
5293
|
.fn()
|
|
4655
5294
|
.mockImplementation(params =>
|
|
4656
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5295
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4657
5296
|
);
|
|
4658
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4659
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4660
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4661
5297
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4662
5298
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4663
5299
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4677,22 +5313,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
5313
|
{
|
|
4678
5314
|
fileName: 'application_p1.bin',
|
|
4679
5315
|
binary: new Uint8Array([3]).buffer,
|
|
4680
|
-
targetId:
|
|
5316
|
+
targetId: 3,
|
|
4681
5317
|
},
|
|
4682
5318
|
],
|
|
4683
5319
|
});
|
|
4684
5320
|
|
|
4685
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
5321
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4686
5322
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4687
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4688
|
-
expect(method.
|
|
4689
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5323
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5324
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4690
5325
|
1,
|
|
4691
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
5326
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4692
5327
|
);
|
|
4693
|
-
expect((method as any).
|
|
5328
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4694
5329
|
2,
|
|
4695
|
-
expect.objectContaining({ processedSize:
|
|
5330
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4696
5331
|
);
|
|
4697
5332
|
});
|
|
4698
5333
|
|
|
@@ -4706,8 +5341,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4706
5341
|
|
|
4707
5342
|
method.postTipMessage = jest.fn();
|
|
4708
5343
|
method.postProgressMessage = jest.fn();
|
|
4709
|
-
(method as any).
|
|
4710
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5344
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4711
5345
|
(method as any).device = stubDevice({
|
|
4712
5346
|
getCommands: () => ({
|
|
4713
5347
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4753,14 +5387,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4753
5387
|
|
|
4754
5388
|
method.postTipMessage = jest.fn();
|
|
4755
5389
|
method.postProgressMessage = jest.fn();
|
|
4756
|
-
(method as any).
|
|
5390
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4757
5391
|
.fn()
|
|
4758
5392
|
.mockImplementation(params =>
|
|
4759
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5393
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4760
5394
|
);
|
|
4761
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4762
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4763
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4764
5395
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4765
5396
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4766
5397
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4975,33 +5606,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4975
5606
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4976
5607
|
});
|
|
4977
5608
|
|
|
4978
|
-
test('
|
|
5609
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
4979
5610
|
const method = new FirmwareUpdateV4({
|
|
4980
5611
|
id: 1,
|
|
4981
5612
|
payload: {
|
|
4982
5613
|
method: 'firmwareUpdateV4',
|
|
4983
5614
|
platform: 'web',
|
|
4984
|
-
targetsToUpdate: ['resource'],
|
|
4985
5615
|
},
|
|
4986
5616
|
});
|
|
4987
5617
|
method.init();
|
|
4988
5618
|
|
|
5619
|
+
const getSysResourceBinarySpy = jest
|
|
5620
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5621
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
4989
5622
|
const getFirmwareLatestReleaseSpy = jest
|
|
4990
5623
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4991
5624
|
.mockReturnValue({
|
|
4992
5625
|
required: false,
|
|
4993
5626
|
version: [1, 0, 0],
|
|
4994
|
-
url: '',
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
headerHash: 'cd'.repeat(64),
|
|
5627
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5628
|
+
bootloaderVersion: [1, 0, 0],
|
|
5629
|
+
upgradeType: 'payload-package-set',
|
|
5630
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5631
|
+
components: {
|
|
5632
|
+
bootloader: {
|
|
5633
|
+
target: 'BOOTLOADER',
|
|
5634
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5003
5635
|
},
|
|
5004
|
-
|
|
5636
|
+
applicationP1: {
|
|
5637
|
+
target: 'APPLICATION_P1',
|
|
5638
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5639
|
+
},
|
|
5640
|
+
applicationP2: {
|
|
5641
|
+
target: 'APPLICATION_P2',
|
|
5642
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
5643
|
+
},
|
|
5644
|
+
},
|
|
5005
5645
|
fingerprint: '',
|
|
5006
5646
|
changelog: {
|
|
5007
5647
|
'zh-CN': '',
|
|
@@ -5009,408 +5649,108 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5009
5649
|
},
|
|
5010
5650
|
});
|
|
5011
5651
|
|
|
5012
|
-
const
|
|
5652
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5013
5653
|
deviceType: 'pro2',
|
|
5014
5654
|
firmwareVersion: '1.0.0',
|
|
5655
|
+
bootloaderVersion: '1.0.0',
|
|
5015
5656
|
capabilities: [],
|
|
5016
5657
|
});
|
|
5017
5658
|
|
|
5018
|
-
expect(
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
version: [2, 0, 0],
|
|
5025
|
-
payloadHash: 'ab'.repeat(64),
|
|
5026
|
-
headerHash: 'cd'.repeat(64),
|
|
5027
|
-
},
|
|
5028
|
-
]);
|
|
5029
|
-
expect(bundles[0].binary.byteLength).toBe(0);
|
|
5659
|
+
expect(remoteBinaries).toEqual({
|
|
5660
|
+
bootloaderBinary: null,
|
|
5661
|
+
fwBinaryMap: [],
|
|
5662
|
+
installItems: [],
|
|
5663
|
+
});
|
|
5664
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5030
5665
|
|
|
5666
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5031
5667
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5032
5668
|
});
|
|
5033
5669
|
|
|
5034
|
-
test('
|
|
5670
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5035
5671
|
const method = new FirmwareUpdateV4({
|
|
5036
5672
|
id: 1,
|
|
5037
5673
|
payload: {
|
|
5038
5674
|
method: 'firmwareUpdateV4',
|
|
5039
5675
|
platform: 'web',
|
|
5040
|
-
targetsToUpdate: ['resource'],
|
|
5041
5676
|
},
|
|
5042
5677
|
});
|
|
5043
5678
|
method.init();
|
|
5044
5679
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5045
5680
|
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
resourceBundles: [
|
|
5054
|
-
{
|
|
5055
|
-
name: 'images',
|
|
5056
|
-
url: 'https://example.com/images.okpkg',
|
|
5057
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5058
|
-
},
|
|
5059
|
-
],
|
|
5060
|
-
fingerprint: '',
|
|
5061
|
-
changelog: {
|
|
5062
|
-
'zh-CN': '',
|
|
5063
|
-
'en-US': '',
|
|
5681
|
+
const remoteBinaries = {
|
|
5682
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5683
|
+
fwBinaryMap: [
|
|
5684
|
+
{
|
|
5685
|
+
fileName: 'application_p1.bin',
|
|
5686
|
+
binary: new Uint8Array([2]).buffer,
|
|
5687
|
+
targetId: 3,
|
|
5064
5688
|
},
|
|
5065
|
-
|
|
5689
|
+
],
|
|
5690
|
+
};
|
|
5066
5691
|
|
|
5067
5692
|
(method as any).device = stubDevice({
|
|
5068
5693
|
originalDescriptor: { protocolType: 'V2' },
|
|
5069
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5694
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5070
5695
|
});
|
|
5071
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5072
|
-
|
|
5073
|
-
|
|
5696
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5697
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5698
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5699
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5700
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5701
|
+
bootloaderVersion: '1.0.0',
|
|
5702
|
+
bleVersion: '0.0.0',
|
|
5703
|
+
firmwareVersion: '1.0.0',
|
|
5074
5704
|
});
|
|
5075
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5076
5705
|
method.postTipMessage = jest.fn();
|
|
5077
5706
|
|
|
5078
|
-
await
|
|
5079
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5707
|
+
await method.run();
|
|
5081
5708
|
|
|
5082
|
-
|
|
5083
|
-
|
|
5709
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5710
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5711
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5712
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5713
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5714
|
+
});
|
|
5084
5715
|
});
|
|
5085
5716
|
|
|
5086
|
-
test('
|
|
5717
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5087
5718
|
const method = new FirmwareUpdateV4({
|
|
5088
5719
|
id: 1,
|
|
5089
5720
|
payload: {
|
|
5090
5721
|
method: 'firmwareUpdateV4',
|
|
5722
|
+
platform: 'web',
|
|
5091
5723
|
},
|
|
5092
5724
|
});
|
|
5093
|
-
|
|
5094
|
-
(method as any).
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
],
|
|
5106
|
-
};
|
|
5107
|
-
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
-
.mockReturnValue({
|
|
5111
|
-
required: false,
|
|
5112
|
-
version: [1, 0, 0],
|
|
5113
|
-
url: '',
|
|
5114
|
-
resourceBundles: [
|
|
5115
|
-
{
|
|
5116
|
-
name: 'images',
|
|
5117
|
-
url: 'https://example.com/images.okpkg',
|
|
5118
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
-
},
|
|
5120
|
-
],
|
|
5121
|
-
fingerprint: '',
|
|
5122
|
-
changelog: {
|
|
5123
|
-
'zh-CN': '',
|
|
5124
|
-
'en-US': '',
|
|
5125
|
-
},
|
|
5126
|
-
});
|
|
5725
|
+
method.init();
|
|
5726
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5727
|
+
(method as any).device = stubDevice({
|
|
5728
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5729
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5730
|
+
});
|
|
5731
|
+
forceReloadDataSpy.mockRejectedValue(
|
|
5732
|
+
ERRORS.TypedError(HardwareErrorCode.NetworkError, 'latest config unavailable')
|
|
5733
|
+
);
|
|
5734
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5735
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5736
|
+
method.postTipMessage = jest.fn();
|
|
5127
5737
|
|
|
5128
|
-
await expect(
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
-
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5738
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
5739
|
+
errorCode: HardwareErrorCode.NetworkError,
|
|
5740
|
+
message: 'latest config unavailable',
|
|
5741
|
+
});
|
|
5134
5742
|
|
|
5135
|
-
|
|
5743
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5744
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5136
5745
|
});
|
|
5137
5746
|
|
|
5138
|
-
test('
|
|
5747
|
+
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5139
5748
|
const method = new FirmwareUpdateV4({
|
|
5140
5749
|
id: 1,
|
|
5141
5750
|
payload: {
|
|
5142
5751
|
method: 'firmwareUpdateV4',
|
|
5143
5752
|
platform: 'web',
|
|
5144
|
-
|
|
5145
|
-
},
|
|
5146
|
-
});
|
|
5147
|
-
method.init();
|
|
5148
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5149
|
-
|
|
5150
|
-
const order: string[] = [];
|
|
5151
|
-
const resourceBinary = buildOkppHeader().buffer as ArrayBuffer;
|
|
5152
|
-
const getSysResourceBinarySpy = jest
|
|
5153
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5154
|
-
.mockImplementation(() => {
|
|
5155
|
-
order.push('download');
|
|
5156
|
-
return Promise.resolve({ binary: resourceBinary });
|
|
5157
|
-
});
|
|
5158
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5159
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5160
|
-
.mockReturnValue({
|
|
5161
|
-
required: false,
|
|
5162
|
-
version: [1, 0, 0],
|
|
5163
|
-
url: '',
|
|
5164
|
-
resourceBundles: [
|
|
5165
|
-
{
|
|
5166
|
-
name: 'images',
|
|
5167
|
-
url: 'https://example.com/images.okpkg',
|
|
5168
|
-
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
5169
|
-
},
|
|
5170
|
-
],
|
|
5171
|
-
fingerprint: '',
|
|
5172
|
-
changelog: {
|
|
5173
|
-
'zh-CN': '',
|
|
5174
|
-
'en-US': '',
|
|
5175
|
-
},
|
|
5176
|
-
});
|
|
5177
|
-
|
|
5178
|
-
(method as any).device = stubDevice({
|
|
5179
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5180
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5181
|
-
});
|
|
5182
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5183
|
-
bootloaderBinary: null,
|
|
5184
|
-
fwBinaryMap: [],
|
|
5185
|
-
});
|
|
5186
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
5187
|
-
order.push('bootloader');
|
|
5188
|
-
return Promise.resolve(true);
|
|
5189
|
-
});
|
|
5190
|
-
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
5191
|
-
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
5192
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
5193
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5194
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5195
|
-
bootloaderVersion: '1.0.0',
|
|
5196
|
-
bleVersion: '0.0.0',
|
|
5197
|
-
firmwareVersion: '1.0.0',
|
|
5198
|
-
});
|
|
5199
|
-
method.postTipMessage = jest.fn();
|
|
5200
|
-
|
|
5201
|
-
await method.run();
|
|
5202
|
-
|
|
5203
|
-
expect(order).toEqual(['download', 'bootloader']);
|
|
5204
|
-
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
5205
|
-
bootloaderBinary: null,
|
|
5206
|
-
fwBinaryMap: [],
|
|
5207
|
-
resourceBundles: [
|
|
5208
|
-
{
|
|
5209
|
-
name: 'images',
|
|
5210
|
-
binary: resourceBinary,
|
|
5211
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5212
|
-
url: 'https://example.com/images.okpkg',
|
|
5213
|
-
},
|
|
5214
|
-
],
|
|
5215
|
-
});
|
|
5216
|
-
|
|
5217
|
-
executeProtocolV2UpdateSpy.mockRestore();
|
|
5218
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5219
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5220
|
-
});
|
|
5221
|
-
|
|
5222
|
-
test('rejects a remote RESC bundle without an OKPP header when metadata is absent', async () => {
|
|
5223
|
-
const method = new FirmwareUpdateV4({
|
|
5224
|
-
id: 1,
|
|
5225
|
-
payload: {
|
|
5226
|
-
method: 'firmwareUpdateV4',
|
|
5227
|
-
},
|
|
5228
|
-
});
|
|
5229
|
-
method.init();
|
|
5230
|
-
const getSysResourceBinarySpy = jest
|
|
5231
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5232
|
-
.mockResolvedValue({ binary: new Uint8Array([1, 2, 3]).buffer });
|
|
5233
|
-
|
|
5234
|
-
await expect(
|
|
5235
|
-
(method as any).materializeProtocolV2ResourceBundles([
|
|
5236
|
-
{
|
|
5237
|
-
name: 'images',
|
|
5238
|
-
binary: new ArrayBuffer(0),
|
|
5239
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5240
|
-
url: 'https://example.com/images.okpkg',
|
|
5241
|
-
},
|
|
5242
|
-
])
|
|
5243
|
-
).rejects.toThrow('Invalid Protocol V2 RESC bundle header: images');
|
|
5244
|
-
|
|
5245
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5246
|
-
});
|
|
5247
|
-
|
|
5248
|
-
test('rejects an empty remote RESC bundle before entering bootloader', async () => {
|
|
5249
|
-
const method = new FirmwareUpdateV4({
|
|
5250
|
-
id: 1,
|
|
5251
|
-
payload: {
|
|
5252
|
-
method: 'firmwareUpdateV4',
|
|
5253
|
-
platform: 'web',
|
|
5254
|
-
targetsToUpdate: ['resource'],
|
|
5255
|
-
},
|
|
5256
|
-
});
|
|
5257
|
-
method.init();
|
|
5258
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5259
|
-
|
|
5260
|
-
const getSysResourceBinarySpy = jest
|
|
5261
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5262
|
-
.mockResolvedValue({ binary: new ArrayBuffer(0) });
|
|
5263
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5264
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5265
|
-
.mockReturnValue({
|
|
5266
|
-
required: false,
|
|
5267
|
-
version: [1, 0, 0],
|
|
5268
|
-
url: '',
|
|
5269
|
-
resourceBundles: [
|
|
5270
|
-
{
|
|
5271
|
-
name: 'images',
|
|
5272
|
-
url: 'https://example.com/images.okpkg',
|
|
5273
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5274
|
-
},
|
|
5275
|
-
],
|
|
5276
|
-
fingerprint: '',
|
|
5277
|
-
changelog: {
|
|
5278
|
-
'zh-CN': '',
|
|
5279
|
-
'en-US': '',
|
|
5280
|
-
},
|
|
5281
|
-
});
|
|
5282
|
-
(method as any).device = stubDevice({
|
|
5283
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5284
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5285
|
-
});
|
|
5286
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5287
|
-
bootloaderBinary: null,
|
|
5288
|
-
fwBinaryMap: [],
|
|
5289
|
-
});
|
|
5290
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5291
|
-
method.postTipMessage = jest.fn();
|
|
5292
|
-
|
|
5293
|
-
await expect(method.run()).rejects.toThrow('Protocol V2 RESC bundle is empty: images');
|
|
5294
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5295
|
-
|
|
5296
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5297
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5298
|
-
});
|
|
5299
|
-
|
|
5300
|
-
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5301
|
-
const method = new FirmwareUpdateV4({
|
|
5302
|
-
id: 1,
|
|
5303
|
-
payload: {
|
|
5304
|
-
method: 'firmwareUpdateV4',
|
|
5305
|
-
platform: 'web',
|
|
5306
|
-
},
|
|
5307
|
-
});
|
|
5308
|
-
method.init();
|
|
5309
|
-
|
|
5310
|
-
const getSysResourceBinarySpy = jest
|
|
5311
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5312
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5313
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5314
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5315
|
-
.mockReturnValue({
|
|
5316
|
-
required: false,
|
|
5317
|
-
version: [1, 0, 0],
|
|
5318
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5319
|
-
bootloaderVersion: [1, 0, 0],
|
|
5320
|
-
upgradeType: 'payload-package-set',
|
|
5321
|
-
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5322
|
-
components: {
|
|
5323
|
-
bootloader: {
|
|
5324
|
-
target: 'BOOTLOADER',
|
|
5325
|
-
url: 'https://example.com/bootloader.pp.bin',
|
|
5326
|
-
},
|
|
5327
|
-
applicationP1: {
|
|
5328
|
-
target: 'APPLICATION_P1',
|
|
5329
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5330
|
-
},
|
|
5331
|
-
applicationP2: {
|
|
5332
|
-
target: 'APPLICATION_P2',
|
|
5333
|
-
url: 'https://example.com/applicationP2.pp.bin',
|
|
5334
|
-
},
|
|
5335
|
-
},
|
|
5336
|
-
fingerprint: '',
|
|
5337
|
-
changelog: {
|
|
5338
|
-
'zh-CN': '',
|
|
5339
|
-
'en-US': '',
|
|
5340
|
-
},
|
|
5341
|
-
});
|
|
5342
|
-
|
|
5343
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5344
|
-
deviceType: 'pro2',
|
|
5345
|
-
firmwareVersion: '1.0.0',
|
|
5346
|
-
bootloaderVersion: '1.0.0',
|
|
5347
|
-
capabilities: [],
|
|
5348
|
-
});
|
|
5349
|
-
|
|
5350
|
-
expect(remoteBinaries).toEqual({
|
|
5351
|
-
bootloaderBinary: null,
|
|
5352
|
-
fwBinaryMap: [],
|
|
5353
|
-
installItems: [],
|
|
5354
|
-
});
|
|
5355
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5356
|
-
|
|
5357
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5358
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5359
|
-
});
|
|
5360
|
-
|
|
5361
|
-
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5362
|
-
const method = new FirmwareUpdateV4({
|
|
5363
|
-
id: 1,
|
|
5364
|
-
payload: {
|
|
5365
|
-
method: 'firmwareUpdateV4',
|
|
5366
|
-
platform: 'web',
|
|
5367
|
-
},
|
|
5368
|
-
});
|
|
5369
|
-
method.init();
|
|
5370
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5371
|
-
|
|
5372
|
-
const remoteBinaries = {
|
|
5373
|
-
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5374
|
-
fwBinaryMap: [
|
|
5375
|
-
{
|
|
5376
|
-
fileName: 'application_p1.bin',
|
|
5377
|
-
binary: new Uint8Array([2]).buffer,
|
|
5378
|
-
targetId: 3,
|
|
5379
|
-
},
|
|
5380
|
-
],
|
|
5381
|
-
};
|
|
5382
|
-
|
|
5383
|
-
(method as any).device = stubDevice({
|
|
5384
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5385
|
-
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5386
|
-
});
|
|
5387
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5388
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5389
|
-
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5390
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5391
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5392
|
-
bootloaderVersion: '1.0.0',
|
|
5393
|
-
bleVersion: '0.0.0',
|
|
5394
|
-
firmwareVersion: '1.0.0',
|
|
5395
|
-
});
|
|
5396
|
-
method.postTipMessage = jest.fn();
|
|
5397
|
-
|
|
5398
|
-
await method.run();
|
|
5399
|
-
|
|
5400
|
-
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5401
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5402
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5403
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5404
|
-
});
|
|
5405
|
-
});
|
|
5406
|
-
|
|
5407
|
-
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5408
|
-
const method = new FirmwareUpdateV4({
|
|
5409
|
-
id: 1,
|
|
5410
|
-
payload: {
|
|
5411
|
-
method: 'firmwareUpdateV4',
|
|
5412
|
-
platform: 'web',
|
|
5413
|
-
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
5753
|
+
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
5414
5754
|
},
|
|
5415
5755
|
});
|
|
5416
5756
|
method.init();
|
|
@@ -5529,10 +5869,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5529
5869
|
|
|
5530
5870
|
method.postTipMessage = jest.fn();
|
|
5531
5871
|
method.postProgressMessage = jest.fn();
|
|
5532
|
-
(method as any).
|
|
5533
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5534
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5535
|
-
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5872
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5536
5873
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5537
5874
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5538
5875
|
|
|
@@ -5548,7 +5885,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5548
5885
|
fwBinaryMap: [],
|
|
5549
5886
|
});
|
|
5550
5887
|
|
|
5551
|
-
expect((method as any).
|
|
5888
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5552
5889
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5553
5890
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5554
5891
|
});
|
|
@@ -5579,19 +5916,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5579
5916
|
});
|
|
5580
5917
|
method.postProgressMessage = jest.fn();
|
|
5581
5918
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5919
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5920
|
+
payload: new Uint8Array(4097).buffer,
|
|
5921
|
+
filePath: 'vol1:firmware.bin',
|
|
5922
|
+
processedSize: 0,
|
|
5923
|
+
totalSize: 4097,
|
|
5584
5924
|
});
|
|
5585
|
-
try {
|
|
5586
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5587
|
-
source,
|
|
5588
|
-
filePath: 'vol1:firmware.bin',
|
|
5589
|
-
processedSize: 0,
|
|
5590
|
-
totalSize: 4097,
|
|
5591
|
-
});
|
|
5592
|
-
} finally {
|
|
5593
|
-
await source?.close();
|
|
5594
|
-
}
|
|
5595
5925
|
|
|
5596
5926
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5597
5927
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5648,16 +5978,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5648
5978
|
method.postProgressMessage = jest.fn();
|
|
5649
5979
|
|
|
5650
5980
|
try {
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
});
|
|
5654
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
-
source,
|
|
5981
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5982
|
+
payload: new Uint8Array(8001).buffer,
|
|
5656
5983
|
filePath: 'vol0:/firmware.bin',
|
|
5657
5984
|
processedSize: 0,
|
|
5658
5985
|
totalSize: 8001,
|
|
5659
5986
|
});
|
|
5660
|
-
await source?.close();
|
|
5661
5987
|
} finally {
|
|
5662
5988
|
setTimeoutSpy.mockRestore();
|
|
5663
5989
|
}
|
|
@@ -5692,21 +6018,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5692
6018
|
});
|
|
5693
6019
|
method.postProgressMessage = jest.fn();
|
|
5694
6020
|
method.postTipMessage = jest.fn();
|
|
5695
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5696
6021
|
|
|
5697
|
-
const source = await openFirmwareByteSource({
|
|
5698
|
-
binary: new Uint8Array(4097).buffer,
|
|
5699
|
-
});
|
|
5700
6022
|
await expect(
|
|
5701
|
-
(method as any).
|
|
5702
|
-
|
|
6023
|
+
(method as any).protocolV2WriteWholeFile({
|
|
6024
|
+
payload: new Uint8Array(4097).buffer,
|
|
5703
6025
|
filePath: 'vol0:/firmware.bin',
|
|
5704
6026
|
processedSize: 0,
|
|
5705
6027
|
totalSize: 4097,
|
|
5706
6028
|
})
|
|
5707
6029
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5708
|
-
|
|
5709
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6030
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5710
6031
|
});
|
|
5711
6032
|
|
|
5712
6033
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5739,19 +6060,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5739
6060
|
});
|
|
5740
6061
|
method.postProgressMessage = jest.fn();
|
|
5741
6062
|
|
|
5742
|
-
|
|
5743
|
-
|
|
6063
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6064
|
+
payload: new Uint8Array(1801).buffer,
|
|
6065
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
6066
|
+
processedSize: 0,
|
|
6067
|
+
totalSize: 1801,
|
|
5744
6068
|
});
|
|
5745
|
-
try {
|
|
5746
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5747
|
-
source,
|
|
5748
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
5749
|
-
processedSize: 0,
|
|
5750
|
-
totalSize: 1801,
|
|
5751
|
-
});
|
|
5752
|
-
} finally {
|
|
5753
|
-
await source?.close();
|
|
5754
|
-
}
|
|
5755
6069
|
|
|
5756
6070
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5757
6071
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -5868,9 +6182,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
6182
|
const typedCall = jest
|
|
5869
6183
|
.fn()
|
|
5870
6184
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6185
|
+
new DOMException(
|
|
6186
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6187
|
+
'NotFoundError'
|
|
5874
6188
|
)
|
|
5875
6189
|
);
|
|
5876
6190
|
|
|
@@ -6035,40 +6349,77 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
6349
|
});
|
|
6036
6350
|
|
|
6037
6351
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
-
});
|
|
6052
|
-
|
|
6053
|
-
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6054
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
6055
|
-
'identity mismatch'
|
|
6352
|
+
const createResourceFilesystemTypedCall = (
|
|
6353
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6354
|
+
missingTypes: string[] = []
|
|
6355
|
+
) => {
|
|
6356
|
+
const missing = new Set(missingTypes);
|
|
6357
|
+
const resourceByPath = new Map(
|
|
6358
|
+
stable.map(resource => [
|
|
6359
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
6360
|
+
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6361
|
+
],
|
|
6362
|
+
resource,
|
|
6363
|
+
])
|
|
6056
6364
|
);
|
|
6057
|
-
|
|
6365
|
+
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6366
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
6367
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6368
|
+
}
|
|
6369
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6370
|
+
const resource = resourceByPath.get(payload.path);
|
|
6371
|
+
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6372
|
+
return {
|
|
6373
|
+
message: {
|
|
6374
|
+
exist: exists,
|
|
6375
|
+
directory: false,
|
|
6376
|
+
size: exists ? resource?.size : 0,
|
|
6377
|
+
},
|
|
6378
|
+
};
|
|
6379
|
+
}
|
|
6380
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6381
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
6382
|
+
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6383
|
+
const header = new Uint8Array(0x52a0);
|
|
6384
|
+
const view = new DataView(header.buffer);
|
|
6385
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6386
|
+
header[index] = char.charCodeAt(0);
|
|
6387
|
+
});
|
|
6388
|
+
'RESC'.split('').forEach((char, index) => {
|
|
6389
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6390
|
+
});
|
|
6391
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6392
|
+
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6393
|
+
header[0x240 + index] = Number.parseInt(
|
|
6394
|
+
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6395
|
+
16
|
|
6396
|
+
);
|
|
6397
|
+
}
|
|
6398
|
+
const offset = Number(payload.file.offset);
|
|
6399
|
+
const chunkLength = Number(payload.chunk_len);
|
|
6400
|
+
return {
|
|
6401
|
+
message: {
|
|
6402
|
+
data: header.slice(offset, offset + chunkLength),
|
|
6403
|
+
},
|
|
6404
|
+
};
|
|
6405
|
+
}
|
|
6406
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6407
|
+
});
|
|
6408
|
+
};
|
|
6409
|
+
|
|
6410
|
+
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6411
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
6412
|
+
'identity mismatch'
|
|
6413
|
+
);
|
|
6414
|
+
expect(() =>
|
|
6058
6415
|
assertProtocolV2ReconnectIdentity('expected-serial', 'expected-serial')
|
|
6059
6416
|
).not.toThrow();
|
|
6060
6417
|
});
|
|
6061
6418
|
|
|
6062
|
-
test('
|
|
6063
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
6064
|
-
|
|
6065
|
-
);
|
|
6066
|
-
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).toThrow(
|
|
6067
|
-
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6068
|
-
);
|
|
6069
|
-
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
6070
|
-
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6071
|
-
);
|
|
6419
|
+
test('allows reconnect when either physical serial is unavailable', () => {
|
|
6420
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).not.toThrow();
|
|
6421
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).not.toThrow();
|
|
6422
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).not.toThrow();
|
|
6072
6423
|
});
|
|
6073
6424
|
|
|
6074
6425
|
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
@@ -6101,7 +6452,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6101
6452
|
);
|
|
6102
6453
|
});
|
|
6103
6454
|
|
|
6104
|
-
test('
|
|
6455
|
+
test('allows firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6105
6456
|
const method = new FirmwareUpdateV4({
|
|
6106
6457
|
id: 1,
|
|
6107
6458
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6114,39 +6465,209 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6114
6465
|
getCommands: () => ({ typedCall }),
|
|
6115
6466
|
});
|
|
6116
6467
|
|
|
6117
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6118
|
-
|
|
6119
|
-
});
|
|
6468
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).resolves.toBeUndefined();
|
|
6469
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
6120
6470
|
});
|
|
6121
6471
|
|
|
6122
|
-
test('uses
|
|
6472
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6123
6473
|
const method = new FirmwareUpdateV4({
|
|
6124
6474
|
id: 1,
|
|
6125
6475
|
payload: {
|
|
6126
6476
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
6477
|
+
platform: 'web',
|
|
6478
|
+
targetsToUpdate: ['resource'],
|
|
6129
6479
|
},
|
|
6130
6480
|
});
|
|
6131
6481
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
});
|
|
6482
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6483
|
+
(type, index) => ({
|
|
6484
|
+
type,
|
|
6485
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6486
|
+
size: 0x52a0 + index + 1,
|
|
6487
|
+
fileHash: 'a'.repeat(64),
|
|
6488
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6489
|
+
})
|
|
6490
|
+
);
|
|
6491
|
+
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
6143
6492
|
(method as any).device = stubDevice({
|
|
6144
6493
|
getCommands: () => ({ typedCall }),
|
|
6145
6494
|
});
|
|
6495
|
+
const resourcesSpy = jest
|
|
6496
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6497
|
+
.mockReturnValue(stable as any);
|
|
6498
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6499
|
+
|
|
6500
|
+
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
6501
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6502
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
6503
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
6504
|
+
resourcesSpy.mockRestore();
|
|
6505
|
+
});
|
|
6506
|
+
|
|
6507
|
+
test('downloads only changed resources from loader inventory', async () => {
|
|
6508
|
+
const method = new FirmwareUpdateV4({
|
|
6509
|
+
id: 1,
|
|
6510
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6511
|
+
});
|
|
6512
|
+
method.init();
|
|
6513
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6514
|
+
(type, index) => ({
|
|
6515
|
+
type,
|
|
6516
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6517
|
+
size: 0x52a0 + index + 1,
|
|
6518
|
+
fileHash: 'a'.repeat(64),
|
|
6519
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6520
|
+
})
|
|
6521
|
+
);
|
|
6522
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6523
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6524
|
+
const resourcesSpy = jest
|
|
6525
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6526
|
+
.mockReturnValue(stable as any);
|
|
6527
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6528
|
+
Promise.resolve({
|
|
6529
|
+
name: `${resource.type}.okpkg`,
|
|
6530
|
+
binary: new ArrayBuffer(resource.size),
|
|
6531
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6532
|
+
})
|
|
6533
|
+
);
|
|
6534
|
+
|
|
6535
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6536
|
+
|
|
6537
|
+
expect(bundles).toHaveLength(1);
|
|
6538
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6539
|
+
resourcesSpy.mockRestore();
|
|
6540
|
+
});
|
|
6541
|
+
|
|
6542
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
6543
|
+
const method = new FirmwareUpdateV4({
|
|
6544
|
+
id: 1,
|
|
6545
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6546
|
+
});
|
|
6547
|
+
method.init();
|
|
6548
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6549
|
+
(type, index) => ({
|
|
6550
|
+
type,
|
|
6551
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6552
|
+
size: 0x52a0 + index + 1,
|
|
6553
|
+
fileHash: 'a'.repeat(64),
|
|
6554
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6555
|
+
})
|
|
6556
|
+
);
|
|
6557
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6558
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6559
|
+
const resourcesSpy = jest
|
|
6560
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6561
|
+
.mockReturnValue(stable as any);
|
|
6562
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6563
|
+
Promise.resolve({
|
|
6564
|
+
name: `${resource.type}.okpkg`,
|
|
6565
|
+
binary: new ArrayBuffer(resource.size),
|
|
6566
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6567
|
+
})
|
|
6568
|
+
);
|
|
6569
|
+
|
|
6570
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6571
|
+
|
|
6572
|
+
expect(bundles).toHaveLength(1);
|
|
6573
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6574
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
6575
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6576
|
+
resourcesSpy.mockRestore();
|
|
6577
|
+
});
|
|
6578
|
+
|
|
6579
|
+
const buildBootResourcesHeader = ({
|
|
6580
|
+
type = 'CRAT',
|
|
6581
|
+
payloadHash = 'ab'.repeat(64),
|
|
6582
|
+
headerHash = 'cd'.repeat(64),
|
|
6583
|
+
}: {
|
|
6584
|
+
type?: string;
|
|
6585
|
+
payloadHash?: string;
|
|
6586
|
+
headerHash?: string;
|
|
6587
|
+
} = {}) => {
|
|
6588
|
+
const header = new Uint8Array(0x52a0);
|
|
6589
|
+
const view = new DataView(header.buffer);
|
|
6590
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6591
|
+
header[index] = char.charCodeAt(0);
|
|
6592
|
+
});
|
|
6593
|
+
type.split('').forEach((char, index) => {
|
|
6594
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6595
|
+
});
|
|
6596
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6597
|
+
const writeHex = (offset: number, hex: string) => {
|
|
6598
|
+
for (let index = 0; index < hex.length / 2; index++) {
|
|
6599
|
+
header[offset + index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
6600
|
+
}
|
|
6601
|
+
};
|
|
6602
|
+
writeHex(0x200, payloadHash);
|
|
6603
|
+
writeHex(0x240, headerHash);
|
|
6604
|
+
return header;
|
|
6605
|
+
};
|
|
6606
|
+
|
|
6607
|
+
test('does not resolve boot resources unless the optional target is selected', async () => {
|
|
6608
|
+
const method = new FirmwareUpdateV4({
|
|
6609
|
+
id: 1,
|
|
6610
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
6611
|
+
});
|
|
6612
|
+
method.init();
|
|
6613
|
+
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6614
|
+
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
6615
|
+
|
|
6616
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
6617
|
+
|
|
6618
|
+
expect(configSpy).not.toHaveBeenCalled();
|
|
6619
|
+
expect(downloadSpy).not.toHaveBeenCalled();
|
|
6620
|
+
});
|
|
6621
|
+
|
|
6622
|
+
test('downloads and maps the selected boot resources CRATE target', async () => {
|
|
6623
|
+
const payloadHash = 'ab'.repeat(64);
|
|
6624
|
+
const headerHash = 'cd'.repeat(64);
|
|
6625
|
+
const bytes = buildBootResourcesHeader({ payloadHash, headerHash });
|
|
6626
|
+
const binary = bytes.buffer as ArrayBuffer;
|
|
6627
|
+
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
6628
|
+
const resource = {
|
|
6629
|
+
required: false as const,
|
|
6630
|
+
target: 'CRATE' as const,
|
|
6631
|
+
url: 'https://example.com/boot-resources.crate.okpkg',
|
|
6632
|
+
size: bytes.byteLength,
|
|
6633
|
+
fileHash,
|
|
6634
|
+
payloadHash,
|
|
6635
|
+
headerHash,
|
|
6636
|
+
};
|
|
6637
|
+
const method = new FirmwareUpdateV4({
|
|
6638
|
+
id: 1,
|
|
6639
|
+
payload: {
|
|
6640
|
+
method: 'firmwareUpdateV4',
|
|
6641
|
+
platform: 'web',
|
|
6642
|
+
targetsToUpdate: ['boot_resources'],
|
|
6643
|
+
},
|
|
6644
|
+
});
|
|
6645
|
+
method.init();
|
|
6646
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
6647
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
6146
6648
|
|
|
6147
|
-
await expect((method as any).
|
|
6148
|
-
|
|
6149
|
-
|
|
6649
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual({
|
|
6650
|
+
fileName: 'boot_resources.crate.okpkg',
|
|
6651
|
+
binary,
|
|
6652
|
+
targetId: 1,
|
|
6653
|
+
kind: 'boot_resources',
|
|
6654
|
+
});
|
|
6655
|
+
});
|
|
6656
|
+
|
|
6657
|
+
test('rejects a non-CRATE manual boot resources package', async () => {
|
|
6658
|
+
const method = new FirmwareUpdateV4({
|
|
6659
|
+
id: 1,
|
|
6660
|
+
payload: {
|
|
6661
|
+
method: 'firmwareUpdateV4',
|
|
6662
|
+
platform: 'web',
|
|
6663
|
+
bootResourcesBinary: buildBootResourcesHeader({ type: 'RESC' }).buffer,
|
|
6664
|
+
},
|
|
6665
|
+
});
|
|
6666
|
+
method.init();
|
|
6667
|
+
|
|
6668
|
+
await expect((method as any).prepareProtocolV2BootResources()).rejects.toThrow(
|
|
6669
|
+
'Invalid Pro2 boot resources CRATE header'
|
|
6670
|
+
);
|
|
6150
6671
|
});
|
|
6151
6672
|
});
|
|
6152
6673
|
|
|
@@ -6286,311 +6807,230 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6286
6807
|
expect(method.unlockPolicy).toBe('none');
|
|
6287
6808
|
});
|
|
6288
6809
|
|
|
6289
|
-
test('
|
|
6810
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6290
6811
|
const calls: string[] = [];
|
|
6812
|
+
const features = { unlocked: true };
|
|
6291
6813
|
const method = {
|
|
6292
|
-
name: '
|
|
6293
|
-
unlockPolicy: '
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
.
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
})
|
|
6301
|
-
.mockImplementationOnce(() => {
|
|
6302
|
-
calls.push('run-2');
|
|
6303
|
-
return Promise.resolve({ message: 'ok' });
|
|
6304
|
-
}),
|
|
6814
|
+
name: 'confluxSignMessageCIP23',
|
|
6815
|
+
unlockPolicy: 'none',
|
|
6816
|
+
useDevicePassphraseState: true,
|
|
6817
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6818
|
+
run: jest.fn(() => {
|
|
6819
|
+
calls.push('run');
|
|
6820
|
+
return Promise.resolve({ address: '0x1' });
|
|
6821
|
+
}),
|
|
6305
6822
|
};
|
|
6306
6823
|
const device = {
|
|
6824
|
+
features,
|
|
6825
|
+
commands: {
|
|
6826
|
+
typedCall: jest.fn(() => {
|
|
6827
|
+
calls.push('status');
|
|
6828
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6829
|
+
}),
|
|
6830
|
+
},
|
|
6307
6831
|
isProtocolV2: () => true,
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6832
|
+
isBootloader: () => false,
|
|
6833
|
+
isRomloader: () => false,
|
|
6834
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6835
|
+
features.unlocked = status.unlocked === true;
|
|
6836
|
+
return features;
|
|
6311
6837
|
}),
|
|
6838
|
+
unlockDevice: jest.fn(),
|
|
6312
6839
|
};
|
|
6313
6840
|
const uiCoordinator = {
|
|
6314
6841
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6842
|
+
enterUnlockInteraction: jest.fn(),
|
|
6317
6843
|
};
|
|
6318
6844
|
|
|
6319
6845
|
await expect(
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
]);
|
|
6846
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6847
|
+
uiCoordinator: uiCoordinator as any,
|
|
6848
|
+
})
|
|
6849
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6850
|
+
|
|
6851
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6852
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6853
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6854
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6330
6855
|
});
|
|
6331
6856
|
|
|
6332
|
-
test('
|
|
6857
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6333
6858
|
const calls: string[] = [];
|
|
6859
|
+
const identityError = new Error('Unexpected device');
|
|
6334
6860
|
const method = {
|
|
6335
6861
|
name: 'evmSignMessage',
|
|
6336
|
-
|
|
6862
|
+
unlockPolicy: 'none',
|
|
6337
6863
|
useDevicePassphraseState: true,
|
|
6338
|
-
|
|
6339
|
-
run: jest
|
|
6340
|
-
.fn()
|
|
6341
|
-
.mockImplementationOnce(() => {
|
|
6342
|
-
calls.push('run-1');
|
|
6343
|
-
return Promise.reject(deviceLockedError());
|
|
6344
|
-
})
|
|
6345
|
-
.mockImplementationOnce(() => {
|
|
6346
|
-
calls.push('run-2');
|
|
6347
|
-
return Promise.resolve({ message: 'ok' });
|
|
6348
|
-
}),
|
|
6864
|
+
run: jest.fn(),
|
|
6349
6865
|
};
|
|
6350
|
-
const
|
|
6351
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6352
|
-
calls.push('negotiate-session');
|
|
6353
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6354
|
-
}
|
|
6355
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6356
|
-
calls.push('resume-hidden-session');
|
|
6357
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6358
|
-
return Promise.resolve({
|
|
6359
|
-
message: {
|
|
6360
|
-
session_id: 'hidden-session',
|
|
6361
|
-
btc_test_address: 'hidden-state',
|
|
6362
|
-
},
|
|
6363
|
-
});
|
|
6364
|
-
}
|
|
6365
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6366
|
-
});
|
|
6866
|
+
const features = { unlocked: false };
|
|
6367
6867
|
const device = {
|
|
6368
|
-
features
|
|
6369
|
-
|
|
6370
|
-
|
|
6868
|
+
features,
|
|
6869
|
+
commands: {
|
|
6870
|
+
typedCall: jest.fn(() => {
|
|
6871
|
+
calls.push('status');
|
|
6872
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6873
|
+
}),
|
|
6874
|
+
},
|
|
6371
6875
|
isProtocolV2: () => true,
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
getCurrentPassphraseProtection: () => true,
|
|
6377
|
-
getInternalState: () => 'hidden-session',
|
|
6378
|
-
clearInternalState: jest.fn(),
|
|
6379
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6380
|
-
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6876
|
+
isBootloader: () => false,
|
|
6877
|
+
isRomloader: () => false,
|
|
6878
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6879
|
+
unlockDevice: jest.fn(),
|
|
6381
6880
|
};
|
|
6382
6881
|
|
|
6383
|
-
await expect(
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
'validate-hidden-session',
|
|
6392
|
-
'run-2',
|
|
6393
|
-
]);
|
|
6394
|
-
});
|
|
6395
|
-
|
|
6396
|
-
test('does not replay a business method when the hidden-wallet session cannot be restored after unlock', async () => {
|
|
6397
|
-
const initialError = deviceLockedError();
|
|
6398
|
-
const restoreError = new Error('Invalid wallet session');
|
|
6399
|
-
const method = {
|
|
6400
|
-
name: 'evmSignMessage',
|
|
6401
|
-
payload: { passphraseState: 'hidden-state' },
|
|
6402
|
-
useDevicePassphraseState: true,
|
|
6403
|
-
unlockPolicy: 'retry-on-locked',
|
|
6404
|
-
run: jest.fn().mockRejectedValueOnce(initialError),
|
|
6405
|
-
};
|
|
6406
|
-
const typedCall = jest.fn((requestType: string) => {
|
|
6407
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6408
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6409
|
-
}
|
|
6410
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6411
|
-
return Promise.reject(restoreError);
|
|
6412
|
-
}
|
|
6413
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6414
|
-
});
|
|
6415
|
-
const device = {
|
|
6416
|
-
features: { unlocked: true, passphraseProtection: true },
|
|
6417
|
-
passphraseState: 'hidden-state',
|
|
6418
|
-
commands: { typedCall },
|
|
6419
|
-
isProtocolV2: () => true,
|
|
6420
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6421
|
-
getCurrentPassphraseProtection: () => true,
|
|
6422
|
-
getInternalState: () => 'hidden-session',
|
|
6423
|
-
clearInternalState: jest.fn(),
|
|
6424
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6425
|
-
updateInternalState: jest.fn(),
|
|
6426
|
-
};
|
|
6882
|
+
await expect(
|
|
6883
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6884
|
+
afterStatusBeforeUnlock: () => {
|
|
6885
|
+
calls.push('identity');
|
|
6886
|
+
throw identityError;
|
|
6887
|
+
},
|
|
6888
|
+
})
|
|
6889
|
+
).rejects.toBe(identityError);
|
|
6427
6890
|
|
|
6428
|
-
|
|
6429
|
-
expect(
|
|
6430
|
-
expect(
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6891
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6892
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6893
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6433
6894
|
});
|
|
6434
6895
|
|
|
6435
|
-
test('
|
|
6896
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6436
6897
|
const calls: string[] = [];
|
|
6898
|
+
const features = { unlocked: true };
|
|
6437
6899
|
const method = {
|
|
6438
6900
|
name: 'evmSignMessage',
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
unlockPolicy: 'retry-on-locked',
|
|
6901
|
+
unlockPolicy: 'unlock-before-run',
|
|
6902
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6442
6903
|
run: jest.fn(() => {
|
|
6443
6904
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6905
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6445
6906
|
}),
|
|
6446
6907
|
};
|
|
6447
|
-
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6448
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6449
|
-
calls.push('negotiate-session');
|
|
6450
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6451
|
-
}
|
|
6452
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6453
|
-
calls.push('resume-hidden-session');
|
|
6454
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6455
|
-
return Promise.resolve({
|
|
6456
|
-
message: {
|
|
6457
|
-
session_id: 'hidden-session',
|
|
6458
|
-
btc_test_address: 'hidden-state',
|
|
6459
|
-
},
|
|
6460
|
-
});
|
|
6461
|
-
}
|
|
6462
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6463
|
-
});
|
|
6464
|
-
const features = {
|
|
6465
|
-
unlocked: false,
|
|
6466
|
-
unlockedAttachPin: true,
|
|
6467
|
-
passphraseProtection: true,
|
|
6468
|
-
};
|
|
6469
6908
|
const device = {
|
|
6470
6909
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6910
|
+
commands: {
|
|
6911
|
+
typedCall: jest.fn(() => {
|
|
6912
|
+
calls.push('status');
|
|
6913
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6914
|
+
}),
|
|
6915
|
+
},
|
|
6473
6916
|
isProtocolV2: () => true,
|
|
6917
|
+
isBootloader: () => false,
|
|
6918
|
+
isRomloader: () => false,
|
|
6919
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6920
|
+
features.unlocked = status.unlocked === true;
|
|
6921
|
+
return features;
|
|
6922
|
+
}),
|
|
6474
6923
|
unlockDevice: jest.fn(() => {
|
|
6475
|
-
calls.push('unlock
|
|
6924
|
+
calls.push('unlock');
|
|
6476
6925
|
features.unlocked = true;
|
|
6477
|
-
features
|
|
6478
|
-
|
|
6926
|
+
return Promise.resolve(features);
|
|
6927
|
+
}),
|
|
6928
|
+
};
|
|
6929
|
+
const uiCoordinator = {
|
|
6930
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6931
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
6932
|
+
calls.push('unlock-prompt');
|
|
6933
|
+
return undefined;
|
|
6479
6934
|
}),
|
|
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
6935
|
};
|
|
6486
6936
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6937
|
+
await expect(
|
|
6938
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6939
|
+
uiCoordinator: uiCoordinator as any,
|
|
6940
|
+
prepare: () => {
|
|
6941
|
+
calls.push('wallet-session');
|
|
6942
|
+
return Promise.resolve();
|
|
6943
|
+
},
|
|
6944
|
+
})
|
|
6945
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
6946
|
+
|
|
6491
6947
|
expect(calls).toEqual([
|
|
6492
|
-
'
|
|
6493
|
-
'
|
|
6494
|
-
'
|
|
6495
|
-
'
|
|
6948
|
+
'status',
|
|
6949
|
+
'unlock-prompt',
|
|
6950
|
+
'unlock',
|
|
6951
|
+
'wallet-session',
|
|
6952
|
+
'method-prompt',
|
|
6496
6953
|
'run',
|
|
6497
6954
|
]);
|
|
6955
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6956
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6957
|
});
|
|
6499
6958
|
|
|
6500
|
-
test('
|
|
6959
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6501
6960
|
const method = {
|
|
6502
6961
|
name: 'evmGetAddress',
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
unlockPolicy: 'retry-on-locked',
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6962
|
+
unlockPolicy: 'unlock-before-run',
|
|
6963
|
+
run: jest.fn(),
|
|
6507
6964
|
};
|
|
6508
6965
|
const device = {
|
|
6509
|
-
features: { unlocked:
|
|
6510
|
-
|
|
6966
|
+
features: { unlocked: undefined },
|
|
6967
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6511
6968
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6969
|
+
isBootloader: () => false,
|
|
6970
|
+
isRomloader: () => false,
|
|
6971
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6972
|
+
unlockDevice: jest.fn(),
|
|
6513
6973
|
};
|
|
6514
6974
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6975
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6976
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6517
6977
|
});
|
|
6518
|
-
expect(device.unlockDevice).
|
|
6519
|
-
expect(method.run).
|
|
6978
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6979
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6980
|
});
|
|
6521
6981
|
|
|
6522
|
-
test('
|
|
6523
|
-
const
|
|
6982
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6983
|
+
const error = deviceLockedError();
|
|
6524
6984
|
const method = {
|
|
6525
|
-
name: '
|
|
6985
|
+
name: 'evmSignMessage',
|
|
6526
6986
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
|
|
6528
|
-
run: jest.fn(() => {
|
|
6529
|
-
calls.push('run');
|
|
6530
|
-
return Promise.resolve({ message: 'ok' });
|
|
6531
|
-
}),
|
|
6987
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6532
6988
|
};
|
|
6533
6989
|
const features = { unlocked: true };
|
|
6534
6990
|
const device = {
|
|
6535
6991
|
features,
|
|
6536
|
-
commands: {
|
|
6537
|
-
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('refresh-status');
|
|
6539
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
-
}),
|
|
6541
|
-
},
|
|
6992
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6542
6993
|
isProtocolV2: () => true,
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
unlockDevice: jest.fn(() => {
|
|
6548
|
-
calls.push('unlock');
|
|
6549
|
-
features.unlocked = true;
|
|
6550
|
-
return Promise.resolve();
|
|
6551
|
-
}),
|
|
6552
|
-
};
|
|
6553
|
-
const uiCoordinator = {
|
|
6554
|
-
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6555
|
-
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6556
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6994
|
+
isBootloader: () => false,
|
|
6995
|
+
isRomloader: () => false,
|
|
6996
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6997
|
+
unlockDevice: jest.fn(),
|
|
6557
6998
|
};
|
|
6558
6999
|
|
|
6559
|
-
await expect(
|
|
6560
|
-
|
|
6561
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6562
|
-
expect(calls).toEqual(['refresh-status', 'unlock-prompt', 'unlock', 'method-prompt', 'run']);
|
|
6563
|
-
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
7000
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
7001
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6564
7002
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
7003
|
});
|
|
6568
7004
|
|
|
6569
|
-
test('
|
|
6570
|
-
const features = { unlocked:
|
|
6571
|
-
const
|
|
6572
|
-
|
|
7005
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
7006
|
+
const features = { unlocked: true };
|
|
7007
|
+
const context = createProtocolV2UnlockContext();
|
|
7008
|
+
const firstMethod = {
|
|
7009
|
+
name: 'allNetworkGetAddress',
|
|
6573
7010
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().mockResolvedValue({
|
|
7011
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
7012
|
+
};
|
|
7013
|
+
const nestedMethod = {
|
|
7014
|
+
name: 'evmGetAddress',
|
|
7015
|
+
unlockPolicy: 'unlock-before-run',
|
|
7016
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
6575
7017
|
};
|
|
6576
7018
|
const device = {
|
|
6577
7019
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
7020
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
7021
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
7022
|
+
isBootloader: () => false,
|
|
7023
|
+
isRomloader: () => false,
|
|
7024
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
7025
|
unlockDevice: jest.fn(),
|
|
6587
7026
|
};
|
|
6588
7027
|
|
|
6589
|
-
await
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
expect(device.
|
|
6593
|
-
expect(
|
|
7028
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
7029
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
7030
|
+
|
|
7031
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
7032
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
7033
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
6594
7034
|
});
|
|
6595
7035
|
|
|
6596
7036
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6610,7 +7050,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
7050
|
unlockDevice: jest.fn(),
|
|
6611
7051
|
};
|
|
6612
7052
|
|
|
6613
|
-
await expect(
|
|
7053
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
7054
|
message: 'updating',
|
|
6615
7055
|
});
|
|
6616
7056
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +7059,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
7059
|
}
|
|
6620
7060
|
);
|
|
6621
7061
|
|
|
6622
|
-
test('
|
|
6623
|
-
const method = {
|
|
6624
|
-
name: 'evmSignMessage',
|
|
6625
|
-
params: { message: 'not-exposed-in-event' },
|
|
6626
|
-
payload: {},
|
|
6627
|
-
unlockPolicy: 'retry-on-locked',
|
|
6628
|
-
run: jest.fn().mockResolvedValue({ signature: 'test' }),
|
|
6629
|
-
};
|
|
6630
|
-
const device = {
|
|
6631
|
-
isProtocolV2: () => true,
|
|
6632
|
-
unlockDevice: jest.fn(),
|
|
6633
|
-
};
|
|
6634
|
-
const uiCoordinator = {
|
|
6635
|
-
enterMethodInteraction: jest.fn(),
|
|
6636
|
-
enterUnlockInteraction: jest.fn(),
|
|
6637
|
-
resumeMethodInteraction: jest.fn(),
|
|
6638
|
-
};
|
|
6639
|
-
|
|
6640
|
-
await runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any);
|
|
6641
|
-
|
|
6642
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledWith({
|
|
6643
|
-
request: 'button',
|
|
6644
|
-
source: 'method-lifecycle',
|
|
6645
|
-
reason: 'signing-confirmation',
|
|
6646
|
-
completion: 'operation-completed',
|
|
6647
|
-
deviceOnly: true,
|
|
6648
|
-
operation: 'evmSignMessage',
|
|
6649
|
-
});
|
|
6650
|
-
});
|
|
6651
|
-
|
|
6652
|
-
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
7062
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
7063
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
7064
|
+
[false, 'unlock-before-run'],
|
|
6655
7065
|
[true, 'none'],
|
|
6656
7066
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
7067
|
const method = {
|
|
6659
7068
|
unlockPolicy,
|
|
6660
|
-
|
|
7069
|
+
useDevicePassphraseState: false,
|
|
7070
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
7071
|
};
|
|
6662
7072
|
const device = {
|
|
7073
|
+
commands: { typedCall: jest.fn() },
|
|
6663
7074
|
isProtocolV2: () => isProtocolV2,
|
|
6664
7075
|
unlockDevice: jest.fn(),
|
|
6665
7076
|
};
|
|
6666
7077
|
|
|
6667
|
-
await expect(
|
|
7078
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
7079
|
+
message: 'ok',
|
|
7080
|
+
});
|
|
7081
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
7082
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
7083
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
7084
|
}
|
|
6671
7085
|
});
|
|
6672
|
-
|
|
6673
|
-
test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
|
|
6674
|
-
const method = {
|
|
6675
|
-
name: 'uploadPortfolio',
|
|
6676
|
-
unlockPolicy: 'none',
|
|
6677
|
-
protocolV2UiMode: 'none',
|
|
6678
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6679
|
-
};
|
|
6680
|
-
const device = {
|
|
6681
|
-
features: { unlocked: false },
|
|
6682
|
-
isProtocolV2: () => true,
|
|
6683
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6684
|
-
};
|
|
6685
|
-
const uiCoordinator = {
|
|
6686
|
-
enterMethodInteraction: jest.fn(),
|
|
6687
|
-
enterUnlockInteraction: jest.fn(),
|
|
6688
|
-
resumeMethodInteraction: jest.fn(),
|
|
6689
|
-
};
|
|
6690
|
-
|
|
6691
|
-
await expect(
|
|
6692
|
-
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6693
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6694
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6695
|
-
expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
|
|
6696
|
-
expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
|
|
6697
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6698
|
-
});
|
|
6699
|
-
|
|
6700
|
-
test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
|
|
6701
|
-
const error = deviceLockedError();
|
|
6702
|
-
const method = {
|
|
6703
|
-
name: 'uploadPortfolio',
|
|
6704
|
-
unlockPolicy: 'none',
|
|
6705
|
-
run: jest.fn().mockRejectedValue(error),
|
|
6706
|
-
};
|
|
6707
|
-
const device = {
|
|
6708
|
-
features: { unlocked: true },
|
|
6709
|
-
isProtocolV2: () => true,
|
|
6710
|
-
unlockDevice: jest.fn(),
|
|
6711
|
-
};
|
|
6712
|
-
|
|
6713
|
-
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6714
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6715
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6716
|
-
});
|
|
6717
|
-
|
|
6718
|
-
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
6719
|
-
const initialError = deviceLockedError();
|
|
6720
|
-
const unlockError = new Error('PIN cancelled');
|
|
6721
|
-
const unlockFailMethod = {
|
|
6722
|
-
unlockPolicy: 'retry-on-locked',
|
|
6723
|
-
run: jest.fn().mockRejectedValue(initialError),
|
|
6724
|
-
};
|
|
6725
|
-
const unlockFailDevice = {
|
|
6726
|
-
isProtocolV2: () => true,
|
|
6727
|
-
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
6728
|
-
};
|
|
6729
|
-
const unlockFailCoordinator = {
|
|
6730
|
-
enterMethodInteraction: jest.fn(),
|
|
6731
|
-
enterUnlockInteraction: jest.fn(),
|
|
6732
|
-
resumeMethodInteraction: jest.fn(),
|
|
6733
|
-
};
|
|
6734
|
-
|
|
6735
|
-
await expect(
|
|
6736
|
-
runMethodWithUnlockRetry(
|
|
6737
|
-
unlockFailMethod as any,
|
|
6738
|
-
unlockFailDevice as any,
|
|
6739
|
-
unlockFailCoordinator as any
|
|
6740
|
-
)
|
|
6741
|
-
).rejects.toBe(unlockError);
|
|
6742
|
-
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
6743
|
-
expect(unlockFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6744
|
-
expect(unlockFailCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6745
|
-
|
|
6746
|
-
const retryError = deviceLockedError();
|
|
6747
|
-
const retryFailMethod = {
|
|
6748
|
-
unlockPolicy: 'retry-on-locked',
|
|
6749
|
-
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
6750
|
-
};
|
|
6751
|
-
const retryFailDevice = {
|
|
6752
|
-
isProtocolV2: () => true,
|
|
6753
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6754
|
-
};
|
|
6755
|
-
const retryFailCoordinator = {
|
|
6756
|
-
enterMethodInteraction: jest.fn(),
|
|
6757
|
-
enterUnlockInteraction: jest.fn(),
|
|
6758
|
-
resumeMethodInteraction: jest.fn(),
|
|
6759
|
-
};
|
|
6760
|
-
|
|
6761
|
-
await expect(
|
|
6762
|
-
runMethodWithUnlockRetry(
|
|
6763
|
-
retryFailMethod as any,
|
|
6764
|
-
retryFailDevice as any,
|
|
6765
|
-
retryFailCoordinator as any
|
|
6766
|
-
)
|
|
6767
|
-
).rejects.toBe(retryError);
|
|
6768
|
-
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
6769
|
-
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6770
|
-
expect(retryFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6771
|
-
expect(retryFailCoordinator.resumeMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6772
|
-
});
|
|
6773
7086
|
});
|
|
6774
7087
|
|
|
6775
7088
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +7214,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
7214
|
|
|
6902
7215
|
await method.run();
|
|
6903
7216
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7217
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7218
|
+
eventless_wallet_session: true,
|
|
7219
|
+
});
|
|
6905
7220
|
});
|
|
6906
7221
|
|
|
6907
7222
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -6977,6 +7292,29 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6977
7292
|
|
|
6978
7293
|
await expect(method.run()).resolves.toBe(features);
|
|
6979
7294
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
7295
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
7296
|
+
emitUiEvent: false,
|
|
7297
|
+
});
|
|
7298
|
+
});
|
|
7299
|
+
|
|
7300
|
+
test('forwards an explicit PIN type when unlocking a Protocol V2 device', async () => {
|
|
7301
|
+
const method = new DeviceUnlock({
|
|
7302
|
+
id: 1,
|
|
7303
|
+
payload: {
|
|
7304
|
+
method: 'deviceUnlock',
|
|
7305
|
+
pinType: DeviceSessionPinType.Any,
|
|
7306
|
+
},
|
|
7307
|
+
});
|
|
7308
|
+
method.init();
|
|
7309
|
+
|
|
7310
|
+
const unlockDevice = jest.fn().mockResolvedValue({ unlocked: true });
|
|
7311
|
+
(method as any).device = { unlockDevice };
|
|
7312
|
+
|
|
7313
|
+
await method.run();
|
|
7314
|
+
|
|
7315
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
7316
|
+
emitUiEvent: false,
|
|
7317
|
+
});
|
|
6980
7318
|
});
|
|
6981
7319
|
|
|
6982
7320
|
test.each([
|