@onekeyfe/hd-core 1.2.0-alpha.54 → 1.2.0-alpha.56
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__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +1 -1
- package/__tests__/deviceSettings.test.ts +1 -1
- 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 +1353 -1011
- package/__tests__/protocolV2FileWrite.test.ts +39 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/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 +2634 -3530
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +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 +584 -975
- 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 +27 -7
- 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/firmware.ts +2 -0
- 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 +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +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,24 +6060,21 @@ 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]);
|
|
5758
6072
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
5759
6073
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
6074
|
+
expect(typedCall.mock.calls.map(call => call[3])).toEqual([
|
|
6075
|
+
{ writeWithResponse: true },
|
|
6076
|
+
{ writeWithResponse: true },
|
|
6077
|
+
]);
|
|
5760
6078
|
});
|
|
5761
6079
|
|
|
5762
6080
|
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
@@ -5868,9 +6186,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
6186
|
const typedCall = jest
|
|
5869
6187
|
.fn()
|
|
5870
6188
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6189
|
+
new DOMException(
|
|
6190
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6191
|
+
'NotFoundError'
|
|
5874
6192
|
)
|
|
5875
6193
|
);
|
|
5876
6194
|
|
|
@@ -6035,40 +6353,77 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
6353
|
});
|
|
6036
6354
|
|
|
6037
6355
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
'
|
|
6356
|
+
const createResourceFilesystemTypedCall = (
|
|
6357
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6358
|
+
missingTypes: string[] = []
|
|
6359
|
+
) => {
|
|
6360
|
+
const missing = new Set(missingTypes);
|
|
6361
|
+
const resourceByPath = new Map(
|
|
6362
|
+
stable.map(resource => [
|
|
6363
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
6364
|
+
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6365
|
+
],
|
|
6366
|
+
resource,
|
|
6367
|
+
])
|
|
6368
|
+
);
|
|
6369
|
+
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6370
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
6371
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6372
|
+
}
|
|
6373
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6374
|
+
const resource = resourceByPath.get(payload.path);
|
|
6375
|
+
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6376
|
+
return {
|
|
6377
|
+
message: {
|
|
6378
|
+
exist: exists,
|
|
6379
|
+
directory: false,
|
|
6380
|
+
size: exists ? resource?.size : 0,
|
|
6381
|
+
},
|
|
6382
|
+
};
|
|
6383
|
+
}
|
|
6384
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6385
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
6386
|
+
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6387
|
+
const header = new Uint8Array(0x52a0);
|
|
6388
|
+
const view = new DataView(header.buffer);
|
|
6389
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6390
|
+
header[index] = char.charCodeAt(0);
|
|
6391
|
+
});
|
|
6392
|
+
'RESC'.split('').forEach((char, index) => {
|
|
6393
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6394
|
+
});
|
|
6395
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6396
|
+
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6397
|
+
header[0x240 + index] = Number.parseInt(
|
|
6398
|
+
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6399
|
+
16
|
|
6400
|
+
);
|
|
6401
|
+
}
|
|
6402
|
+
const offset = Number(payload.file.offset);
|
|
6403
|
+
const chunkLength = Number(payload.chunk_len);
|
|
6404
|
+
return {
|
|
6405
|
+
message: {
|
|
6406
|
+
data: header.slice(offset, offset + chunkLength),
|
|
6407
|
+
},
|
|
6408
|
+
};
|
|
6409
|
+
}
|
|
6410
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6411
|
+
});
|
|
6412
|
+
};
|
|
6413
|
+
|
|
6414
|
+
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6415
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
6416
|
+
'identity mismatch'
|
|
6056
6417
|
);
|
|
6057
6418
|
expect(() =>
|
|
6058
6419
|
assertProtocolV2ReconnectIdentity('expected-serial', 'expected-serial')
|
|
6059
6420
|
).not.toThrow();
|
|
6060
6421
|
});
|
|
6061
6422
|
|
|
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
|
-
);
|
|
6423
|
+
test('allows reconnect when either physical serial is unavailable', () => {
|
|
6424
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).not.toThrow();
|
|
6425
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).not.toThrow();
|
|
6426
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).not.toThrow();
|
|
6072
6427
|
});
|
|
6073
6428
|
|
|
6074
6429
|
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
@@ -6101,7 +6456,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6101
6456
|
);
|
|
6102
6457
|
});
|
|
6103
6458
|
|
|
6104
|
-
test('
|
|
6459
|
+
test('allows firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6105
6460
|
const method = new FirmwareUpdateV4({
|
|
6106
6461
|
id: 1,
|
|
6107
6462
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6114,39 +6469,209 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6114
6469
|
getCommands: () => ({ typedCall }),
|
|
6115
6470
|
});
|
|
6116
6471
|
|
|
6117
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6118
|
-
|
|
6119
|
-
});
|
|
6472
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).resolves.toBeUndefined();
|
|
6473
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
6120
6474
|
});
|
|
6121
6475
|
|
|
6122
|
-
test('uses
|
|
6476
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6123
6477
|
const method = new FirmwareUpdateV4({
|
|
6124
6478
|
id: 1,
|
|
6125
6479
|
payload: {
|
|
6126
6480
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
6481
|
+
platform: 'web',
|
|
6482
|
+
targetsToUpdate: ['resource'],
|
|
6129
6483
|
},
|
|
6130
6484
|
});
|
|
6131
6485
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
});
|
|
6486
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6487
|
+
(type, index) => ({
|
|
6488
|
+
type,
|
|
6489
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6490
|
+
size: 0x52a0 + index + 1,
|
|
6491
|
+
fileHash: 'a'.repeat(64),
|
|
6492
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6493
|
+
})
|
|
6494
|
+
);
|
|
6495
|
+
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
6143
6496
|
(method as any).device = stubDevice({
|
|
6144
6497
|
getCommands: () => ({ typedCall }),
|
|
6145
6498
|
});
|
|
6499
|
+
const resourcesSpy = jest
|
|
6500
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6501
|
+
.mockReturnValue(stable as any);
|
|
6502
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6503
|
+
|
|
6504
|
+
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
6505
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6506
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
6507
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
6508
|
+
resourcesSpy.mockRestore();
|
|
6509
|
+
});
|
|
6510
|
+
|
|
6511
|
+
test('downloads only changed resources from loader inventory', async () => {
|
|
6512
|
+
const method = new FirmwareUpdateV4({
|
|
6513
|
+
id: 1,
|
|
6514
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6515
|
+
});
|
|
6516
|
+
method.init();
|
|
6517
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6518
|
+
(type, index) => ({
|
|
6519
|
+
type,
|
|
6520
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6521
|
+
size: 0x52a0 + index + 1,
|
|
6522
|
+
fileHash: 'a'.repeat(64),
|
|
6523
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6524
|
+
})
|
|
6525
|
+
);
|
|
6526
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6527
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6528
|
+
const resourcesSpy = jest
|
|
6529
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6530
|
+
.mockReturnValue(stable as any);
|
|
6531
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6532
|
+
Promise.resolve({
|
|
6533
|
+
name: `${resource.type}.okpkg`,
|
|
6534
|
+
binary: new ArrayBuffer(resource.size),
|
|
6535
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6536
|
+
})
|
|
6537
|
+
);
|
|
6538
|
+
|
|
6539
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6540
|
+
|
|
6541
|
+
expect(bundles).toHaveLength(1);
|
|
6542
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6543
|
+
resourcesSpy.mockRestore();
|
|
6544
|
+
});
|
|
6545
|
+
|
|
6546
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
6547
|
+
const method = new FirmwareUpdateV4({
|
|
6548
|
+
id: 1,
|
|
6549
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6550
|
+
});
|
|
6551
|
+
method.init();
|
|
6552
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6553
|
+
(type, index) => ({
|
|
6554
|
+
type,
|
|
6555
|
+
url: `https://example.com/${type}.okpkg`,
|
|
6556
|
+
size: 0x52a0 + index + 1,
|
|
6557
|
+
fileHash: 'a'.repeat(64),
|
|
6558
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
6559
|
+
})
|
|
6560
|
+
);
|
|
6561
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6562
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6563
|
+
const resourcesSpy = jest
|
|
6564
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6565
|
+
.mockReturnValue(stable as any);
|
|
6566
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6567
|
+
Promise.resolve({
|
|
6568
|
+
name: `${resource.type}.okpkg`,
|
|
6569
|
+
binary: new ArrayBuffer(resource.size),
|
|
6570
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6571
|
+
})
|
|
6572
|
+
);
|
|
6573
|
+
|
|
6574
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6146
6575
|
|
|
6147
|
-
|
|
6148
|
-
expect(
|
|
6149
|
-
expect(
|
|
6576
|
+
expect(bundles).toHaveLength(1);
|
|
6577
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6578
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
6579
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6580
|
+
resourcesSpy.mockRestore();
|
|
6581
|
+
});
|
|
6582
|
+
|
|
6583
|
+
const buildBootResourcesHeader = ({
|
|
6584
|
+
type = 'CRAT',
|
|
6585
|
+
payloadHash = 'ab'.repeat(64),
|
|
6586
|
+
headerHash = 'cd'.repeat(64),
|
|
6587
|
+
}: {
|
|
6588
|
+
type?: string;
|
|
6589
|
+
payloadHash?: string;
|
|
6590
|
+
headerHash?: string;
|
|
6591
|
+
} = {}) => {
|
|
6592
|
+
const header = new Uint8Array(0x52a0);
|
|
6593
|
+
const view = new DataView(header.buffer);
|
|
6594
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6595
|
+
header[index] = char.charCodeAt(0);
|
|
6596
|
+
});
|
|
6597
|
+
type.split('').forEach((char, index) => {
|
|
6598
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6599
|
+
});
|
|
6600
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6601
|
+
const writeHex = (offset: number, hex: string) => {
|
|
6602
|
+
for (let index = 0; index < hex.length / 2; index++) {
|
|
6603
|
+
header[offset + index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
6604
|
+
}
|
|
6605
|
+
};
|
|
6606
|
+
writeHex(0x200, payloadHash);
|
|
6607
|
+
writeHex(0x240, headerHash);
|
|
6608
|
+
return header;
|
|
6609
|
+
};
|
|
6610
|
+
|
|
6611
|
+
test('does not resolve boot resources unless the optional target is selected', async () => {
|
|
6612
|
+
const method = new FirmwareUpdateV4({
|
|
6613
|
+
id: 1,
|
|
6614
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
6615
|
+
});
|
|
6616
|
+
method.init();
|
|
6617
|
+
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6618
|
+
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
6619
|
+
|
|
6620
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
6621
|
+
|
|
6622
|
+
expect(configSpy).not.toHaveBeenCalled();
|
|
6623
|
+
expect(downloadSpy).not.toHaveBeenCalled();
|
|
6624
|
+
});
|
|
6625
|
+
|
|
6626
|
+
test('downloads and maps the selected boot resources CRATE target', async () => {
|
|
6627
|
+
const payloadHash = 'ab'.repeat(64);
|
|
6628
|
+
const headerHash = 'cd'.repeat(64);
|
|
6629
|
+
const bytes = buildBootResourcesHeader({ payloadHash, headerHash });
|
|
6630
|
+
const binary = bytes.buffer as ArrayBuffer;
|
|
6631
|
+
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
6632
|
+
const resource = {
|
|
6633
|
+
required: false as const,
|
|
6634
|
+
target: 'CRATE' as const,
|
|
6635
|
+
url: 'https://example.com/boot-resources.crate.okpkg',
|
|
6636
|
+
size: bytes.byteLength,
|
|
6637
|
+
fileHash,
|
|
6638
|
+
payloadHash,
|
|
6639
|
+
headerHash,
|
|
6640
|
+
};
|
|
6641
|
+
const method = new FirmwareUpdateV4({
|
|
6642
|
+
id: 1,
|
|
6643
|
+
payload: {
|
|
6644
|
+
method: 'firmwareUpdateV4',
|
|
6645
|
+
platform: 'web',
|
|
6646
|
+
targetsToUpdate: ['boot_resources'],
|
|
6647
|
+
},
|
|
6648
|
+
});
|
|
6649
|
+
method.init();
|
|
6650
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
6651
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
6652
|
+
|
|
6653
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual({
|
|
6654
|
+
fileName: 'boot_resources.crate.okpkg',
|
|
6655
|
+
binary,
|
|
6656
|
+
targetId: 1,
|
|
6657
|
+
kind: 'boot_resources',
|
|
6658
|
+
});
|
|
6659
|
+
});
|
|
6660
|
+
|
|
6661
|
+
test('rejects a non-CRATE manual boot resources package', async () => {
|
|
6662
|
+
const method = new FirmwareUpdateV4({
|
|
6663
|
+
id: 1,
|
|
6664
|
+
payload: {
|
|
6665
|
+
method: 'firmwareUpdateV4',
|
|
6666
|
+
platform: 'web',
|
|
6667
|
+
bootResourcesBinary: buildBootResourcesHeader({ type: 'RESC' }).buffer,
|
|
6668
|
+
},
|
|
6669
|
+
});
|
|
6670
|
+
method.init();
|
|
6671
|
+
|
|
6672
|
+
await expect((method as any).prepareProtocolV2BootResources()).rejects.toThrow(
|
|
6673
|
+
'Invalid Pro2 boot resources CRATE header'
|
|
6674
|
+
);
|
|
6150
6675
|
});
|
|
6151
6676
|
});
|
|
6152
6677
|
|
|
@@ -6286,311 +6811,230 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6286
6811
|
expect(method.unlockPolicy).toBe('none');
|
|
6287
6812
|
});
|
|
6288
6813
|
|
|
6289
|
-
test('
|
|
6814
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6290
6815
|
const calls: string[] = [];
|
|
6816
|
+
const features = { unlocked: true };
|
|
6291
6817
|
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
|
-
}),
|
|
6818
|
+
name: 'confluxSignMessageCIP23',
|
|
6819
|
+
unlockPolicy: 'none',
|
|
6820
|
+
useDevicePassphraseState: true,
|
|
6821
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6822
|
+
run: jest.fn(() => {
|
|
6823
|
+
calls.push('run');
|
|
6824
|
+
return Promise.resolve({ address: '0x1' });
|
|
6825
|
+
}),
|
|
6305
6826
|
};
|
|
6306
6827
|
const device = {
|
|
6828
|
+
features,
|
|
6829
|
+
commands: {
|
|
6830
|
+
typedCall: jest.fn(() => {
|
|
6831
|
+
calls.push('status');
|
|
6832
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6833
|
+
}),
|
|
6834
|
+
},
|
|
6307
6835
|
isProtocolV2: () => true,
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6836
|
+
isBootloader: () => false,
|
|
6837
|
+
isRomloader: () => false,
|
|
6838
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6839
|
+
features.unlocked = status.unlocked === true;
|
|
6840
|
+
return features;
|
|
6311
6841
|
}),
|
|
6842
|
+
unlockDevice: jest.fn(),
|
|
6312
6843
|
};
|
|
6313
6844
|
const uiCoordinator = {
|
|
6314
6845
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6846
|
+
enterUnlockInteraction: jest.fn(),
|
|
6317
6847
|
};
|
|
6318
6848
|
|
|
6319
6849
|
await expect(
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
]);
|
|
6850
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6851
|
+
uiCoordinator: uiCoordinator as any,
|
|
6852
|
+
})
|
|
6853
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6854
|
+
|
|
6855
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6856
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6857
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6858
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6330
6859
|
});
|
|
6331
6860
|
|
|
6332
|
-
test('
|
|
6861
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6333
6862
|
const calls: string[] = [];
|
|
6863
|
+
const identityError = new Error('Unexpected device');
|
|
6334
6864
|
const method = {
|
|
6335
6865
|
name: 'evmSignMessage',
|
|
6336
|
-
|
|
6866
|
+
unlockPolicy: 'none',
|
|
6337
6867
|
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
|
-
}),
|
|
6868
|
+
run: jest.fn(),
|
|
6349
6869
|
};
|
|
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
|
-
});
|
|
6870
|
+
const features = { unlocked: false };
|
|
6367
6871
|
const device = {
|
|
6368
|
-
features
|
|
6369
|
-
|
|
6370
|
-
|
|
6872
|
+
features,
|
|
6873
|
+
commands: {
|
|
6874
|
+
typedCall: jest.fn(() => {
|
|
6875
|
+
calls.push('status');
|
|
6876
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6877
|
+
}),
|
|
6878
|
+
},
|
|
6371
6879
|
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')),
|
|
6880
|
+
isBootloader: () => false,
|
|
6881
|
+
isRomloader: () => false,
|
|
6882
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6883
|
+
unlockDevice: jest.fn(),
|
|
6381
6884
|
};
|
|
6382
6885
|
|
|
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
|
-
};
|
|
6886
|
+
await expect(
|
|
6887
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6888
|
+
afterStatusBeforeUnlock: () => {
|
|
6889
|
+
calls.push('identity');
|
|
6890
|
+
throw identityError;
|
|
6891
|
+
},
|
|
6892
|
+
})
|
|
6893
|
+
).rejects.toBe(identityError);
|
|
6427
6894
|
|
|
6428
|
-
|
|
6429
|
-
expect(
|
|
6430
|
-
expect(
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6895
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6896
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6897
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6433
6898
|
});
|
|
6434
6899
|
|
|
6435
|
-
test('
|
|
6900
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6436
6901
|
const calls: string[] = [];
|
|
6902
|
+
const features = { unlocked: true };
|
|
6437
6903
|
const method = {
|
|
6438
6904
|
name: 'evmSignMessage',
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
unlockPolicy: 'retry-on-locked',
|
|
6905
|
+
unlockPolicy: 'unlock-before-run',
|
|
6906
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6442
6907
|
run: jest.fn(() => {
|
|
6443
6908
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6909
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6445
6910
|
}),
|
|
6446
6911
|
};
|
|
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
6912
|
const device = {
|
|
6470
6913
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6914
|
+
commands: {
|
|
6915
|
+
typedCall: jest.fn(() => {
|
|
6916
|
+
calls.push('status');
|
|
6917
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6918
|
+
}),
|
|
6919
|
+
},
|
|
6473
6920
|
isProtocolV2: () => true,
|
|
6921
|
+
isBootloader: () => false,
|
|
6922
|
+
isRomloader: () => false,
|
|
6923
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6924
|
+
features.unlocked = status.unlocked === true;
|
|
6925
|
+
return features;
|
|
6926
|
+
}),
|
|
6474
6927
|
unlockDevice: jest.fn(() => {
|
|
6475
|
-
calls.push('unlock
|
|
6928
|
+
calls.push('unlock');
|
|
6476
6929
|
features.unlocked = true;
|
|
6477
|
-
features
|
|
6478
|
-
|
|
6930
|
+
return Promise.resolve(features);
|
|
6931
|
+
}),
|
|
6932
|
+
};
|
|
6933
|
+
const uiCoordinator = {
|
|
6934
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6935
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
6936
|
+
calls.push('unlock-prompt');
|
|
6937
|
+
return undefined;
|
|
6479
6938
|
}),
|
|
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
6939
|
};
|
|
6486
6940
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6941
|
+
await expect(
|
|
6942
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6943
|
+
uiCoordinator: uiCoordinator as any,
|
|
6944
|
+
prepare: () => {
|
|
6945
|
+
calls.push('wallet-session');
|
|
6946
|
+
return Promise.resolve();
|
|
6947
|
+
},
|
|
6948
|
+
})
|
|
6949
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
6950
|
+
|
|
6491
6951
|
expect(calls).toEqual([
|
|
6492
|
-
'
|
|
6493
|
-
'
|
|
6494
|
-
'
|
|
6495
|
-
'
|
|
6952
|
+
'status',
|
|
6953
|
+
'unlock-prompt',
|
|
6954
|
+
'unlock',
|
|
6955
|
+
'wallet-session',
|
|
6956
|
+
'method-prompt',
|
|
6496
6957
|
'run',
|
|
6497
6958
|
]);
|
|
6959
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6960
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6961
|
});
|
|
6499
6962
|
|
|
6500
|
-
test('
|
|
6963
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6501
6964
|
const method = {
|
|
6502
6965
|
name: 'evmGetAddress',
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
unlockPolicy: 'retry-on-locked',
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6966
|
+
unlockPolicy: 'unlock-before-run',
|
|
6967
|
+
run: jest.fn(),
|
|
6507
6968
|
};
|
|
6508
6969
|
const device = {
|
|
6509
|
-
features: { unlocked:
|
|
6510
|
-
|
|
6970
|
+
features: { unlocked: undefined },
|
|
6971
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6511
6972
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6973
|
+
isBootloader: () => false,
|
|
6974
|
+
isRomloader: () => false,
|
|
6975
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6976
|
+
unlockDevice: jest.fn(),
|
|
6513
6977
|
};
|
|
6514
6978
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6979
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6980
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6517
6981
|
});
|
|
6518
|
-
expect(device.unlockDevice).
|
|
6519
|
-
expect(method.run).
|
|
6982
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6983
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6984
|
});
|
|
6521
6985
|
|
|
6522
|
-
test('
|
|
6523
|
-
const
|
|
6986
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6987
|
+
const error = deviceLockedError();
|
|
6524
6988
|
const method = {
|
|
6525
|
-
name: '
|
|
6989
|
+
name: 'evmSignMessage',
|
|
6526
6990
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
|
|
6528
|
-
run: jest.fn(() => {
|
|
6529
|
-
calls.push('run');
|
|
6530
|
-
return Promise.resolve({ message: 'ok' });
|
|
6531
|
-
}),
|
|
6991
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6532
6992
|
};
|
|
6533
6993
|
const features = { unlocked: true };
|
|
6534
6994
|
const device = {
|
|
6535
6995
|
features,
|
|
6536
|
-
commands: {
|
|
6537
|
-
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('refresh-status');
|
|
6539
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
-
}),
|
|
6541
|
-
},
|
|
6996
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6542
6997
|
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')),
|
|
6998
|
+
isBootloader: () => false,
|
|
6999
|
+
isRomloader: () => false,
|
|
7000
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7001
|
+
unlockDevice: jest.fn(),
|
|
6557
7002
|
};
|
|
6558
7003
|
|
|
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', {});
|
|
7004
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
7005
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6564
7006
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
7007
|
});
|
|
6568
7008
|
|
|
6569
|
-
test('
|
|
6570
|
-
const features = { unlocked:
|
|
6571
|
-
const
|
|
6572
|
-
|
|
7009
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
7010
|
+
const features = { unlocked: true };
|
|
7011
|
+
const context = createProtocolV2UnlockContext();
|
|
7012
|
+
const firstMethod = {
|
|
7013
|
+
name: 'allNetworkGetAddress',
|
|
6573
7014
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().mockResolvedValue({
|
|
7015
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
7016
|
+
};
|
|
7017
|
+
const nestedMethod = {
|
|
7018
|
+
name: 'evmGetAddress',
|
|
7019
|
+
unlockPolicy: 'unlock-before-run',
|
|
7020
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
6575
7021
|
};
|
|
6576
7022
|
const device = {
|
|
6577
7023
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
7024
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
7025
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
7026
|
+
isBootloader: () => false,
|
|
7027
|
+
isRomloader: () => false,
|
|
7028
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
7029
|
unlockDevice: jest.fn(),
|
|
6587
7030
|
};
|
|
6588
7031
|
|
|
6589
|
-
await
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
expect(device.
|
|
6593
|
-
expect(
|
|
7032
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
7033
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
7034
|
+
|
|
7035
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
7036
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
7037
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
6594
7038
|
});
|
|
6595
7039
|
|
|
6596
7040
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6610,7 +7054,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
7054
|
unlockDevice: jest.fn(),
|
|
6611
7055
|
};
|
|
6612
7056
|
|
|
6613
|
-
await expect(
|
|
7057
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
7058
|
message: 'updating',
|
|
6615
7059
|
});
|
|
6616
7060
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +7063,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
7063
|
}
|
|
6620
7064
|
);
|
|
6621
7065
|
|
|
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 () => {
|
|
7066
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
7067
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
7068
|
+
[false, 'unlock-before-run'],
|
|
6655
7069
|
[true, 'none'],
|
|
6656
7070
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
7071
|
const method = {
|
|
6659
7072
|
unlockPolicy,
|
|
6660
|
-
|
|
7073
|
+
useDevicePassphraseState: false,
|
|
7074
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
7075
|
};
|
|
6662
7076
|
const device = {
|
|
7077
|
+
commands: { typedCall: jest.fn() },
|
|
6663
7078
|
isProtocolV2: () => isProtocolV2,
|
|
6664
7079
|
unlockDevice: jest.fn(),
|
|
6665
7080
|
};
|
|
6666
7081
|
|
|
6667
|
-
await expect(
|
|
7082
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
7083
|
+
message: 'ok',
|
|
7084
|
+
});
|
|
7085
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
7086
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
7087
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
7088
|
}
|
|
6671
7089
|
});
|
|
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
7090
|
});
|
|
6774
7091
|
|
|
6775
7092
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +7218,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
7218
|
|
|
6902
7219
|
await method.run();
|
|
6903
7220
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7221
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7222
|
+
eventless_wallet_session: true,
|
|
7223
|
+
});
|
|
6905
7224
|
});
|
|
6906
7225
|
|
|
6907
7226
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -6977,6 +7296,29 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6977
7296
|
|
|
6978
7297
|
await expect(method.run()).resolves.toBe(features);
|
|
6979
7298
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
7299
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
7300
|
+
emitUiEvent: false,
|
|
7301
|
+
});
|
|
7302
|
+
});
|
|
7303
|
+
|
|
7304
|
+
test('forwards an explicit PIN type when unlocking a Protocol V2 device', async () => {
|
|
7305
|
+
const method = new DeviceUnlock({
|
|
7306
|
+
id: 1,
|
|
7307
|
+
payload: {
|
|
7308
|
+
method: 'deviceUnlock',
|
|
7309
|
+
pinType: DeviceSessionPinType.Any,
|
|
7310
|
+
},
|
|
7311
|
+
});
|
|
7312
|
+
method.init();
|
|
7313
|
+
|
|
7314
|
+
const unlockDevice = jest.fn().mockResolvedValue({ unlocked: true });
|
|
7315
|
+
(method as any).device = { unlockDevice };
|
|
7316
|
+
|
|
7317
|
+
await method.run();
|
|
7318
|
+
|
|
7319
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
7320
|
+
emitUiEvent: false,
|
|
7321
|
+
});
|
|
6980
7322
|
});
|
|
6981
7323
|
|
|
6982
7324
|
test.each([
|