@onekeyfe/hd-core 1.2.0-alpha.56 → 1.2.0-alpha.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -231
- package/__tests__/DeviceCommands.test.ts +0 -45
- package/__tests__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -198
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/device-lifecycle-events.test.ts +3 -105
- package/__tests__/device-state-contract.test.ts +0 -1
- package/__tests__/device-state-mapper.test.ts +1 -1
- package/__tests__/deviceSettings.test.ts +1 -1
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +294 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +12 -132
- package/__tests__/homescreen.test.ts +1 -41
- package/__tests__/kaspaSignTransaction.test.ts +26 -3
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +0 -19
- package/__tests__/open-wallet-session.test.ts +37 -568
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -201
- package/__tests__/protocol-v2.test.ts +1046 -1388
- package/__tests__/protocolV2FileWrite.test.ts +0 -39
- package/__tests__/protocolV2UiInteraction.test.ts +0 -63
- package/__tests__/public-pro2-api-boundary.test.ts +0 -1
- package/__tests__/search-devices.test.ts +3 -12
- package/dist/api/BaseMethod.d.ts +1 -3
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +25 -11
- 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 +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +9 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +0 -2
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +2 -4
- 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 +6 -29
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/device.d.ts +0 -4
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +2 -4
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -31
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +2 -10
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +231 -214
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3530 -2634
- package/dist/inject.d.ts +1 -6
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +4 -0
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -6
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -2
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +1 -5
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -2
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +70 -2
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +10 -7
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +0 -3
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +0 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -22
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -4
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +0 -4
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts +0 -10
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +10 -8
- package/src/api/CheckAllFirmwareRelease.ts +57 -54
- package/src/api/FirmwareUpdate.ts +15 -7
- package/src/api/FirmwareUpdateV2.ts +316 -126
- package/src/api/FirmwareUpdateV3.ts +265 -63
- package/src/api/FirmwareUpdateV4.ts +975 -584
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -70
- package/src/api/SearchDevices.ts +1 -3
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -34
- package/src/api/device/DeviceUnlock.ts +3 -8
- package/src/api/device/DeviceUpdateBootloader.ts +122 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -16
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +415 -0
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +144 -39
- package/src/api/helpers/protocolV2FileWrite.ts +7 -27
- package/src/api/index.ts +0 -2
- package/src/api/kaspa/KaspaSignTransaction.ts +12 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
- package/src/api/protocol-v2/helpers.ts +0 -1
- package/src/core/deviceEventRegistration.ts +0 -4
- package/src/core/index.ts +83 -167
- package/src/data/messages/messages-protocol-v2.json +0 -25
- package/src/data-manager/DataManager.ts +39 -71
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +43 -259
- package/src/device/DeviceCommands.ts +1 -7
- package/src/device/DevicePool.ts +2 -7
- package/src/events/device.ts +0 -4
- package/src/events/logBlockEvent.ts +0 -1
- package/src/events/ui-promise.ts +2 -4
- package/src/events/ui-request.ts +2 -36
- package/src/events/ui-response.ts +2 -12
- package/src/index.ts +5 -0
- package/src/inject.ts +24 -60
- package/src/lowLevelInject.ts +8 -7
- package/src/protocols/protocol-v2/features.ts +7 -10
- package/src/protocols/protocol-v2/firmware.ts +0 -2
- package/src/protocols/protocol-v2/lockedError.ts +10 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
- package/src/protocols/protocol-v2/walletSession.ts +36 -214
- package/src/topLevelInject.ts +8 -7
- package/src/types/api/checkAllFirmwareRelease.ts +4 -2
- package/src/types/api/deviceUnlock.ts +1 -12
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -1
- package/src/types/api/firmwareUpdate.ts +76 -3
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -13
- package/src/types/api/protocolV2.ts +0 -13
- package/src/types/device.ts +0 -3
- package/src/types/params.ts +1 -7
- package/src/types/settings.ts +13 -42
- package/src/utils/deviceFeaturesCompat.ts +6 -0
- package/src/utils/deviceFeaturesUtils.ts +0 -8
- package/src/utils/homescreen.ts +6 -32
- package/src/utils/index.ts +1 -6
- package/src/utils/pro2Wallpaper.ts +8 -35
- package/__tests__/deviceUploadNft.test.ts +0 -293
- package/__tests__/pro2Nft.test.ts +0 -108
- package/__tests__/protocol-binding.test.ts +0 -89
- package/__tests__/protocol-v2-resources.test.ts +0 -286
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
- package/__tests__/uiProgressThrottle.test.ts +0 -74
- package/__tests__/uiPromiseRegistry.test.ts +0 -86
- package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
- package/dist/core/uiPromiseRegistry.d.ts +0 -4
- package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -30
- package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/utils/pro2Nft.d.ts +0 -31
- package/dist/utils/pro2Nft.d.ts.map +0 -1
- package/dist/utils/uiProgressThrottle.d.ts +0 -3
- package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
- package/src/api/DetectDeviceConnectProtocol.ts +0 -18
- package/src/api/protocol-v2/DeviceUploadNft.ts +0 -189
- package/src/core/uiPromiseRegistry.ts +0 -41
- package/src/protocols/protocol-v2/resources.ts +0 -359
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -118
- package/src/types/api/detectDeviceConnectProtocol.ts +0 -6
- package/src/utils/pro2Nft.ts +0 -142
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { EFirmwareType,
|
|
1
|
+
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
DeviceSessionPinType,
|
|
5
5
|
DeviceSettingsPage,
|
|
6
6
|
DeviceType,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
9
8
|
|
|
10
9
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
11
10
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -62,7 +61,6 @@ import { createCoreApi } from '../src/inject';
|
|
|
62
61
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
63
62
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
64
63
|
import { DevicePool } from '../src/device/DevicePool';
|
|
65
|
-
import { DEVICE } from '../src/events';
|
|
66
64
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
67
65
|
import {
|
|
68
66
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -76,15 +74,11 @@ import {
|
|
|
76
74
|
requestProtocolV2ProtocolInfo,
|
|
77
75
|
supportsProtocolV2Message,
|
|
78
76
|
} from '../src/protocols/protocol-v2/features';
|
|
79
|
-
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
80
77
|
import {
|
|
81
78
|
getProtocolV2WalletSession,
|
|
82
79
|
refreshProtocolV2DeviceStatus,
|
|
83
80
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
84
|
-
import {
|
|
85
|
-
createProtocolV2UnlockContext,
|
|
86
|
-
runMethodWithUnlockPolicy,
|
|
87
|
-
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
81
|
+
import { runMethodWithUnlockRetry } from '../src/protocols/protocol-v2/unlockRetry';
|
|
88
82
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
89
83
|
import {
|
|
90
84
|
buildProtocolV1FeaturesPayload,
|
|
@@ -98,6 +92,7 @@ import {
|
|
|
98
92
|
getMethodVersionRange,
|
|
99
93
|
} from '../src/utils';
|
|
100
94
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
+
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
101
96
|
import {
|
|
102
97
|
getPassphraseState,
|
|
103
98
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -246,7 +241,7 @@ describe('UploadPortfolio', () => {
|
|
|
246
241
|
(method as any).device = device;
|
|
247
242
|
|
|
248
243
|
method.init();
|
|
249
|
-
await
|
|
244
|
+
await runMethodWithUnlockRetry(method, device as any);
|
|
250
245
|
|
|
251
246
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
252
247
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -422,16 +417,14 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
422
417
|
}
|
|
423
418
|
return d.features;
|
|
424
419
|
};
|
|
425
|
-
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
426
|
-
requestProtocolV2ProtocolInfo({
|
|
427
|
-
commands: d.getCommands?.() ?? d.commands,
|
|
428
|
-
timeoutMs,
|
|
429
|
-
});
|
|
430
420
|
d.probeProtocolV2RuntimeState ??= async (
|
|
431
421
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
432
422
|
timeoutMs?: number
|
|
433
423
|
) => {
|
|
434
|
-
const protocolInfo = await
|
|
424
|
+
const protocolInfo = await requestProtocolV2ProtocolInfo({
|
|
425
|
+
commands: d.getCommands(),
|
|
426
|
+
timeoutMs,
|
|
427
|
+
});
|
|
435
428
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
436
429
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
437
430
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -788,9 +781,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
788
781
|
if (request === 'DeviceStatusGet') {
|
|
789
782
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
790
783
|
}
|
|
791
|
-
if (request === 'DeviceSessionAskPassphrase') {
|
|
792
|
-
return { type: 'Success', message: {} };
|
|
793
|
-
}
|
|
794
784
|
if (request === 'DeviceSessionGet') {
|
|
795
785
|
return {
|
|
796
786
|
type: 'DeviceSession',
|
|
@@ -815,7 +805,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
815
805
|
).resolves.toEqual({
|
|
816
806
|
passphraseState: 'state-1',
|
|
817
807
|
newSession: 'session-1',
|
|
818
|
-
unlockedAttachPin:
|
|
808
|
+
unlockedAttachPin: undefined,
|
|
819
809
|
resumed: false,
|
|
820
810
|
});
|
|
821
811
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -824,13 +814,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
824
814
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
825
815
|
type: DeviceSessionPinType.Main,
|
|
826
816
|
});
|
|
827
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
828
|
-
passphrase: '',
|
|
829
|
-
on_device: false,
|
|
830
|
-
});
|
|
831
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
832
|
-
seed_domains: [],
|
|
833
|
-
});
|
|
817
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
834
818
|
device.passphraseState = 'state-1';
|
|
835
819
|
expect(device.getInternalState()).toBe('session-1');
|
|
836
820
|
});
|
|
@@ -863,7 +847,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
847
|
},
|
|
864
848
|
};
|
|
865
849
|
}
|
|
866
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
867
850
|
if (request === 'DeviceSessionGet') {
|
|
868
851
|
return {
|
|
869
852
|
message: {
|
|
@@ -880,16 +863,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
880
863
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
881
864
|
|
|
882
865
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
883
|
-
expect(
|
|
884
|
-
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
885
|
-
).toHaveLength(1);
|
|
886
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
887
|
-
seed_domains: [],
|
|
888
|
-
});
|
|
866
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
889
867
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
890
868
|
session_id: 'standard-session',
|
|
891
|
-
btc_test_address: 'standard-state',
|
|
892
|
-
seed_domains: [],
|
|
893
869
|
});
|
|
894
870
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
895
871
|
passphraseState: 'standard-state',
|
|
@@ -931,7 +907,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
931
907
|
},
|
|
932
908
|
};
|
|
933
909
|
}
|
|
934
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
935
910
|
if (request === 'DeviceSessionAskPin') {
|
|
936
911
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
937
912
|
}
|
|
@@ -1026,7 +1001,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1026
1001
|
if (request === 'DeviceStatusGet') {
|
|
1027
1002
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1028
1003
|
}
|
|
1029
|
-
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1030
1004
|
if (request === 'DeviceSessionGet') {
|
|
1031
1005
|
sessionGetCalls += 1;
|
|
1032
1006
|
return {
|
|
@@ -1253,9 +1227,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1253
1227
|
passphrase: 'host hidden wallet',
|
|
1254
1228
|
on_device: false,
|
|
1255
1229
|
});
|
|
1256
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1257
|
-
seed_domains: [],
|
|
1258
|
-
});
|
|
1230
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1259
1231
|
});
|
|
1260
1232
|
|
|
1261
1233
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1363,8 +1335,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1363
1335
|
|
|
1364
1336
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1365
1337
|
session_id: 'session-a',
|
|
1366
|
-
btc_test_address: 'state-a',
|
|
1367
|
-
seed_domains: [],
|
|
1368
1338
|
});
|
|
1369
1339
|
expect(device.getInternalState()).toBe('session-b');
|
|
1370
1340
|
});
|
|
@@ -1432,15 +1402,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1432
1402
|
});
|
|
1433
1403
|
expect(typedCall.mock.calls).toEqual([
|
|
1434
1404
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1435
|
-
[
|
|
1436
|
-
'DeviceSessionGet',
|
|
1437
|
-
'DeviceSession',
|
|
1438
|
-
{
|
|
1439
|
-
session_id: 'session-a',
|
|
1440
|
-
btc_test_address: 'state-a',
|
|
1441
|
-
seed_domains: [],
|
|
1442
|
-
},
|
|
1443
|
-
],
|
|
1405
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
1444
1406
|
]);
|
|
1445
1407
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1446
1408
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1488,7 +1450,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1488
1450
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1489
1451
|
],
|
|
1490
1452
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1491
|
-
['DeviceSessionGet', 'DeviceSession', {
|
|
1453
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1492
1454
|
]);
|
|
1493
1455
|
});
|
|
1494
1456
|
|
|
@@ -1591,73 +1553,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1591
1553
|
expect(device.getInternalState()).toBeUndefined();
|
|
1592
1554
|
});
|
|
1593
1555
|
|
|
1594
|
-
test.each([false, true])(
|
|
1595
|
-
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1596
|
-
async lockFails => {
|
|
1597
|
-
const deviceId = 'attach-mismatch-device';
|
|
1598
|
-
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1599
|
-
(device as any).features = normalizeProtocolV2Features(
|
|
1600
|
-
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1601
|
-
{
|
|
1602
|
-
status: {
|
|
1603
|
-
device_id: deviceId,
|
|
1604
|
-
unlocked: true,
|
|
1605
|
-
passphrase_enabled: true,
|
|
1606
|
-
attach_to_pin_enabled: true,
|
|
1607
|
-
unlocked_by_attach_to_pin: false,
|
|
1608
|
-
},
|
|
1609
|
-
}
|
|
1610
|
-
);
|
|
1611
|
-
device.passphraseState = 'expected-state';
|
|
1612
|
-
|
|
1613
|
-
const typedCall = jest.fn((request: string) => {
|
|
1614
|
-
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1615
|
-
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1616
|
-
if (request === 'DeviceStatusGet') {
|
|
1617
|
-
return {
|
|
1618
|
-
message: {
|
|
1619
|
-
device_id: deviceId,
|
|
1620
|
-
unlocked: true,
|
|
1621
|
-
passphrase_enabled: true,
|
|
1622
|
-
attach_to_pin_enabled: true,
|
|
1623
|
-
unlocked_by_attach_to_pin: true,
|
|
1624
|
-
},
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
if (request === 'DeviceSessionGet') {
|
|
1628
|
-
return {
|
|
1629
|
-
message: {
|
|
1630
|
-
btc_test_address: 'wrong-attach-state',
|
|
1631
|
-
session_id: 'wrong-attach-session',
|
|
1632
|
-
},
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
if (request === 'LockDevice') {
|
|
1636
|
-
if (lockFails) throw new Error('Lock unsupported');
|
|
1637
|
-
return { message: { message: 'Device locked' } };
|
|
1638
|
-
}
|
|
1639
|
-
throw new Error(`Unexpected request: ${request}`);
|
|
1640
|
-
});
|
|
1641
|
-
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1642
|
-
(device as any).commands = { typedCall, promptPassphrase };
|
|
1643
|
-
|
|
1644
|
-
await expect(
|
|
1645
|
-
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1646
|
-
).rejects.toMatchObject({
|
|
1647
|
-
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1648
|
-
});
|
|
1649
|
-
|
|
1650
|
-
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1651
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1652
|
-
btc_test_address: 'expected-state',
|
|
1653
|
-
seed_domains: [],
|
|
1654
|
-
});
|
|
1655
|
-
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1656
|
-
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1657
|
-
expect(device.getInternalState()).toBeUndefined();
|
|
1658
|
-
}
|
|
1659
|
-
);
|
|
1660
|
-
|
|
1661
1556
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1662
1557
|
const device = Device.fromDescriptor({
|
|
1663
1558
|
id: 'cache-device-attach-recovery',
|
|
@@ -1714,17 +1609,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1714
1609
|
}),
|
|
1715
1610
|
{ cancelDeviceOnReject: false }
|
|
1716
1611
|
);
|
|
1717
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1718
|
-
DeviceSessionPinType.AttachToPin,
|
|
1719
|
-
expect.objectContaining({
|
|
1720
|
-
emitUiEvent: false,
|
|
1721
|
-
interaction: expect.objectContaining({
|
|
1722
|
-
phase: 'pin',
|
|
1723
|
-
transition: 'start',
|
|
1724
|
-
protocol: 'V2',
|
|
1725
|
-
}),
|
|
1726
|
-
})
|
|
1727
|
-
);
|
|
1612
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
|
|
1728
1613
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1729
1614
|
});
|
|
1730
1615
|
|
|
@@ -2008,10 +1893,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2008
1893
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
2009
1894
|
});
|
|
2010
1895
|
|
|
2011
|
-
test('
|
|
1896
|
+
test('marks an interactive cache miss as a wallet-session recovery', async () => {
|
|
2012
1897
|
const typedCall = jest
|
|
2013
1898
|
.fn()
|
|
2014
1899
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
+
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
+
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
2015
1902
|
.mockResolvedValueOnce({
|
|
2016
1903
|
message: {
|
|
2017
1904
|
btc_test_address: 'expected-state',
|
|
@@ -2039,11 +1926,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2039
1926
|
await expect(
|
|
2040
1927
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
2041
1928
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2042
|
-
expect(
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1929
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
1930
|
+
{
|
|
1931
|
+
existsAttachPinUser: false,
|
|
1932
|
+
deviceOnly: false,
|
|
1933
|
+
source: 'wallet-session-coordinator',
|
|
1934
|
+
reason: 'session-recovery',
|
|
1935
|
+
expectedPassphraseState: 'expected-state',
|
|
1936
|
+
},
|
|
1937
|
+
{ cancelDeviceOnReject: false }
|
|
1938
|
+
);
|
|
2047
1939
|
});
|
|
2048
1940
|
|
|
2049
1941
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2109,15 +2001,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2109
2001
|
true
|
|
2110
2002
|
);
|
|
2111
2003
|
expect(typedCall.mock.calls).toEqual([
|
|
2112
|
-
[
|
|
2113
|
-
|
|
2114
|
-
'DeviceSession',
|
|
2115
|
-
{
|
|
2116
|
-
session_id: 'session-pro2-app',
|
|
2117
|
-
btc_test_address: 'state-pro2-app',
|
|
2118
|
-
seed_domains: [],
|
|
2119
|
-
},
|
|
2120
|
-
],
|
|
2004
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
2005
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
2121
2006
|
]);
|
|
2122
2007
|
});
|
|
2123
2008
|
|
|
@@ -2306,9 +2191,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2306
2191
|
passphrase: 'host hidden wallet',
|
|
2307
2192
|
on_device: false,
|
|
2308
2193
|
});
|
|
2309
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2310
|
-
seed_domains: [],
|
|
2311
|
-
});
|
|
2194
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2312
2195
|
});
|
|
2313
2196
|
|
|
2314
2197
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2386,83 +2269,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2386
2269
|
);
|
|
2387
2270
|
|
|
2388
2271
|
expect(device.getInternalState()).toBeUndefined();
|
|
2389
|
-
expect(typedCall).
|
|
2390
|
-
btc_test_address: 'expected-state',
|
|
2391
|
-
seed_domains: [],
|
|
2392
|
-
});
|
|
2393
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2272
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2394
2273
|
passphrase: 'host hidden wallet',
|
|
2395
2274
|
on_device: false,
|
|
2396
2275
|
});
|
|
2397
|
-
expect(typedCall).
|
|
2398
|
-
expect(typedCall).
|
|
2399
|
-
seed_domains: [],
|
|
2400
|
-
});
|
|
2401
|
-
});
|
|
2402
|
-
|
|
2403
|
-
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
2404
|
-
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2405
|
-
const typedCall = jest.fn((request: string) => {
|
|
2406
|
-
if (request === 'ProtocolInfoRequest') {
|
|
2407
|
-
return { message: { version: 2 } };
|
|
2408
|
-
}
|
|
2409
|
-
if (request === 'DeviceStatusGet') {
|
|
2410
|
-
return {
|
|
2411
|
-
message: {
|
|
2412
|
-
device_id: 'attach-wallet-device',
|
|
2413
|
-
unlocked: true,
|
|
2414
|
-
passphrase_enabled: true,
|
|
2415
|
-
attach_to_pin_enabled: true,
|
|
2416
|
-
unlocked_by_attach_to_pin: true,
|
|
2417
|
-
},
|
|
2418
|
-
};
|
|
2419
|
-
}
|
|
2420
|
-
if (request === 'DeviceSessionAskPin' || request === 'DeviceSessionAskPassphrase') {
|
|
2421
|
-
return { message: { message: 'Wallet selected' } };
|
|
2422
|
-
}
|
|
2423
|
-
if (request === 'DeviceSessionGet') {
|
|
2424
|
-
return {
|
|
2425
|
-
message: {
|
|
2426
|
-
session_id: 'main-wallet-session',
|
|
2427
|
-
btc_test_address: 'main-wallet-state',
|
|
2428
|
-
},
|
|
2429
|
-
};
|
|
2430
|
-
}
|
|
2431
|
-
if (request === 'LockDevice') {
|
|
2432
|
-
return { message: { message: 'Device locked' } };
|
|
2433
|
-
}
|
|
2434
|
-
throw new Error(`Unexpected request: ${request}`);
|
|
2435
|
-
});
|
|
2436
|
-
|
|
2437
|
-
(device as any).features = normalizeProtocolV2Features(
|
|
2438
|
-
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2439
|
-
{
|
|
2440
|
-
status: {
|
|
2441
|
-
device_id: 'attach-wallet-device',
|
|
2442
|
-
unlocked: true,
|
|
2443
|
-
passphrase_enabled: true,
|
|
2444
|
-
attach_to_pin_enabled: true,
|
|
2445
|
-
// 模拟缓存滞后:设备状态刷新后才确认当前由 Attach PIN 解锁。
|
|
2446
|
-
unlocked_by_attach_to_pin: false,
|
|
2447
|
-
},
|
|
2448
|
-
}
|
|
2449
|
-
);
|
|
2450
|
-
(device as any).commands = { typedCall };
|
|
2451
|
-
const clearInternalState = jest.spyOn(device, 'clearInternalState');
|
|
2452
|
-
|
|
2453
|
-
await expect(device.checkPassphraseStateSafety(undefined, true, false)).rejects.toMatchObject({
|
|
2454
|
-
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2455
|
-
});
|
|
2456
|
-
|
|
2457
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2458
|
-
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
2459
|
-
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2460
|
-
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2461
|
-
'DeviceSessionAskPassphrase',
|
|
2462
|
-
'Success',
|
|
2463
|
-
expect.anything()
|
|
2464
|
-
);
|
|
2465
|
-
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
2276
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2277
|
+
expect(typedCall).toHaveBeenNthCalledWith(4, 'DeviceSessionGet', 'DeviceSession', {});
|
|
2466
2278
|
});
|
|
2467
2279
|
|
|
2468
2280
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2495,22 +2307,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2495
2307
|
await expect(
|
|
2496
2308
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2497
2309
|
).resolves.toBe(true);
|
|
2498
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2310
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
2499
2311
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2500
2312
|
eventless_wallet_session: true,
|
|
2501
2313
|
});
|
|
2502
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2503
|
-
seed_domains: [],
|
|
2504
|
-
});
|
|
2505
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2506
|
-
passphrase: '',
|
|
2507
|
-
on_device: false,
|
|
2508
|
-
});
|
|
2509
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2510
|
-
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2511
|
-
2
|
|
2512
|
-
);
|
|
2513
|
-
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2514
2314
|
});
|
|
2515
2315
|
|
|
2516
2316
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2581,7 +2381,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2581
2381
|
uuid: 'CACHED-SERIAL',
|
|
2582
2382
|
deviceId: null,
|
|
2583
2383
|
bleName: 'Cached BLE',
|
|
2584
|
-
name: 'Cached
|
|
2384
|
+
name: 'Cached Label',
|
|
2585
2385
|
label: 'Cached Label',
|
|
2586
2386
|
deviceType: 'pro2',
|
|
2587
2387
|
});
|
|
@@ -2608,7 +2408,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2608
2408
|
expect(device.features?.label).toBeNull();
|
|
2609
2409
|
});
|
|
2610
2410
|
|
|
2611
|
-
test('
|
|
2411
|
+
test('prefers the real Protocol V2 label over the BLE name for display', () => {
|
|
2612
2412
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2613
2413
|
(device as any).features = {
|
|
2614
2414
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
@@ -2616,10 +2416,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2616
2416
|
bleName: 'Pro2 6136',
|
|
2617
2417
|
};
|
|
2618
2418
|
|
|
2619
|
-
expect(device.getCurrentDisplayName()).toBe('My Pro 2');
|
|
2620
2419
|
expect(device.toMessageObject()).toMatchObject({
|
|
2621
2420
|
bleName: 'Pro2 6136',
|
|
2622
|
-
name: '
|
|
2421
|
+
name: 'My Pro 2',
|
|
2623
2422
|
label: 'My Pro 2',
|
|
2624
2423
|
});
|
|
2625
2424
|
});
|
|
@@ -2673,6 +2472,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2673
2472
|
hw: true,
|
|
2674
2473
|
fw: true,
|
|
2675
2474
|
coprocessor: true,
|
|
2475
|
+
se1: true,
|
|
2476
|
+
se2: true,
|
|
2477
|
+
se3: true,
|
|
2478
|
+
se4: true,
|
|
2676
2479
|
},
|
|
2677
2480
|
types: {
|
|
2678
2481
|
version: true,
|
|
@@ -2864,6 +2667,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2864
2667
|
hw: true,
|
|
2865
2668
|
fw: true,
|
|
2866
2669
|
coprocessor: true,
|
|
2670
|
+
se1: true,
|
|
2671
|
+
se2: true,
|
|
2672
|
+
se3: true,
|
|
2673
|
+
se4: true,
|
|
2867
2674
|
},
|
|
2868
2675
|
types: {
|
|
2869
2676
|
version: true,
|
|
@@ -2874,9 +2681,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2874
2681
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2875
2682
|
}
|
|
2876
2683
|
);
|
|
2877
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2878
|
-
eventless_wallet_session: true,
|
|
2879
|
-
});
|
|
2684
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2880
2685
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2881
2686
|
});
|
|
2882
2687
|
|
|
@@ -2914,9 +2719,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2914
2719
|
}),
|
|
2915
2720
|
expect.anything()
|
|
2916
2721
|
);
|
|
2917
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2918
|
-
eventless_wallet_session: true,
|
|
2919
|
-
});
|
|
2722
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2920
2723
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2921
2724
|
expect(device.features).toMatchObject({
|
|
2922
2725
|
deviceId: null,
|
|
@@ -3182,6 +2985,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3182
2985
|
fw: { application: { version: '1.2.4' } },
|
|
3183
2986
|
},
|
|
3184
2987
|
})
|
|
2988
|
+
.mockResolvedValueOnce({
|
|
2989
|
+
type: 'ProtocolInfo',
|
|
2990
|
+
message: protocolV2ApplicationInfo,
|
|
2991
|
+
})
|
|
3185
2992
|
.mockResolvedValueOnce({
|
|
3186
2993
|
type: 'DeviceStatus',
|
|
3187
2994
|
message: { init_states: true, unlocked: true },
|
|
@@ -3203,7 +3010,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3203
3010
|
firmwareVersion: '1.2.4',
|
|
3204
3011
|
passphraseProtection: null,
|
|
3205
3012
|
});
|
|
3206
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3013
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
3207
3014
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3208
3015
|
4,
|
|
3209
3016
|
'DeviceInfoGet',
|
|
@@ -3213,6 +3020,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3213
3020
|
hw: true,
|
|
3214
3021
|
fw: true,
|
|
3215
3022
|
coprocessor: true,
|
|
3023
|
+
se1: true,
|
|
3024
|
+
se2: true,
|
|
3025
|
+
se3: true,
|
|
3026
|
+
se4: true,
|
|
3216
3027
|
},
|
|
3217
3028
|
types: {
|
|
3218
3029
|
version: true,
|
|
@@ -3313,23 +3124,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3313
3124
|
status: { init_states: true },
|
|
3314
3125
|
}
|
|
3315
3126
|
);
|
|
3316
|
-
const pinOnDevice = jest.fn();
|
|
3317
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3318
3127
|
|
|
3319
3128
|
const features = await device.unlockDevice();
|
|
3320
3129
|
|
|
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
|
-
);
|
|
3333
3130
|
expect(typedCall.mock.calls).toEqual([
|
|
3334
3131
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3335
3132
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3375,12 +3172,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3375
3172
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3376
3173
|
});
|
|
3377
3174
|
(device as any).commands = { typedCall };
|
|
3378
|
-
const pinOnDevice = jest.fn();
|
|
3379
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3380
3175
|
|
|
3381
|
-
await device.unlockDevice(
|
|
3176
|
+
await device.unlockDevice();
|
|
3382
3177
|
|
|
3383
|
-
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3384
3178
|
expect(typedCall.mock.calls).toEqual([
|
|
3385
3179
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3386
3180
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3783,15 +3577,6 @@ describe('API compatibility handling', () => {
|
|
|
3783
3577
|
|
|
3784
3578
|
describe('Protocol V2 firmware update targets', () => {
|
|
3785
3579
|
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
|
-
});
|
|
3795
3580
|
|
|
3796
3581
|
const buildOkppHeader = ({
|
|
3797
3582
|
type = 'RESC',
|
|
@@ -3916,7 +3701,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3916
3701
|
specific: true,
|
|
3917
3702
|
},
|
|
3918
3703
|
},
|
|
3919
|
-
{ timeoutMs:
|
|
3704
|
+
{ timeoutMs: 5000 }
|
|
3920
3705
|
);
|
|
3921
3706
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3922
3707
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3932,7 +3717,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3932
3717
|
});
|
|
3933
3718
|
});
|
|
3934
3719
|
|
|
3935
|
-
test('
|
|
3720
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
3936
3721
|
const method = new FirmwareUpdateV4({
|
|
3937
3722
|
id: 1,
|
|
3938
3723
|
payload: {
|
|
@@ -3967,7 +3752,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3967
3752
|
|
|
3968
3753
|
await method.run();
|
|
3969
3754
|
|
|
3970
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3755
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3971
3756
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3972
3757
|
fwBinaryMap: [
|
|
3973
3758
|
{
|
|
@@ -3978,69 +3763,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3978
3763
|
],
|
|
3979
3764
|
bootloaderBinary: null,
|
|
3980
3765
|
});
|
|
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']);
|
|
4044
3766
|
});
|
|
4045
3767
|
|
|
4046
3768
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -4092,33 +3814,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4092
3814
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4093
3815
|
});
|
|
4094
3816
|
|
|
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
|
-
|
|
4122
3817
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
4123
3818
|
const method = new FirmwareUpdateV4({
|
|
4124
3819
|
id: 1,
|
|
@@ -4171,9 +3866,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4171
3866
|
},
|
|
4172
3867
|
});
|
|
4173
3868
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4174
|
-
const prepareResourceBundles = jest
|
|
4175
|
-
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4176
|
-
.mockResolvedValue(undefined);
|
|
4177
3869
|
(method as any).protocolV2Reboot = jest.fn();
|
|
4178
3870
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
4179
3871
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -4186,7 +3878,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4186
3878
|
|
|
4187
3879
|
await method.run();
|
|
4188
3880
|
|
|
4189
|
-
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
4190
3881
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4191
3882
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
4192
3883
|
bootloaderBinary: null,
|
|
@@ -4269,10 +3960,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4269
3960
|
getCommands: () => commands,
|
|
4270
3961
|
mainId: 'usb-path',
|
|
4271
3962
|
});
|
|
4272
|
-
const cachedDevice = {
|
|
4273
|
-
getConnectId: () => 'usb-path',
|
|
4274
|
-
getCurrentSerialNo: () => '',
|
|
4275
|
-
};
|
|
3963
|
+
const cachedDevice = { getConnectId: () => 'usb-path' };
|
|
4276
3964
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4277
3965
|
devices: {},
|
|
4278
3966
|
deviceList: [cachedDevice],
|
|
@@ -4289,191 +3977,15 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4289
3977
|
expect(initialize).not.toHaveBeenCalled();
|
|
4290
3978
|
});
|
|
4291
3979
|
|
|
4292
|
-
test('
|
|
3980
|
+
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4293
3981
|
const method = new FirmwareUpdateV4({
|
|
4294
3982
|
id: 1,
|
|
4295
3983
|
payload: {
|
|
4296
3984
|
method: 'firmwareUpdateV4',
|
|
4297
3985
|
},
|
|
4298
3986
|
});
|
|
4299
|
-
const commands = { disposed:
|
|
4300
|
-
const
|
|
4301
|
-
const device = stubDevice({
|
|
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);
|
|
3987
|
+
const commands = { disposed: false, mainId: 'usb-session' };
|
|
3988
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
4477
3989
|
const device = stubDevice({
|
|
4478
3990
|
originalDescriptor: {
|
|
4479
3991
|
id: 'usb-id',
|
|
@@ -4559,9 +4071,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4559
4071
|
});
|
|
4560
4072
|
method.postTipMessage = jest.fn();
|
|
4561
4073
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4562
|
-
|
|
4563
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4564
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4074
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4565
4075
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4566
4076
|
message: 'Device rebooted successfully',
|
|
4567
4077
|
});
|
|
@@ -4570,29 +4080,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4570
4080
|
|
|
4571
4081
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4572
4082
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4573
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4574
4083
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4575
4084
|
});
|
|
4576
4085
|
|
|
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
|
-
|
|
4596
4086
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4597
4087
|
const method = new FirmwareUpdateV4({
|
|
4598
4088
|
id: 1,
|
|
@@ -4635,27 +4125,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4635
4125
|
});
|
|
4636
4126
|
});
|
|
4637
4127
|
|
|
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
|
-
|
|
4659
4128
|
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4660
4129
|
const method = new FirmwareUpdateV4({
|
|
4661
4130
|
id: 1,
|
|
@@ -4666,9 +4135,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4666
4135
|
const typedCall = jest
|
|
4667
4136
|
.fn()
|
|
4668
4137
|
.mockRejectedValue(
|
|
4669
|
-
|
|
4670
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4671
|
-
'NotFoundError'
|
|
4138
|
+
Object.assign(
|
|
4139
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4140
|
+
{ name: 'NotFoundError' }
|
|
4672
4141
|
)
|
|
4673
4142
|
);
|
|
4674
4143
|
|
|
@@ -4746,7 +4215,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4746
4215
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4747
4216
|
]);
|
|
4748
4217
|
|
|
4749
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4218
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
4750
4219
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4751
4220
|
'DeviceFirmwareUpdateStatusGet',
|
|
4752
4221
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4784,46 +4253,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4784
4253
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4785
4254
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4786
4255
|
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');
|
|
4827
4256
|
});
|
|
4828
4257
|
|
|
4829
4258
|
test.each([
|
|
@@ -4889,6 +4318,77 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4889
4318
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4890
4319
|
});
|
|
4891
4320
|
|
|
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
|
+
|
|
4892
4392
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4893
4393
|
const method = new FirmwareUpdateV4({
|
|
4894
4394
|
id: 1,
|
|
@@ -4931,152 +4431,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4931
4431
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4932
4432
|
});
|
|
4933
4433
|
|
|
4934
|
-
test('
|
|
4935
|
-
const method = new FirmwareUpdateV4({
|
|
4936
|
-
id: 1,
|
|
4937
|
-
payload: {
|
|
4938
|
-
method: 'firmwareUpdateV4',
|
|
4939
|
-
},
|
|
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);
|
|
4960
|
-
method.postProgressMessage = jest.fn();
|
|
4961
|
-
|
|
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', () => {
|
|
4434
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
5080
4435
|
const method = new FirmwareUpdateV4({
|
|
5081
4436
|
id: 1,
|
|
5082
4437
|
payload: {
|
|
@@ -5110,7 +4465,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5110
4465
|
).toBe(true);
|
|
5111
4466
|
|
|
5112
4467
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
5113
|
-
expect(
|
|
4468
|
+
expect(() =>
|
|
5114
4469
|
(method as any).assertProtocolV2TargetStatus(
|
|
5115
4470
|
[
|
|
5116
4471
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -5118,8 +4473,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5118
4473
|
],
|
|
5119
4474
|
new Set([4, 10])
|
|
5120
4475
|
)
|
|
5121
|
-
).
|
|
5122
|
-
expect(method.postProgressMessage).
|
|
4476
|
+
).toThrow('Protocol V2 install status conflicts with target 4');
|
|
4477
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5123
4478
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5124
4479
|
|
|
5125
4480
|
expect(
|
|
@@ -5208,7 +4563,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5208
4563
|
path: true,
|
|
5209
4564
|
},
|
|
5210
4565
|
},
|
|
5211
|
-
{ timeoutMs:
|
|
4566
|
+
{ timeoutMs: 5000 }
|
|
5212
4567
|
);
|
|
5213
4568
|
});
|
|
5214
4569
|
|
|
@@ -5223,10 +4578,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5223
4578
|
const writtenPaths: string[] = [];
|
|
5224
4579
|
method.postTipMessage = jest.fn();
|
|
5225
4580
|
method.postProgressMessage = jest.fn();
|
|
5226
|
-
(method as any).
|
|
4581
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
5227
4582
|
writtenPaths.push(params.filePath);
|
|
5228
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4583
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
5229
4584
|
});
|
|
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({});
|
|
5230
4589
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5231
4590
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5232
4591
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5266,10 +4625,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5266
4625
|
'vol0:/coprocessor.bin',
|
|
5267
4626
|
1
|
|
5268
4627
|
);
|
|
5269
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
5270
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4628
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(2);
|
|
4629
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
4630
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4631
|
+
});
|
|
4632
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5271
4633
|
targets: [
|
|
5272
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5273
4634
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5274
4635
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
5275
4636
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -5279,7 +4640,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5279
4640
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
5280
4641
|
});
|
|
5281
4642
|
|
|
5282
|
-
test('announces
|
|
4643
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
5283
4644
|
const method = new FirmwareUpdateV4({
|
|
5284
4645
|
id: 1,
|
|
5285
4646
|
payload: {
|
|
@@ -5289,11 +4650,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5289
4650
|
|
|
5290
4651
|
method.postTipMessage = jest.fn();
|
|
5291
4652
|
method.postProgressMessage = jest.fn();
|
|
5292
|
-
(method as any).
|
|
4653
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5293
4654
|
.fn()
|
|
5294
4655
|
.mockImplementation(params =>
|
|
5295
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4656
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5296
4657
|
);
|
|
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({});
|
|
5297
4661
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5298
4662
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5299
4663
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5313,21 +4677,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5313
4677
|
{
|
|
5314
4678
|
fileName: 'application_p1.bin',
|
|
5315
4679
|
binary: new Uint8Array([3]).buffer,
|
|
5316
|
-
targetId:
|
|
4680
|
+
targetId: 4,
|
|
5317
4681
|
},
|
|
5318
4682
|
],
|
|
5319
4683
|
});
|
|
5320
4684
|
|
|
5321
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4685
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
5322
4686
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
5323
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
5324
|
-
expect(
|
|
4687
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
4688
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
4689
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5325
4690
|
1,
|
|
5326
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4691
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
5327
4692
|
);
|
|
5328
|
-
expect((method as any).
|
|
4693
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5329
4694
|
2,
|
|
5330
|
-
expect.objectContaining({ processedSize:
|
|
4695
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
5331
4696
|
);
|
|
5332
4697
|
});
|
|
5333
4698
|
|
|
@@ -5341,7 +4706,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5341
4706
|
|
|
5342
4707
|
method.postTipMessage = jest.fn();
|
|
5343
4708
|
method.postProgressMessage = jest.fn();
|
|
5344
|
-
(method as any).
|
|
4709
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4710
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5345
4711
|
(method as any).device = stubDevice({
|
|
5346
4712
|
getCommands: () => ({
|
|
5347
4713
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -5387,11 +4753,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5387
4753
|
|
|
5388
4754
|
method.postTipMessage = jest.fn();
|
|
5389
4755
|
method.postProgressMessage = jest.fn();
|
|
5390
|
-
(method as any).
|
|
4756
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5391
4757
|
.fn()
|
|
5392
4758
|
.mockImplementation(params =>
|
|
5393
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4759
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5394
4760
|
);
|
|
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({});
|
|
5395
4764
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5396
4765
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5397
4766
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5606,42 +4975,33 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5606
4975
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5607
4976
|
});
|
|
5608
4977
|
|
|
5609
|
-
test('
|
|
4978
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
5610
4979
|
const method = new FirmwareUpdateV4({
|
|
5611
4980
|
id: 1,
|
|
5612
4981
|
payload: {
|
|
5613
4982
|
method: 'firmwareUpdateV4',
|
|
5614
4983
|
platform: 'web',
|
|
4984
|
+
targetsToUpdate: ['resource'],
|
|
5615
4985
|
},
|
|
5616
4986
|
});
|
|
5617
4987
|
method.init();
|
|
5618
4988
|
|
|
5619
|
-
const getSysResourceBinarySpy = jest
|
|
5620
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5621
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5622
4989
|
const getFirmwareLatestReleaseSpy = jest
|
|
5623
4990
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5624
4991
|
.mockReturnValue({
|
|
5625
4992
|
required: false,
|
|
5626
4993
|
version: [1, 0, 0],
|
|
5627
|
-
url: '
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
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',
|
|
4994
|
+
url: '',
|
|
4995
|
+
resourceBundles: [
|
|
4996
|
+
{
|
|
4997
|
+
name: 'images',
|
|
4998
|
+
url: 'https://example.com/images.okpkg',
|
|
4999
|
+
devicePath: 'VOL0:/resource/images/images.okpkg',
|
|
5000
|
+
version: [2, 0, 0],
|
|
5001
|
+
payloadHash: 'ab'.repeat(64),
|
|
5002
|
+
headerHash: 'cd'.repeat(64),
|
|
5643
5003
|
},
|
|
5644
|
-
|
|
5004
|
+
],
|
|
5645
5005
|
fingerprint: '',
|
|
5646
5006
|
changelog: {
|
|
5647
5007
|
'zh-CN': '',
|
|
@@ -5649,135 +5009,435 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5649
5009
|
},
|
|
5650
5010
|
});
|
|
5651
5011
|
|
|
5652
|
-
const
|
|
5012
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
5653
5013
|
deviceType: 'pro2',
|
|
5654
5014
|
firmwareVersion: '1.0.0',
|
|
5655
|
-
bootloaderVersion: '1.0.0',
|
|
5656
5015
|
capabilities: [],
|
|
5657
5016
|
});
|
|
5658
5017
|
|
|
5659
|
-
expect(
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5018
|
+
expect(bundles).toEqual([
|
|
5019
|
+
{
|
|
5020
|
+
name: 'images',
|
|
5021
|
+
binary: expect.any(ArrayBuffer),
|
|
5022
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5023
|
+
url: 'https://example.com/images.okpkg',
|
|
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);
|
|
5665
5030
|
|
|
5666
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5667
5031
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5668
5032
|
});
|
|
5669
5033
|
|
|
5670
|
-
test('
|
|
5034
|
+
test('rejects remote RESC bundle paths before download or bootloader entry', async () => {
|
|
5671
5035
|
const method = new FirmwareUpdateV4({
|
|
5672
5036
|
id: 1,
|
|
5673
5037
|
payload: {
|
|
5674
5038
|
method: 'firmwareUpdateV4',
|
|
5675
5039
|
platform: 'web',
|
|
5040
|
+
targetsToUpdate: ['resource'],
|
|
5676
5041
|
},
|
|
5677
5042
|
});
|
|
5678
5043
|
method.init();
|
|
5679
5044
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5680
5045
|
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5046
|
+
const getSysResourceBinarySpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
5047
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5048
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5049
|
+
.mockReturnValue({
|
|
5050
|
+
required: false,
|
|
5051
|
+
version: [1, 0, 0],
|
|
5052
|
+
url: '',
|
|
5053
|
+
resourceBundles: [
|
|
5054
|
+
{
|
|
5055
|
+
name: 'images',
|
|
5056
|
+
url: 'https://example.com/images.okpkg',
|
|
5057
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5058
|
+
},
|
|
5059
|
+
],
|
|
5060
|
+
fingerprint: '',
|
|
5061
|
+
changelog: {
|
|
5062
|
+
'zh-CN': '',
|
|
5063
|
+
'en-US': '',
|
|
5688
5064
|
},
|
|
5689
|
-
|
|
5690
|
-
};
|
|
5065
|
+
});
|
|
5691
5066
|
|
|
5692
5067
|
(method as any).device = stubDevice({
|
|
5693
5068
|
originalDescriptor: { protocolType: 'V2' },
|
|
5694
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5069
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5695
5070
|
});
|
|
5696
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5697
|
-
|
|
5698
|
-
|
|
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',
|
|
5071
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5072
|
+
bootloaderBinary: null,
|
|
5073
|
+
fwBinaryMap: [],
|
|
5704
5074
|
});
|
|
5075
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5705
5076
|
method.postTipMessage = jest.fn();
|
|
5706
5077
|
|
|
5707
|
-
await method.run();
|
|
5078
|
+
await expect(method.run()).rejects.toThrow('resourceBundles[0].devicePath');
|
|
5079
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5708
5081
|
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5712
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5713
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5714
|
-
});
|
|
5082
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5083
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5715
5084
|
});
|
|
5716
5085
|
|
|
5717
|
-
test('
|
|
5086
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
5718
5087
|
const method = new FirmwareUpdateV4({
|
|
5719
5088
|
id: 1,
|
|
5720
5089
|
payload: {
|
|
5721
5090
|
method: 'firmwareUpdateV4',
|
|
5722
|
-
platform: 'web',
|
|
5723
5091
|
},
|
|
5724
5092
|
});
|
|
5725
|
-
|
|
5726
|
-
(method as any).
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5093
|
+
const openPreparedSource = jest.fn();
|
|
5094
|
+
(method as any).params = {
|
|
5095
|
+
targetsToUpdate: ['resource'],
|
|
5096
|
+
resourceBundleArtifacts: [
|
|
5097
|
+
{
|
|
5098
|
+
name: 'images',
|
|
5099
|
+
artifact: {
|
|
5100
|
+
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5101
|
+
size: 1,
|
|
5102
|
+
sha256: 'a'.repeat(64),
|
|
5103
|
+
},
|
|
5104
|
+
},
|
|
5105
|
+
],
|
|
5106
|
+
};
|
|
5107
|
+
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
+
.mockReturnValue({
|
|
5111
|
+
required: false,
|
|
5112
|
+
version: [1, 0, 0],
|
|
5113
|
+
url: '',
|
|
5114
|
+
resourceBundles: [
|
|
5115
|
+
{
|
|
5116
|
+
name: 'images',
|
|
5117
|
+
url: 'https://example.com/images.okpkg',
|
|
5118
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
+
},
|
|
5120
|
+
],
|
|
5121
|
+
fingerprint: '',
|
|
5122
|
+
changelog: {
|
|
5123
|
+
'zh-CN': '',
|
|
5124
|
+
'en-US': '',
|
|
5125
|
+
},
|
|
5126
|
+
});
|
|
5737
5127
|
|
|
5738
|
-
await expect(
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5128
|
+
await expect(
|
|
5129
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
+
deviceType: 'pro2',
|
|
5131
|
+
})
|
|
5132
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5742
5134
|
|
|
5743
|
-
|
|
5744
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5135
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5745
5136
|
});
|
|
5746
5137
|
|
|
5747
|
-
test('
|
|
5138
|
+
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5748
5139
|
const method = new FirmwareUpdateV4({
|
|
5749
5140
|
id: 1,
|
|
5750
5141
|
payload: {
|
|
5751
5142
|
method: 'firmwareUpdateV4',
|
|
5752
5143
|
platform: 'web',
|
|
5753
|
-
|
|
5144
|
+
targetsToUpdate: ['resource'],
|
|
5754
5145
|
},
|
|
5755
5146
|
});
|
|
5756
5147
|
method.init();
|
|
5757
5148
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5758
5149
|
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
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,
|
|
5414
|
+
},
|
|
5415
|
+
});
|
|
5416
|
+
method.init();
|
|
5417
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5418
|
+
|
|
5419
|
+
(method as any).device = stubDevice({
|
|
5420
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5421
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5422
|
+
});
|
|
5423
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5424
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5425
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5426
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5427
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5428
|
+
bootloaderVersion: '1.0.0',
|
|
5429
|
+
bleVersion: '0.0.0',
|
|
5430
|
+
firmwareVersion: '1.0.0',
|
|
5431
|
+
});
|
|
5432
|
+
method.postTipMessage = jest.fn();
|
|
5433
|
+
|
|
5434
|
+
await method.run();
|
|
5435
|
+
|
|
5436
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5437
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5438
|
+
bootloaderBinary: null,
|
|
5439
|
+
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
5440
|
+
});
|
|
5781
5441
|
});
|
|
5782
5442
|
|
|
5783
5443
|
test('treats manual resource bundles as explicit payload without remote firmware auto-fill', async () => {
|
|
@@ -5869,7 +5529,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5869
5529
|
|
|
5870
5530
|
method.postTipMessage = jest.fn();
|
|
5871
5531
|
method.postProgressMessage = jest.fn();
|
|
5872
|
-
(method as any).
|
|
5532
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
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);
|
|
5873
5536
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5874
5537
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5875
5538
|
|
|
@@ -5885,7 +5548,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5885
5548
|
fwBinaryMap: [],
|
|
5886
5549
|
});
|
|
5887
5550
|
|
|
5888
|
-
expect((method as any).
|
|
5551
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5889
5552
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5890
5553
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5891
5554
|
});
|
|
@@ -5916,12 +5579,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5916
5579
|
});
|
|
5917
5580
|
method.postProgressMessage = jest.fn();
|
|
5918
5581
|
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
filePath: 'vol1:firmware.bin',
|
|
5922
|
-
processedSize: 0,
|
|
5923
|
-
totalSize: 4097,
|
|
5582
|
+
const source = await openFirmwareByteSource({
|
|
5583
|
+
binary: new Uint8Array(4097).buffer,
|
|
5924
5584
|
});
|
|
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
|
+
}
|
|
5925
5595
|
|
|
5926
5596
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5927
5597
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5978,12 +5648,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5978
5648
|
method.postProgressMessage = jest.fn();
|
|
5979
5649
|
|
|
5980
5650
|
try {
|
|
5981
|
-
|
|
5982
|
-
|
|
5651
|
+
const source = await openFirmwareByteSource({
|
|
5652
|
+
binary: new Uint8Array(8001).buffer,
|
|
5653
|
+
});
|
|
5654
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
+
source,
|
|
5983
5656
|
filePath: 'vol0:/firmware.bin',
|
|
5984
5657
|
processedSize: 0,
|
|
5985
5658
|
totalSize: 8001,
|
|
5986
5659
|
});
|
|
5660
|
+
await source?.close();
|
|
5987
5661
|
} finally {
|
|
5988
5662
|
setTimeoutSpy.mockRestore();
|
|
5989
5663
|
}
|
|
@@ -6018,16 +5692,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6018
5692
|
});
|
|
6019
5693
|
method.postProgressMessage = jest.fn();
|
|
6020
5694
|
method.postTipMessage = jest.fn();
|
|
5695
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
6021
5696
|
|
|
5697
|
+
const source = await openFirmwareByteSource({
|
|
5698
|
+
binary: new Uint8Array(4097).buffer,
|
|
5699
|
+
});
|
|
6022
5700
|
await expect(
|
|
6023
|
-
(method as any).
|
|
6024
|
-
|
|
5701
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
5702
|
+
source,
|
|
6025
5703
|
filePath: 'vol0:/firmware.bin',
|
|
6026
5704
|
processedSize: 0,
|
|
6027
5705
|
totalSize: 4097,
|
|
6028
5706
|
})
|
|
6029
5707
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
6030
|
-
|
|
5708
|
+
await source?.close();
|
|
5709
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6031
5710
|
});
|
|
6032
5711
|
|
|
6033
5712
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -6060,21 +5739,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6060
5739
|
});
|
|
6061
5740
|
method.postProgressMessage = jest.fn();
|
|
6062
5741
|
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
6066
|
-
processedSize: 0,
|
|
6067
|
-
totalSize: 1801,
|
|
5742
|
+
const source = await openFirmwareByteSource({
|
|
5743
|
+
binary: new Uint8Array(1801).buffer,
|
|
6068
5744
|
});
|
|
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
|
+
}
|
|
6069
5755
|
|
|
6070
5756
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
6071
5757
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
6072
5758
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
6073
5759
|
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
|
-
]);
|
|
6078
5760
|
});
|
|
6079
5761
|
|
|
6080
5762
|
test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
|
|
@@ -6186,9 +5868,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6186
5868
|
const typedCall = jest
|
|
6187
5869
|
.fn()
|
|
6188
5870
|
.mockRejectedValue(
|
|
6189
|
-
|
|
6190
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6191
|
-
'NotFoundError'
|
|
5871
|
+
Object.assign(
|
|
5872
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
5873
|
+
{ name: 'NotFoundError' }
|
|
6192
5874
|
)
|
|
6193
5875
|
);
|
|
6194
5876
|
|
|
@@ -6353,63 +6035,20 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6353
6035
|
});
|
|
6354
6036
|
|
|
6355
6037
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
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}`);
|
|
6038
|
+
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
6039
|
+
const method = new FirmwareUpdateV4({
|
|
6040
|
+
id: 1,
|
|
6041
|
+
payload: {
|
|
6042
|
+
method: 'firmwareUpdateV4',
|
|
6043
|
+
platform: 'native',
|
|
6044
|
+
expectedDeviceId: 'persisted-device-id',
|
|
6045
|
+
},
|
|
6411
6046
|
});
|
|
6412
|
-
|
|
6047
|
+
|
|
6048
|
+
method.init();
|
|
6049
|
+
|
|
6050
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
+
});
|
|
6413
6052
|
|
|
6414
6053
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6415
6054
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6420,10 +6059,16 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6420
6059
|
).not.toThrow();
|
|
6421
6060
|
});
|
|
6422
6061
|
|
|
6423
|
-
test('
|
|
6424
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).
|
|
6425
|
-
|
|
6426
|
-
|
|
6062
|
+
test('fails closed when the physical serial is unavailable', () => {
|
|
6063
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
6064
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
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
|
+
);
|
|
6427
6072
|
});
|
|
6428
6073
|
|
|
6429
6074
|
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
@@ -6456,7 +6101,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6456
6101
|
);
|
|
6457
6102
|
});
|
|
6458
6103
|
|
|
6459
|
-
test('
|
|
6104
|
+
test('rejects firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6460
6105
|
const method = new FirmwareUpdateV4({
|
|
6461
6106
|
id: 1,
|
|
6462
6107
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6469,209 +6114,39 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6469
6114
|
getCommands: () => ({ typedCall }),
|
|
6470
6115
|
});
|
|
6471
6116
|
|
|
6472
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6473
|
-
|
|
6474
|
-
});
|
|
6475
|
-
|
|
6476
|
-
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6477
|
-
const method = new FirmwareUpdateV4({
|
|
6478
|
-
id: 1,
|
|
6479
|
-
payload: {
|
|
6480
|
-
method: 'firmwareUpdateV4',
|
|
6481
|
-
platform: 'web',
|
|
6482
|
-
targetsToUpdate: ['resource'],
|
|
6483
|
-
},
|
|
6484
|
-
});
|
|
6485
|
-
method.init();
|
|
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);
|
|
6496
|
-
(method as any).device = stubDevice({
|
|
6497
|
-
getCommands: () => ({ typedCall }),
|
|
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'] },
|
|
6117
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).rejects.toMatchObject({
|
|
6118
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
6515
6119
|
});
|
|
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();
|
|
6575
|
-
|
|
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
6120
|
});
|
|
6582
6121
|
|
|
6583
|
-
|
|
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
|
-
};
|
|
6122
|
+
test('uses the BLE read limit when reading an existing firmware bundle header', async () => {
|
|
6641
6123
|
const method = new FirmwareUpdateV4({
|
|
6642
6124
|
id: 1,
|
|
6643
6125
|
payload: {
|
|
6644
6126
|
method: 'firmwareUpdateV4',
|
|
6645
|
-
platform: '
|
|
6646
|
-
|
|
6127
|
+
platform: 'native',
|
|
6128
|
+
chunkSize: 1800,
|
|
6647
6129
|
},
|
|
6648
6130
|
});
|
|
6649
6131
|
method.init();
|
|
6650
|
-
|
|
6651
|
-
jest.
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6132
|
+
const readChunkLengths: number[] = [];
|
|
6133
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6134
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6135
|
+
return Promise.resolve({ message: { exist: true, directory: false, size: 0x52a0 } });
|
|
6136
|
+
}
|
|
6137
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6138
|
+
readChunkLengths.push(request.chunk_len);
|
|
6139
|
+
return Promise.resolve({ message: { data: new Uint8Array(request.chunk_len) } });
|
|
6140
|
+
}
|
|
6141
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6658
6142
|
});
|
|
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
|
-
},
|
|
6143
|
+
(method as any).device = stubDevice({
|
|
6144
|
+
getCommands: () => ({ typedCall }),
|
|
6669
6145
|
});
|
|
6670
|
-
method.init();
|
|
6671
6146
|
|
|
6672
|
-
await expect((method as any).
|
|
6673
|
-
|
|
6674
|
-
);
|
|
6147
|
+
await expect((method as any).readProtocolV2DeviceFileHeader('bundle.okpp')).resolves.toBeNull();
|
|
6148
|
+
expect(readChunkLengths.length).toBeGreaterThan(1);
|
|
6149
|
+
expect(Math.max(...readChunkLengths)).toBe(900);
|
|
6675
6150
|
});
|
|
6676
6151
|
});
|
|
6677
6152
|
|
|
@@ -6798,245 +6273,326 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6798
6273
|
});
|
|
6799
6274
|
});
|
|
6800
6275
|
|
|
6801
|
-
test.each([
|
|
6802
|
-
['deviceInfoGet', DeviceInfoGet],
|
|
6803
|
-
['deviceStatusGet', DeviceStatusGet],
|
|
6804
|
-
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6805
|
-
const method = new MethodClass({
|
|
6806
|
-
id: 1,
|
|
6807
|
-
payload: { method: _name },
|
|
6808
|
-
} as any);
|
|
6809
|
-
method.init();
|
|
6276
|
+
test.each([
|
|
6277
|
+
['deviceInfoGet', DeviceInfoGet],
|
|
6278
|
+
['deviceStatusGet', DeviceStatusGet],
|
|
6279
|
+
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6280
|
+
const method = new MethodClass({
|
|
6281
|
+
id: 1,
|
|
6282
|
+
payload: { method: _name },
|
|
6283
|
+
} as any);
|
|
6284
|
+
method.init();
|
|
6285
|
+
|
|
6286
|
+
expect(method.unlockPolicy).toBe('none');
|
|
6287
|
+
});
|
|
6288
|
+
|
|
6289
|
+
test('unlocks and retries an opted-in Protocol V2 method once', async () => {
|
|
6290
|
+
const calls: string[] = [];
|
|
6291
|
+
const method = {
|
|
6292
|
+
name: 'testBusinessMethod',
|
|
6293
|
+
unlockPolicy: 'retry-on-locked',
|
|
6294
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6295
|
+
run: jest
|
|
6296
|
+
.fn()
|
|
6297
|
+
.mockImplementationOnce(() => {
|
|
6298
|
+
calls.push('run-1');
|
|
6299
|
+
return Promise.reject(deviceLockedError());
|
|
6300
|
+
})
|
|
6301
|
+
.mockImplementationOnce(() => {
|
|
6302
|
+
calls.push('run-2');
|
|
6303
|
+
return Promise.resolve({ message: 'ok' });
|
|
6304
|
+
}),
|
|
6305
|
+
};
|
|
6306
|
+
const device = {
|
|
6307
|
+
isProtocolV2: () => true,
|
|
6308
|
+
unlockDevice: jest.fn(() => {
|
|
6309
|
+
calls.push('unlock');
|
|
6310
|
+
return Promise.resolve();
|
|
6311
|
+
}),
|
|
6312
|
+
};
|
|
6313
|
+
const uiCoordinator = {
|
|
6314
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6316
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6317
|
+
};
|
|
6318
|
+
|
|
6319
|
+
await expect(
|
|
6320
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6321
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6322
|
+
expect(calls).toEqual([
|
|
6323
|
+
'method-prompt',
|
|
6324
|
+
'run-1',
|
|
6325
|
+
'unlock-prompt',
|
|
6326
|
+
'unlock',
|
|
6327
|
+
'method-prompt',
|
|
6328
|
+
'run-2',
|
|
6329
|
+
]);
|
|
6330
|
+
});
|
|
6331
|
+
|
|
6332
|
+
test('restores the expected hidden-wallet session before retrying after unlock', async () => {
|
|
6333
|
+
const calls: string[] = [];
|
|
6334
|
+
const method = {
|
|
6335
|
+
name: 'evmSignMessage',
|
|
6336
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6337
|
+
useDevicePassphraseState: true,
|
|
6338
|
+
unlockPolicy: 'retry-on-locked',
|
|
6339
|
+
run: jest
|
|
6340
|
+
.fn()
|
|
6341
|
+
.mockImplementationOnce(() => {
|
|
6342
|
+
calls.push('run-1');
|
|
6343
|
+
return Promise.reject(deviceLockedError());
|
|
6344
|
+
})
|
|
6345
|
+
.mockImplementationOnce(() => {
|
|
6346
|
+
calls.push('run-2');
|
|
6347
|
+
return Promise.resolve({ message: 'ok' });
|
|
6348
|
+
}),
|
|
6349
|
+
};
|
|
6350
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6351
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6352
|
+
calls.push('negotiate-session');
|
|
6353
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6354
|
+
}
|
|
6355
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6356
|
+
calls.push('resume-hidden-session');
|
|
6357
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6358
|
+
return Promise.resolve({
|
|
6359
|
+
message: {
|
|
6360
|
+
session_id: 'hidden-session',
|
|
6361
|
+
btc_test_address: 'hidden-state',
|
|
6362
|
+
},
|
|
6363
|
+
});
|
|
6364
|
+
}
|
|
6365
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6366
|
+
});
|
|
6367
|
+
const device = {
|
|
6368
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
6369
|
+
passphraseState: 'hidden-state',
|
|
6370
|
+
commands: { typedCall },
|
|
6371
|
+
isProtocolV2: () => true,
|
|
6372
|
+
unlockDevice: jest.fn(() => {
|
|
6373
|
+
calls.push('unlock');
|
|
6374
|
+
return Promise.resolve();
|
|
6375
|
+
}),
|
|
6376
|
+
getCurrentPassphraseProtection: () => true,
|
|
6377
|
+
getInternalState: () => 'hidden-session',
|
|
6378
|
+
clearInternalState: jest.fn(),
|
|
6379
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6380
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6381
|
+
};
|
|
6382
|
+
|
|
6383
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6384
|
+
message: 'ok',
|
|
6385
|
+
});
|
|
6386
|
+
expect(calls).toEqual([
|
|
6387
|
+
'run-1',
|
|
6388
|
+
'unlock',
|
|
6389
|
+
'negotiate-session',
|
|
6390
|
+
'resume-hidden-session',
|
|
6391
|
+
'validate-hidden-session',
|
|
6392
|
+
'run-2',
|
|
6393
|
+
]);
|
|
6394
|
+
});
|
|
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
|
+
};
|
|
6810
6427
|
|
|
6811
|
-
expect(method
|
|
6428
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(restoreError);
|
|
6429
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6430
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6431
|
+
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
+
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6812
6433
|
});
|
|
6813
6434
|
|
|
6814
|
-
test('
|
|
6435
|
+
test('restores the expected hidden-wallet session after pre-unlock without selecting Attach PIN', async () => {
|
|
6815
6436
|
const calls: string[] = [];
|
|
6816
|
-
const features = { unlocked: true };
|
|
6817
6437
|
const method = {
|
|
6818
|
-
name: '
|
|
6819
|
-
|
|
6438
|
+
name: 'evmSignMessage',
|
|
6439
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6820
6440
|
useDevicePassphraseState: true,
|
|
6821
|
-
|
|
6441
|
+
unlockPolicy: 'retry-on-locked',
|
|
6822
6442
|
run: jest.fn(() => {
|
|
6823
6443
|
calls.push('run');
|
|
6824
|
-
return Promise.resolve({
|
|
6444
|
+
return Promise.resolve({ message: 'ok' });
|
|
6825
6445
|
}),
|
|
6826
6446
|
};
|
|
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
|
+
};
|
|
6827
6469
|
const device = {
|
|
6828
6470
|
features,
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
calls.push('status');
|
|
6832
|
-
return Promise.resolve({ message: { unlocked: true } });
|
|
6833
|
-
}),
|
|
6834
|
-
},
|
|
6471
|
+
passphraseState: 'hidden-state',
|
|
6472
|
+
commands: { typedCall },
|
|
6835
6473
|
isProtocolV2: () => true,
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
features.
|
|
6840
|
-
return
|
|
6474
|
+
unlockDevice: jest.fn(() => {
|
|
6475
|
+
calls.push('unlock-main');
|
|
6476
|
+
features.unlocked = true;
|
|
6477
|
+
features.unlockedAttachPin = false;
|
|
6478
|
+
return Promise.resolve();
|
|
6841
6479
|
}),
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
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')),
|
|
6847
6485
|
};
|
|
6848
6486
|
|
|
6849
|
-
await expect(
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
).
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6487
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6488
|
+
message: 'ok',
|
|
6489
|
+
});
|
|
6490
|
+
expect(device.unlockDevice).toHaveBeenCalledWith();
|
|
6491
|
+
expect(calls).toEqual([
|
|
6492
|
+
'unlock-main',
|
|
6493
|
+
'negotiate-session',
|
|
6494
|
+
'resume-hidden-session',
|
|
6495
|
+
'validate-hidden-session',
|
|
6496
|
+
'run',
|
|
6497
|
+
]);
|
|
6859
6498
|
});
|
|
6860
6499
|
|
|
6861
|
-
test('
|
|
6862
|
-
const calls: string[] = [];
|
|
6863
|
-
const identityError = new Error('Unexpected device');
|
|
6500
|
+
test('does not restore a hidden-wallet session for a standard-wallet pre-unlock', async () => {
|
|
6864
6501
|
const method = {
|
|
6865
|
-
name: '
|
|
6866
|
-
|
|
6502
|
+
name: 'evmGetAddress',
|
|
6503
|
+
payload: { useEmptyPassphrase: true },
|
|
6867
6504
|
useDevicePassphraseState: true,
|
|
6868
|
-
|
|
6505
|
+
unlockPolicy: 'retry-on-locked',
|
|
6506
|
+
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6869
6507
|
};
|
|
6870
|
-
const features = { unlocked: false };
|
|
6871
6508
|
const device = {
|
|
6872
|
-
features,
|
|
6873
|
-
|
|
6874
|
-
typedCall: jest.fn(() => {
|
|
6875
|
-
calls.push('status');
|
|
6876
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6877
|
-
}),
|
|
6878
|
-
},
|
|
6509
|
+
features: { unlocked: false },
|
|
6510
|
+
passphraseState: 'stale-hidden-state',
|
|
6879
6511
|
isProtocolV2: () => true,
|
|
6880
|
-
|
|
6881
|
-
isRomloader: () => false,
|
|
6882
|
-
updateProtocolV2Status: jest.fn(() => features),
|
|
6883
|
-
unlockDevice: jest.fn(),
|
|
6512
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6884
6513
|
};
|
|
6885
6514
|
|
|
6886
|
-
await expect(
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
},
|
|
6892
|
-
})
|
|
6893
|
-
).rejects.toBe(identityError);
|
|
6894
|
-
|
|
6895
|
-
expect(calls).toEqual(['status', 'identity']);
|
|
6896
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6897
|
-
expect(method.run).not.toHaveBeenCalled();
|
|
6515
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6516
|
+
address: 'standard-wallet-address',
|
|
6517
|
+
});
|
|
6518
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6519
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6898
6520
|
});
|
|
6899
6521
|
|
|
6900
|
-
test('unlocks before
|
|
6522
|
+
test('refreshes status and unlocks before a protected Protocol V2 method', async () => {
|
|
6901
6523
|
const calls: string[] = [];
|
|
6902
|
-
const features = { unlocked: true };
|
|
6903
6524
|
const method = {
|
|
6904
|
-
name: '
|
|
6525
|
+
name: 'deviceSettingsPageShow',
|
|
6905
6526
|
unlockPolicy: 'unlock-before-run',
|
|
6906
|
-
protocolV2UiInteraction: { reason: '
|
|
6527
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6907
6528
|
run: jest.fn(() => {
|
|
6908
6529
|
calls.push('run');
|
|
6909
|
-
return Promise.resolve({
|
|
6530
|
+
return Promise.resolve({ message: 'ok' });
|
|
6910
6531
|
}),
|
|
6911
6532
|
};
|
|
6533
|
+
const features = { unlocked: true };
|
|
6912
6534
|
const device = {
|
|
6913
6535
|
features,
|
|
6914
6536
|
commands: {
|
|
6915
6537
|
typedCall: jest.fn(() => {
|
|
6916
|
-
calls.push('status');
|
|
6538
|
+
calls.push('refresh-status');
|
|
6917
6539
|
return Promise.resolve({ message: { unlocked: false } });
|
|
6918
6540
|
}),
|
|
6919
6541
|
},
|
|
6920
6542
|
isProtocolV2: () => true,
|
|
6921
|
-
isBootloader: () => false,
|
|
6922
|
-
isRomloader: () => false,
|
|
6923
6543
|
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6924
|
-
features.unlocked = status.unlocked
|
|
6544
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6925
6545
|
return features;
|
|
6926
6546
|
}),
|
|
6927
6547
|
unlockDevice: jest.fn(() => {
|
|
6928
6548
|
calls.push('unlock');
|
|
6929
6549
|
features.unlocked = true;
|
|
6930
|
-
return Promise.resolve(
|
|
6550
|
+
return Promise.resolve();
|
|
6931
6551
|
}),
|
|
6932
6552
|
};
|
|
6933
6553
|
const uiCoordinator = {
|
|
6934
6554
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6935
|
-
enterUnlockInteraction: jest.fn(() =>
|
|
6936
|
-
|
|
6937
|
-
return undefined;
|
|
6938
|
-
}),
|
|
6555
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6556
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6939
6557
|
};
|
|
6940
6558
|
|
|
6941
6559
|
await expect(
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
return Promise.resolve();
|
|
6947
|
-
},
|
|
6948
|
-
})
|
|
6949
|
-
).resolves.toEqual({ signature: 'signed' });
|
|
6950
|
-
|
|
6951
|
-
expect(calls).toEqual([
|
|
6952
|
-
'status',
|
|
6953
|
-
'unlock-prompt',
|
|
6954
|
-
'unlock',
|
|
6955
|
-
'wallet-session',
|
|
6956
|
-
'method-prompt',
|
|
6957
|
-
'run',
|
|
6958
|
-
]);
|
|
6959
|
-
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6560
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
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', {});
|
|
6960
6564
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6961
6567
|
});
|
|
6962
6568
|
|
|
6963
|
-
test('
|
|
6964
|
-
const
|
|
6965
|
-
name: 'evmGetAddress',
|
|
6966
|
-
unlockPolicy: 'unlock-before-run',
|
|
6967
|
-
run: jest.fn(),
|
|
6968
|
-
};
|
|
6969
|
-
const device = {
|
|
6970
|
-
features: { unlocked: undefined },
|
|
6971
|
-
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6972
|
-
isProtocolV2: () => true,
|
|
6973
|
-
isBootloader: () => false,
|
|
6974
|
-
isRomloader: () => false,
|
|
6975
|
-
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6976
|
-
unlockDevice: jest.fn(),
|
|
6977
|
-
};
|
|
6978
|
-
|
|
6979
|
-
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6980
|
-
errorCode: HardwareErrorCode.RuntimeError,
|
|
6981
|
-
});
|
|
6982
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6983
|
-
expect(method.run).not.toHaveBeenCalled();
|
|
6984
|
-
});
|
|
6985
|
-
|
|
6986
|
-
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6987
|
-
const error = deviceLockedError();
|
|
6569
|
+
test('uses refreshed unlocked status instead of a stale locked cache', async () => {
|
|
6570
|
+
const features = { unlocked: false };
|
|
6988
6571
|
const method = {
|
|
6989
|
-
name: '
|
|
6572
|
+
name: 'deviceSettings',
|
|
6990
6573
|
unlockPolicy: 'unlock-before-run',
|
|
6991
|
-
run: jest.fn().
|
|
6574
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6992
6575
|
};
|
|
6993
|
-
const features = { unlocked: true };
|
|
6994
6576
|
const device = {
|
|
6995
6577
|
features,
|
|
6996
|
-
commands: {
|
|
6578
|
+
commands: {
|
|
6579
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
+
},
|
|
6997
6581
|
isProtocolV2: () => true,
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
6582
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6583
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6584
|
+
return features;
|
|
6585
|
+
}),
|
|
7001
6586
|
unlockDevice: jest.fn(),
|
|
7002
6587
|
};
|
|
7003
6588
|
|
|
7004
|
-
await expect(
|
|
6589
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6590
|
+
message: 'ok',
|
|
6591
|
+
});
|
|
7005
6592
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7006
6593
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7007
6594
|
});
|
|
7008
6595
|
|
|
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',
|
|
7014
|
-
unlockPolicy: 'unlock-before-run',
|
|
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' }),
|
|
7021
|
-
};
|
|
7022
|
-
const device = {
|
|
7023
|
-
features,
|
|
7024
|
-
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
7025
|
-
isProtocolV2: () => true,
|
|
7026
|
-
isBootloader: () => false,
|
|
7027
|
-
isRomloader: () => false,
|
|
7028
|
-
updateProtocolV2Status: jest.fn(() => features),
|
|
7029
|
-
unlockDevice: jest.fn(),
|
|
7030
|
-
};
|
|
7031
|
-
|
|
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);
|
|
7038
|
-
});
|
|
7039
|
-
|
|
7040
6596
|
test.each(['bootloader', 'romloader'] as const)(
|
|
7041
6597
|
'skips DeviceStatus and unlock in Protocol V2 %s mode',
|
|
7042
6598
|
async mode => {
|
|
@@ -7054,7 +6610,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7054
6610
|
unlockDevice: jest.fn(),
|
|
7055
6611
|
};
|
|
7056
6612
|
|
|
7057
|
-
await expect(
|
|
6613
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
7058
6614
|
message: 'updating',
|
|
7059
6615
|
});
|
|
7060
6616
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -7063,30 +6619,157 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7063
6619
|
}
|
|
7064
6620
|
);
|
|
7065
6621
|
|
|
7066
|
-
test('
|
|
6622
|
+
test('infers a signing interaction before running a Protocol V2 business method', async () => {
|
|
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 () => {
|
|
7067
6653
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
7068
|
-
[false, '
|
|
6654
|
+
[false, 'retry-on-locked'],
|
|
7069
6655
|
[true, 'none'],
|
|
7070
6656
|
] as const) {
|
|
6657
|
+
const error = deviceLockedError();
|
|
7071
6658
|
const method = {
|
|
7072
6659
|
unlockPolicy,
|
|
7073
|
-
|
|
7074
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6660
|
+
run: jest.fn().mockRejectedValue(error),
|
|
7075
6661
|
};
|
|
7076
6662
|
const device = {
|
|
7077
|
-
commands: { typedCall: jest.fn() },
|
|
7078
6663
|
isProtocolV2: () => isProtocolV2,
|
|
7079
6664
|
unlockDevice: jest.fn(),
|
|
7080
6665
|
};
|
|
7081
6666
|
|
|
7082
|
-
await expect(
|
|
7083
|
-
message: 'ok',
|
|
7084
|
-
});
|
|
7085
|
-
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6667
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
7086
6668
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7087
6669
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7088
6670
|
}
|
|
7089
6671
|
});
|
|
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
|
+
});
|
|
7090
6773
|
});
|
|
7091
6774
|
|
|
7092
6775
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -7218,9 +6901,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7218
6901
|
|
|
7219
6902
|
await method.run();
|
|
7220
6903
|
|
|
7221
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7222
|
-
eventless_wallet_session: true,
|
|
7223
|
-
});
|
|
6904
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
7224
6905
|
});
|
|
7225
6906
|
|
|
7226
6907
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -7296,29 +6977,6 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7296
6977
|
|
|
7297
6978
|
await expect(method.run()).resolves.toBe(features);
|
|
7298
6979
|
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
|
-
});
|
|
7322
6980
|
});
|
|
7323
6981
|
|
|
7324
6982
|
test.each([
|