@onekeyfe/hd-core 1.2.0-alpha.53 → 1.2.0-alpha.54
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 -78
- 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__/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__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +0 -19
- package/__tests__/open-wallet-session.test.ts +33 -446
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -201
- package/__tests__/protocol-v2.test.ts +1034 -1378
- 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/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 +3497 -2559
- 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.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 -5
- 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 +965 -566
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -54
- package/src/api/SearchDevices.ts +1 -3
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
- 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 +4 -11
- package/src/api/index.ts +0 -2
- 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 +38 -67
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +42 -258
- 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 +3 -10
- 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 +30 -180
- 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 -284
- 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 -7
- package/src/utils/pro2Nft.ts +0 -142
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -5,7 +5,6 @@ import {
|
|
|
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', () => {
|
|
@@ -2881,9 +2681,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2881
2681
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2882
2682
|
}
|
|
2883
2683
|
);
|
|
2884
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2885
|
-
eventless_wallet_session: true,
|
|
2886
|
-
});
|
|
2684
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2887
2685
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2888
2686
|
});
|
|
2889
2687
|
|
|
@@ -2921,9 +2719,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2921
2719
|
}),
|
|
2922
2720
|
expect.anything()
|
|
2923
2721
|
);
|
|
2924
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2925
|
-
eventless_wallet_session: true,
|
|
2926
|
-
});
|
|
2722
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2927
2723
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2928
2724
|
expect(device.features).toMatchObject({
|
|
2929
2725
|
deviceId: null,
|
|
@@ -3189,6 +2985,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3189
2985
|
fw: { application: { version: '1.2.4' } },
|
|
3190
2986
|
},
|
|
3191
2987
|
})
|
|
2988
|
+
.mockResolvedValueOnce({
|
|
2989
|
+
type: 'ProtocolInfo',
|
|
2990
|
+
message: protocolV2ApplicationInfo,
|
|
2991
|
+
})
|
|
3192
2992
|
.mockResolvedValueOnce({
|
|
3193
2993
|
type: 'DeviceStatus',
|
|
3194
2994
|
message: { init_states: true, unlocked: true },
|
|
@@ -3210,7 +3010,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3210
3010
|
firmwareVersion: '1.2.4',
|
|
3211
3011
|
passphraseProtection: null,
|
|
3212
3012
|
});
|
|
3213
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3013
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
3214
3014
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3215
3015
|
4,
|
|
3216
3016
|
'DeviceInfoGet',
|
|
@@ -3324,23 +3124,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3324
3124
|
status: { init_states: true },
|
|
3325
3125
|
}
|
|
3326
3126
|
);
|
|
3327
|
-
const pinOnDevice = jest.fn();
|
|
3328
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3329
3127
|
|
|
3330
3128
|
const features = await device.unlockDevice();
|
|
3331
3129
|
|
|
3332
|
-
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3333
|
-
device,
|
|
3334
|
-
DeviceSessionPinType.Main,
|
|
3335
|
-
expect.objectContaining({
|
|
3336
|
-
source: 'unlock-coordinator',
|
|
3337
|
-
reason: 'device-unlock',
|
|
3338
|
-
deviceOnly: true,
|
|
3339
|
-
})
|
|
3340
|
-
);
|
|
3341
|
-
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3342
|
-
typedCall.mock.invocationCallOrder[0]
|
|
3343
|
-
);
|
|
3344
3130
|
expect(typedCall.mock.calls).toEqual([
|
|
3345
3131
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3346
3132
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3386,12 +3172,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3386
3172
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3387
3173
|
});
|
|
3388
3174
|
(device as any).commands = { typedCall };
|
|
3389
|
-
const pinOnDevice = jest.fn();
|
|
3390
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3391
3175
|
|
|
3392
|
-
await device.unlockDevice(
|
|
3176
|
+
await device.unlockDevice();
|
|
3393
3177
|
|
|
3394
|
-
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3395
3178
|
expect(typedCall.mock.calls).toEqual([
|
|
3396
3179
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3397
3180
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3794,15 +3577,6 @@ describe('API compatibility handling', () => {
|
|
|
3794
3577
|
|
|
3795
3578
|
describe('Protocol V2 firmware update targets', () => {
|
|
3796
3579
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3797
|
-
let forceReloadDataSpy: jest.SpyInstance<Promise<void>, []>;
|
|
3798
|
-
|
|
3799
|
-
beforeEach(() => {
|
|
3800
|
-
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3801
|
-
});
|
|
3802
|
-
|
|
3803
|
-
afterEach(() => {
|
|
3804
|
-
jest.restoreAllMocks();
|
|
3805
|
-
});
|
|
3806
3580
|
|
|
3807
3581
|
const buildOkppHeader = ({
|
|
3808
3582
|
type = 'RESC',
|
|
@@ -3927,7 +3701,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3927
3701
|
specific: true,
|
|
3928
3702
|
},
|
|
3929
3703
|
},
|
|
3930
|
-
{ timeoutMs:
|
|
3704
|
+
{ timeoutMs: 5000 }
|
|
3931
3705
|
);
|
|
3932
3706
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3933
3707
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3943,7 +3717,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3943
3717
|
});
|
|
3944
3718
|
});
|
|
3945
3719
|
|
|
3946
|
-
test('
|
|
3720
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
3947
3721
|
const method = new FirmwareUpdateV4({
|
|
3948
3722
|
id: 1,
|
|
3949
3723
|
payload: {
|
|
@@ -3978,7 +3752,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3978
3752
|
|
|
3979
3753
|
await method.run();
|
|
3980
3754
|
|
|
3981
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3755
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3982
3756
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3983
3757
|
fwBinaryMap: [
|
|
3984
3758
|
{
|
|
@@ -3989,69 +3763,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3989
3763
|
],
|
|
3990
3764
|
bootloaderBinary: null,
|
|
3991
3765
|
});
|
|
3992
|
-
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
3993
|
-
});
|
|
3994
|
-
|
|
3995
|
-
test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
|
|
3996
|
-
const method = new FirmwareUpdateV4({
|
|
3997
|
-
id: 1,
|
|
3998
|
-
payload: {
|
|
3999
|
-
method: 'firmwareUpdateV4',
|
|
4000
|
-
platform: 'web',
|
|
4001
|
-
targetsToUpdate: ['resource'],
|
|
4002
|
-
},
|
|
4003
|
-
});
|
|
4004
|
-
method.init();
|
|
4005
|
-
|
|
4006
|
-
(method as any).device = stubDevice({
|
|
4007
|
-
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4008
|
-
features: {
|
|
4009
|
-
deviceType: 'pro2',
|
|
4010
|
-
firmwareVersion: '1.0.0',
|
|
4011
|
-
mode: 'normal',
|
|
4012
|
-
bootloaderMode: false,
|
|
4013
|
-
capabilities: [],
|
|
4014
|
-
},
|
|
4015
|
-
isBootloader: () => false,
|
|
4016
|
-
isRomloader: () => false,
|
|
4017
|
-
});
|
|
4018
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4019
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4020
|
-
bootloaderBinary: null,
|
|
4021
|
-
fwBinaryMap: [],
|
|
4022
|
-
installItems: [],
|
|
4023
|
-
});
|
|
4024
|
-
|
|
4025
|
-
const order: string[] = [];
|
|
4026
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4027
|
-
order.push('enter-bootloader');
|
|
4028
|
-
return Promise.resolve(true);
|
|
4029
|
-
});
|
|
4030
|
-
(method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
|
|
4031
|
-
order.push('prepare-resources');
|
|
4032
|
-
return Promise.resolve([
|
|
4033
|
-
{
|
|
4034
|
-
name: 'images.okpkg',
|
|
4035
|
-
binary: new Uint8Array([1]).buffer,
|
|
4036
|
-
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
4037
|
-
},
|
|
4038
|
-
]);
|
|
4039
|
-
});
|
|
4040
|
-
(method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
|
|
4041
|
-
order.push('execute-update');
|
|
4042
|
-
return Promise.resolve();
|
|
4043
|
-
});
|
|
4044
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4045
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
4046
|
-
bootloaderVersion: '1.0.0',
|
|
4047
|
-
bleVersion: '1.0.0',
|
|
4048
|
-
firmwareVersion: '1.0.0',
|
|
4049
|
-
});
|
|
4050
|
-
method.postTipMessage = jest.fn();
|
|
4051
|
-
|
|
4052
|
-
await method.run();
|
|
4053
|
-
|
|
4054
|
-
expect(order).toEqual(['enter-bootloader', 'prepare-resources', 'execute-update']);
|
|
4055
3766
|
});
|
|
4056
3767
|
|
|
4057
3768
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -4103,33 +3814,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4103
3814
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4104
3815
|
});
|
|
4105
3816
|
|
|
4106
|
-
test('keeps Protocol V2 bootloader active before firmware transfer', async () => {
|
|
4107
|
-
const method = new FirmwareUpdateV4({
|
|
4108
|
-
id: 1,
|
|
4109
|
-
payload: {
|
|
4110
|
-
method: 'firmwareUpdateV4',
|
|
4111
|
-
},
|
|
4112
|
-
});
|
|
4113
|
-
(method as any).device = stubDevice({
|
|
4114
|
-
originalDescriptor: { id: 'usb-id', path: 'bootloader-path', protocolType: 'V2' },
|
|
4115
|
-
features: {
|
|
4116
|
-
deviceType: 'pro2',
|
|
4117
|
-
mode: 'bootloader',
|
|
4118
|
-
bootloaderMode: true,
|
|
4119
|
-
capabilities: [],
|
|
4120
|
-
},
|
|
4121
|
-
isBootloader: () => true,
|
|
4122
|
-
isRomloader: () => false,
|
|
4123
|
-
});
|
|
4124
|
-
(method as any).protocolV2Reboot = jest.fn();
|
|
4125
|
-
method.postTipMessage = jest.fn();
|
|
4126
|
-
|
|
4127
|
-
await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
|
|
4128
|
-
|
|
4129
|
-
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4130
|
-
expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
4131
|
-
});
|
|
4132
|
-
|
|
4133
3817
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
4134
3818
|
const method = new FirmwareUpdateV4({
|
|
4135
3819
|
id: 1,
|
|
@@ -4182,9 +3866,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4182
3866
|
},
|
|
4183
3867
|
});
|
|
4184
3868
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4185
|
-
const prepareResourceBundles = jest
|
|
4186
|
-
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4187
|
-
.mockResolvedValue(undefined);
|
|
4188
3869
|
(method as any).protocolV2Reboot = jest.fn();
|
|
4189
3870
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
4190
3871
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -4197,7 +3878,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4197
3878
|
|
|
4198
3879
|
await method.run();
|
|
4199
3880
|
|
|
4200
|
-
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
4201
3881
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4202
3882
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
4203
3883
|
bootloaderBinary: null,
|
|
@@ -4280,10 +3960,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4280
3960
|
getCommands: () => commands,
|
|
4281
3961
|
mainId: 'usb-path',
|
|
4282
3962
|
});
|
|
4283
|
-
const cachedDevice = {
|
|
4284
|
-
getConnectId: () => 'usb-path',
|
|
4285
|
-
getCurrentSerialNo: () => '',
|
|
4286
|
-
};
|
|
3963
|
+
const cachedDevice = { getConnectId: () => 'usb-path' };
|
|
4287
3964
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4288
3965
|
devices: {},
|
|
4289
3966
|
deviceList: [cachedDevice],
|
|
@@ -4300,207 +3977,31 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4300
3977
|
expect(initialize).not.toHaveBeenCalled();
|
|
4301
3978
|
});
|
|
4302
3979
|
|
|
4303
|
-
test('
|
|
3980
|
+
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4304
3981
|
const method = new FirmwareUpdateV4({
|
|
4305
3982
|
id: 1,
|
|
4306
3983
|
payload: {
|
|
4307
3984
|
method: 'firmwareUpdateV4',
|
|
4308
3985
|
},
|
|
4309
3986
|
});
|
|
4310
|
-
const commands = { disposed:
|
|
4311
|
-
const
|
|
3987
|
+
const commands = { disposed: false, mainId: 'usb-session' };
|
|
3988
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
4312
3989
|
const device = stubDevice({
|
|
4313
|
-
originalDescriptor: {
|
|
3990
|
+
originalDescriptor: {
|
|
3991
|
+
id: 'usb-id',
|
|
3992
|
+
path: 'usb-path',
|
|
3993
|
+
session: 'usb-session',
|
|
3994
|
+
protocolType: 'V2',
|
|
3995
|
+
},
|
|
4314
3996
|
deviceConnector: {
|
|
4315
3997
|
enumerate: jest.fn().mockResolvedValue({
|
|
4316
3998
|
descriptors: [
|
|
4317
|
-
{
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
hasDeviceAcquire: jest.fn(() => false),
|
|
4324
|
-
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4325
|
-
commands,
|
|
4326
|
-
getCommands: () => commands,
|
|
4327
|
-
mainId: 'old-usb-path',
|
|
4328
|
-
});
|
|
4329
|
-
const otherDevice = {
|
|
4330
|
-
getConnectId: () => 'usb-a',
|
|
4331
|
-
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4332
|
-
};
|
|
4333
|
-
const expectedDevice = {
|
|
4334
|
-
getConnectId: () => 'usb-b',
|
|
4335
|
-
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4336
|
-
};
|
|
4337
|
-
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4338
|
-
devices: {},
|
|
4339
|
-
deviceList: [otherDevice, expectedDevice],
|
|
4340
|
-
} as any);
|
|
4341
|
-
(method as any).device = device;
|
|
4342
|
-
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4343
|
-
|
|
4344
|
-
try {
|
|
4345
|
-
await (method as any).reconnectProtocolV2Device();
|
|
4346
|
-
} finally {
|
|
4347
|
-
getDevices.mockRestore();
|
|
4348
|
-
}
|
|
4349
|
-
|
|
4350
|
-
expect(updateFromCache).toHaveBeenCalledWith(expectedDevice);
|
|
4351
|
-
});
|
|
4352
|
-
|
|
4353
|
-
test('rejects ambiguous Protocol V2 USB reconnect candidates without an identity match', async () => {
|
|
4354
|
-
const method = new FirmwareUpdateV4({
|
|
4355
|
-
id: 1,
|
|
4356
|
-
payload: {
|
|
4357
|
-
method: 'firmwareUpdateV4',
|
|
4358
|
-
},
|
|
4359
|
-
});
|
|
4360
|
-
const updateFromCache = jest.fn();
|
|
4361
|
-
const device = stubDevice({
|
|
4362
|
-
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4363
|
-
deviceConnector: {
|
|
4364
|
-
enumerate: jest.fn().mockResolvedValue({
|
|
4365
|
-
descriptors: [
|
|
4366
|
-
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4367
|
-
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4368
|
-
],
|
|
4369
|
-
}),
|
|
4370
|
-
},
|
|
4371
|
-
updateFromCache,
|
|
4372
|
-
});
|
|
4373
|
-
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4374
|
-
devices: {},
|
|
4375
|
-
deviceList: [
|
|
4376
|
-
{
|
|
4377
|
-
getConnectId: () => 'usb-a',
|
|
4378
|
-
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4379
|
-
},
|
|
4380
|
-
{
|
|
4381
|
-
getConnectId: () => 'usb-b',
|
|
4382
|
-
getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
|
|
4383
|
-
},
|
|
4384
|
-
],
|
|
4385
|
-
} as any);
|
|
4386
|
-
(method as any).device = device;
|
|
4387
|
-
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-C';
|
|
4388
|
-
|
|
4389
|
-
try {
|
|
4390
|
-
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4391
|
-
} finally {
|
|
4392
|
-
getDevices.mockRestore();
|
|
4393
|
-
}
|
|
4394
|
-
|
|
4395
|
-
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4396
|
-
});
|
|
4397
|
-
|
|
4398
|
-
test('allows a single Protocol V2 USB reconnect candidate when its physical serial is unavailable', async () => {
|
|
4399
|
-
const method = new FirmwareUpdateV4({
|
|
4400
|
-
id: 1,
|
|
4401
|
-
payload: {
|
|
4402
|
-
method: 'firmwareUpdateV4',
|
|
4403
|
-
},
|
|
4404
|
-
});
|
|
4405
|
-
const commands = { disposed: true, mainId: '' };
|
|
4406
|
-
const updateFromCache = jest.fn();
|
|
4407
|
-
const device = stubDevice({
|
|
4408
|
-
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4409
|
-
deviceConnector: {
|
|
4410
|
-
enumerate: jest.fn().mockResolvedValue({
|
|
4411
|
-
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4412
|
-
}),
|
|
4413
|
-
},
|
|
4414
|
-
updateFromCache,
|
|
4415
|
-
hasDeviceAcquire: jest.fn(() => false),
|
|
4416
|
-
acquire: jest.fn().mockResolvedValue(undefined),
|
|
4417
|
-
commands,
|
|
4418
|
-
getCommands: () => commands,
|
|
4419
|
-
mainId: 'old-usb-path',
|
|
4420
|
-
});
|
|
4421
|
-
const candidate = {
|
|
4422
|
-
getConnectId: () => 'usb-a',
|
|
4423
|
-
getCurrentSerialNo: () => '',
|
|
4424
|
-
};
|
|
4425
|
-
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4426
|
-
devices: {},
|
|
4427
|
-
deviceList: [candidate],
|
|
4428
|
-
} as any);
|
|
4429
|
-
(method as any).device = device;
|
|
4430
|
-
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-A';
|
|
4431
|
-
|
|
4432
|
-
try {
|
|
4433
|
-
await (method as any).reconnectProtocolV2Device();
|
|
4434
|
-
} finally {
|
|
4435
|
-
getDevices.mockRestore();
|
|
4436
|
-
}
|
|
4437
|
-
|
|
4438
|
-
expect(updateFromCache).toHaveBeenCalledWith(candidate);
|
|
4439
|
-
});
|
|
4440
|
-
|
|
4441
|
-
test('rejects a single Protocol V2 USB reconnect candidate with a different physical serial', async () => {
|
|
4442
|
-
const method = new FirmwareUpdateV4({
|
|
4443
|
-
id: 1,
|
|
4444
|
-
payload: {
|
|
4445
|
-
method: 'firmwareUpdateV4',
|
|
4446
|
-
},
|
|
4447
|
-
});
|
|
4448
|
-
const updateFromCache = jest.fn();
|
|
4449
|
-
const device = stubDevice({
|
|
4450
|
-
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
4451
|
-
deviceConnector: {
|
|
4452
|
-
enumerate: jest.fn().mockResolvedValue({
|
|
4453
|
-
descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
|
|
4454
|
-
}),
|
|
4455
|
-
},
|
|
4456
|
-
updateFromCache,
|
|
4457
|
-
});
|
|
4458
|
-
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
4459
|
-
devices: {},
|
|
4460
|
-
deviceList: [
|
|
4461
|
-
{
|
|
4462
|
-
getConnectId: () => 'usb-a',
|
|
4463
|
-
getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
|
|
4464
|
-
},
|
|
4465
|
-
],
|
|
4466
|
-
} as any);
|
|
4467
|
-
(method as any).device = device;
|
|
4468
|
-
(method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
|
|
4469
|
-
|
|
4470
|
-
try {
|
|
4471
|
-
await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
|
|
4472
|
-
} finally {
|
|
4473
|
-
getDevices.mockRestore();
|
|
4474
|
-
}
|
|
4475
|
-
|
|
4476
|
-
expect(updateFromCache).not.toHaveBeenCalled();
|
|
4477
|
-
});
|
|
4478
|
-
|
|
4479
|
-
test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
|
|
4480
|
-
const method = new FirmwareUpdateV4({
|
|
4481
|
-
id: 1,
|
|
4482
|
-
payload: {
|
|
4483
|
-
method: 'firmwareUpdateV4',
|
|
4484
|
-
},
|
|
4485
|
-
});
|
|
4486
|
-
const commands = { disposed: false, mainId: 'usb-session' };
|
|
4487
|
-
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
4488
|
-
const device = stubDevice({
|
|
4489
|
-
originalDescriptor: {
|
|
4490
|
-
id: 'usb-id',
|
|
4491
|
-
path: 'usb-path',
|
|
4492
|
-
session: 'usb-session',
|
|
4493
|
-
protocolType: 'V2',
|
|
4494
|
-
},
|
|
4495
|
-
deviceConnector: {
|
|
4496
|
-
enumerate: jest.fn().mockResolvedValue({
|
|
4497
|
-
descriptors: [
|
|
4498
|
-
{
|
|
4499
|
-
id: 'usb-id',
|
|
4500
|
-
path: 'usb-path',
|
|
4501
|
-
session: 'usb-session',
|
|
4502
|
-
protocolType: 'V2',
|
|
4503
|
-
},
|
|
3999
|
+
{
|
|
4000
|
+
id: 'usb-id',
|
|
4001
|
+
path: 'usb-path',
|
|
4002
|
+
session: 'usb-session',
|
|
4003
|
+
protocolType: 'V2',
|
|
4004
|
+
},
|
|
4504
4005
|
],
|
|
4505
4006
|
}),
|
|
4506
4007
|
},
|
|
@@ -4570,9 +4071,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4570
4071
|
});
|
|
4571
4072
|
method.postTipMessage = jest.fn();
|
|
4572
4073
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4573
|
-
|
|
4574
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4575
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4074
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4576
4075
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4577
4076
|
message: 'Device rebooted successfully',
|
|
4578
4077
|
});
|
|
@@ -4581,29 +4080,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4581
4080
|
|
|
4582
4081
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4583
4082
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4584
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4585
4083
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4586
4084
|
});
|
|
4587
4085
|
|
|
4588
|
-
test('skips a second Normal reboot after Pro2 already returned to App mode', async () => {
|
|
4589
|
-
const method = new FirmwareUpdateV4({
|
|
4590
|
-
id: 1,
|
|
4591
|
-
payload: {
|
|
4592
|
-
method: 'firmwareUpdateV4',
|
|
4593
|
-
},
|
|
4594
|
-
});
|
|
4595
|
-
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4596
|
-
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4597
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4598
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4599
|
-
(method as any).protocolV2Reboot = jest.fn();
|
|
4600
|
-
|
|
4601
|
-
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
4602
|
-
|
|
4603
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4604
|
-
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4605
|
-
});
|
|
4606
|
-
|
|
4607
4086
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4608
4087
|
const method = new FirmwareUpdateV4({
|
|
4609
4088
|
id: 1,
|
|
@@ -4646,27 +4125,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4646
4125
|
});
|
|
4647
4126
|
});
|
|
4648
4127
|
|
|
4649
|
-
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
4650
|
-
const method = new FirmwareUpdateV4({
|
|
4651
|
-
id: 1,
|
|
4652
|
-
payload: {
|
|
4653
|
-
method: 'firmwareUpdateV4',
|
|
4654
|
-
},
|
|
4655
|
-
});
|
|
4656
|
-
const typedCall = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
4657
|
-
|
|
4658
|
-
(method as any).device = stubDevice({
|
|
4659
|
-
getCommands: () => ({ typedCall }),
|
|
4660
|
-
});
|
|
4661
|
-
|
|
4662
|
-
await expect((method as any).protocolV2Reboot(DeviceRebootType.Bootloader)).resolves.toEqual({
|
|
4663
|
-
message: 'Device rebooted successfully',
|
|
4664
|
-
});
|
|
4665
|
-
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
4666
|
-
DeviceRebootType.Bootloader
|
|
4667
|
-
);
|
|
4668
|
-
});
|
|
4669
|
-
|
|
4670
4128
|
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4671
4129
|
const method = new FirmwareUpdateV4({
|
|
4672
4130
|
id: 1,
|
|
@@ -4677,9 +4135,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
4135
|
const typedCall = jest
|
|
4678
4136
|
.fn()
|
|
4679
4137
|
.mockRejectedValue(
|
|
4680
|
-
|
|
4681
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4682
|
-
'NotFoundError'
|
|
4138
|
+
Object.assign(
|
|
4139
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4140
|
+
{ name: 'NotFoundError' }
|
|
4683
4141
|
)
|
|
4684
4142
|
);
|
|
4685
4143
|
|
|
@@ -4757,7 +4215,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4757
4215
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4758
4216
|
]);
|
|
4759
4217
|
|
|
4760
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4218
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
4761
4219
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4762
4220
|
'DeviceFirmwareUpdateStatusGet',
|
|
4763
4221
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4795,46 +4253,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4795
4253
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4796
4254
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4797
4255
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4798
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4799
|
-
});
|
|
4800
|
-
|
|
4801
|
-
test('accepts an empty firmware status dump only after confirming normal mode', async () => {
|
|
4802
|
-
const method = new FirmwareUpdateV4({
|
|
4803
|
-
id: 1,
|
|
4804
|
-
payload: {
|
|
4805
|
-
method: 'firmwareUpdateV4',
|
|
4806
|
-
},
|
|
4807
|
-
});
|
|
4808
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
4809
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
4810
|
-
message: { records: [] },
|
|
4811
|
-
});
|
|
4812
|
-
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4813
|
-
let now = 0;
|
|
4814
|
-
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4815
|
-
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4816
|
-
now += 31 * 1000;
|
|
4817
|
-
callback();
|
|
4818
|
-
return 0 as any;
|
|
4819
|
-
}) as typeof setTimeout);
|
|
4820
|
-
|
|
4821
|
-
(method as any).device = stubDevice({
|
|
4822
|
-
getCommands: () => ({ typedCall }),
|
|
4823
|
-
});
|
|
4824
|
-
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4825
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4826
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4827
|
-
method.postProgressMessage = jest.fn();
|
|
4828
|
-
|
|
4829
|
-
await expect(
|
|
4830
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4831
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4832
|
-
])
|
|
4833
|
-
).resolves.toBeUndefined();
|
|
4834
|
-
|
|
4835
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
4836
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4837
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4838
4256
|
});
|
|
4839
4257
|
|
|
4840
4258
|
test.each([
|
|
@@ -4900,6 +4318,77 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4900
4318
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4901
4319
|
});
|
|
4902
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
|
+
|
|
4903
4392
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4904
4393
|
const method = new FirmwareUpdateV4({
|
|
4905
4394
|
id: 1,
|
|
@@ -4942,152 +4431,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4942
4431
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4943
4432
|
});
|
|
4944
4433
|
|
|
4945
|
-
test('
|
|
4946
|
-
const method = new FirmwareUpdateV4({
|
|
4947
|
-
id: 1,
|
|
4948
|
-
payload: {
|
|
4949
|
-
method: 'firmwareUpdateV4',
|
|
4950
|
-
},
|
|
4951
|
-
});
|
|
4952
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
4953
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
4954
|
-
message: {
|
|
4955
|
-
records: [{ target_id: 4, status: 2 }],
|
|
4956
|
-
},
|
|
4957
|
-
});
|
|
4958
|
-
let now = 0;
|
|
4959
|
-
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
4960
|
-
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
4961
|
-
now += 31 * 1000;
|
|
4962
|
-
callback();
|
|
4963
|
-
return 0 as any;
|
|
4964
|
-
}) as typeof setTimeout);
|
|
4965
|
-
|
|
4966
|
-
(method as any).device = stubDevice({
|
|
4967
|
-
getCommands: () => ({ typedCall }),
|
|
4968
|
-
});
|
|
4969
|
-
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4970
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4971
|
-
method.postProgressMessage = jest.fn();
|
|
4972
|
-
|
|
4973
|
-
await expect(
|
|
4974
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4975
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4976
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4977
|
-
])
|
|
4978
|
-
).rejects.toMatchObject({
|
|
4979
|
-
errorCode: HardwareErrorCode.FirmwareError,
|
|
4980
|
-
message: expect.stringContaining('targetIds=3'),
|
|
4981
|
-
});
|
|
4982
|
-
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4983
|
-
});
|
|
4984
|
-
|
|
4985
|
-
test('resets the missing-record grace period after a Pro2 reboot interruption', async () => {
|
|
4986
|
-
const method = new FirmwareUpdateV4({
|
|
4987
|
-
id: 1,
|
|
4988
|
-
payload: {
|
|
4989
|
-
method: 'firmwareUpdateV4',
|
|
4990
|
-
},
|
|
4991
|
-
});
|
|
4992
|
-
const typedCall = jest
|
|
4993
|
-
.fn()
|
|
4994
|
-
.mockResolvedValueOnce({
|
|
4995
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
4996
|
-
message: { records: [{ target_id: 4, status: 2 }] },
|
|
4997
|
-
})
|
|
4998
|
-
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
4999
|
-
.mockResolvedValueOnce({
|
|
5000
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
5001
|
-
message: { records: [{ target_id: 4, status: 2 }] },
|
|
5002
|
-
})
|
|
5003
|
-
.mockResolvedValueOnce({
|
|
5004
|
-
type: 'DeviceFirmwareUpdateStatus',
|
|
5005
|
-
message: {
|
|
5006
|
-
records: [
|
|
5007
|
-
{ target_id: 3, status: 2 },
|
|
5008
|
-
{ target_id: 4, status: 2 },
|
|
5009
|
-
],
|
|
5010
|
-
},
|
|
5011
|
-
});
|
|
5012
|
-
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5013
|
-
let now = 0;
|
|
5014
|
-
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5015
|
-
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5016
|
-
now += 31 * 1000;
|
|
5017
|
-
callback();
|
|
5018
|
-
return 0 as any;
|
|
5019
|
-
}) as typeof setTimeout);
|
|
5020
|
-
|
|
5021
|
-
(method as any).device = stubDevice({
|
|
5022
|
-
getCommands: () => ({ typedCall }),
|
|
5023
|
-
});
|
|
5024
|
-
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5025
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5026
|
-
method.postProgressMessage = jest.fn();
|
|
5027
|
-
|
|
5028
|
-
await expect(
|
|
5029
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5030
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5031
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5032
|
-
])
|
|
5033
|
-
).resolves.toBeUndefined();
|
|
5034
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
5035
|
-
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
5036
|
-
});
|
|
5037
|
-
|
|
5038
|
-
test('uses an eight-minute Pro2 install status window', async () => {
|
|
5039
|
-
const method = new FirmwareUpdateV4({
|
|
5040
|
-
id: 1,
|
|
5041
|
-
payload: {
|
|
5042
|
-
method: 'firmwareUpdateV4',
|
|
5043
|
-
},
|
|
5044
|
-
});
|
|
5045
|
-
jest
|
|
5046
|
-
.spyOn(Date, 'now')
|
|
5047
|
-
.mockReturnValueOnce(0)
|
|
5048
|
-
.mockReturnValue(8 * 60 * 1000);
|
|
5049
|
-
|
|
5050
|
-
await expect(
|
|
5051
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5052
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5053
|
-
])
|
|
5054
|
-
).rejects.toThrow('within 480s');
|
|
5055
|
-
});
|
|
5056
|
-
|
|
5057
|
-
test('uses a 90-second Pro2 bootloader reconnect window', async () => {
|
|
5058
|
-
const method = new FirmwareUpdateV4({
|
|
5059
|
-
id: 1,
|
|
5060
|
-
payload: {
|
|
5061
|
-
method: 'firmwareUpdateV4',
|
|
5062
|
-
},
|
|
5063
|
-
});
|
|
5064
|
-
jest
|
|
5065
|
-
.spyOn(Date, 'now')
|
|
5066
|
-
.mockReturnValueOnce(0)
|
|
5067
|
-
.mockReturnValue(90 * 1000);
|
|
5068
|
-
|
|
5069
|
-
await expect((method as any).waitForProtocolV2BootloaderMode()).rejects.toThrow('within 90s');
|
|
5070
|
-
});
|
|
5071
|
-
|
|
5072
|
-
test('uses a three-minute Pro2 final reconnect window', async () => {
|
|
5073
|
-
const method = new FirmwareUpdateV4({
|
|
5074
|
-
id: 1,
|
|
5075
|
-
payload: {
|
|
5076
|
-
method: 'firmwareUpdateV4',
|
|
5077
|
-
},
|
|
5078
|
-
});
|
|
5079
|
-
const waitForProtocolV2ReconnectAndFeatures = jest
|
|
5080
|
-
.fn()
|
|
5081
|
-
.mockRejectedValue(new Error('stop after capturing timeout'));
|
|
5082
|
-
(method as any).waitForProtocolV2ReconnectAndFeatures = waitForProtocolV2ReconnectAndFeatures;
|
|
5083
|
-
|
|
5084
|
-
await expect((method as any).waitForProtocolV2FinalFeatures()).rejects.toThrow(
|
|
5085
|
-
'stop after capturing timeout'
|
|
5086
|
-
);
|
|
5087
|
-
expect(waitForProtocolV2ReconnectAndFeatures).toHaveBeenCalledWith(3 * 60 * 1000);
|
|
5088
|
-
});
|
|
5089
|
-
|
|
5090
|
-
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
4434
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
5091
4435
|
const method = new FirmwareUpdateV4({
|
|
5092
4436
|
id: 1,
|
|
5093
4437
|
payload: {
|
|
@@ -5121,7 +4465,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5121
4465
|
).toBe(true);
|
|
5122
4466
|
|
|
5123
4467
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
5124
|
-
expect(
|
|
4468
|
+
expect(() =>
|
|
5125
4469
|
(method as any).assertProtocolV2TargetStatus(
|
|
5126
4470
|
[
|
|
5127
4471
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -5129,8 +4473,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5129
4473
|
],
|
|
5130
4474
|
new Set([4, 10])
|
|
5131
4475
|
)
|
|
5132
|
-
).
|
|
5133
|
-
expect(method.postProgressMessage).
|
|
4476
|
+
).toThrow('Protocol V2 install status conflicts with target 4');
|
|
4477
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5134
4478
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5135
4479
|
|
|
5136
4480
|
expect(
|
|
@@ -5219,7 +4563,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5219
4563
|
path: true,
|
|
5220
4564
|
},
|
|
5221
4565
|
},
|
|
5222
|
-
{ timeoutMs:
|
|
4566
|
+
{ timeoutMs: 5000 }
|
|
5223
4567
|
);
|
|
5224
4568
|
});
|
|
5225
4569
|
|
|
@@ -5234,10 +4578,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5234
4578
|
const writtenPaths: string[] = [];
|
|
5235
4579
|
method.postTipMessage = jest.fn();
|
|
5236
4580
|
method.postProgressMessage = jest.fn();
|
|
5237
|
-
(method as any).
|
|
4581
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
5238
4582
|
writtenPaths.push(params.filePath);
|
|
5239
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4583
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
5240
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({});
|
|
5241
4589
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5242
4590
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5243
4591
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5277,10 +4625,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5277
4625
|
'vol0:/coprocessor.bin',
|
|
5278
4626
|
1
|
|
5279
4627
|
);
|
|
5280
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
5281
|
-
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, {
|
|
5282
4633
|
targets: [
|
|
5283
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
5284
4634
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5285
4635
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
5286
4636
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -5290,7 +4640,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5290
4640
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
5291
4641
|
});
|
|
5292
4642
|
|
|
5293
|
-
test('announces
|
|
4643
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
5294
4644
|
const method = new FirmwareUpdateV4({
|
|
5295
4645
|
id: 1,
|
|
5296
4646
|
payload: {
|
|
@@ -5300,11 +4650,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5300
4650
|
|
|
5301
4651
|
method.postTipMessage = jest.fn();
|
|
5302
4652
|
method.postProgressMessage = jest.fn();
|
|
5303
|
-
(method as any).
|
|
4653
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5304
4654
|
.fn()
|
|
5305
4655
|
.mockImplementation(params =>
|
|
5306
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4656
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5307
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({});
|
|
5308
4661
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5309
4662
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5310
4663
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5324,21 +4677,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5324
4677
|
{
|
|
5325
4678
|
fileName: 'application_p1.bin',
|
|
5326
4679
|
binary: new Uint8Array([3]).buffer,
|
|
5327
|
-
targetId:
|
|
4680
|
+
targetId: 4,
|
|
5328
4681
|
},
|
|
5329
4682
|
],
|
|
5330
4683
|
});
|
|
5331
4684
|
|
|
5332
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4685
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
5333
4686
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
5334
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
5335
|
-
expect(
|
|
4687
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
4688
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
4689
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5336
4690
|
1,
|
|
5337
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4691
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
5338
4692
|
);
|
|
5339
|
-
expect((method as any).
|
|
4693
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5340
4694
|
2,
|
|
5341
|
-
expect.objectContaining({ processedSize:
|
|
4695
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
5342
4696
|
);
|
|
5343
4697
|
});
|
|
5344
4698
|
|
|
@@ -5352,7 +4706,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5352
4706
|
|
|
5353
4707
|
method.postTipMessage = jest.fn();
|
|
5354
4708
|
method.postProgressMessage = jest.fn();
|
|
5355
|
-
(method as any).
|
|
4709
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4710
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5356
4711
|
(method as any).device = stubDevice({
|
|
5357
4712
|
getCommands: () => ({
|
|
5358
4713
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -5398,11 +4753,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5398
4753
|
|
|
5399
4754
|
method.postTipMessage = jest.fn();
|
|
5400
4755
|
method.postProgressMessage = jest.fn();
|
|
5401
|
-
(method as any).
|
|
4756
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
5402
4757
|
.fn()
|
|
5403
4758
|
.mockImplementation(params =>
|
|
5404
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4759
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
5405
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({});
|
|
5406
4764
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5407
4765
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
5408
4766
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5617,42 +4975,33 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5617
4975
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5618
4976
|
});
|
|
5619
4977
|
|
|
5620
|
-
test('
|
|
4978
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
5621
4979
|
const method = new FirmwareUpdateV4({
|
|
5622
4980
|
id: 1,
|
|
5623
4981
|
payload: {
|
|
5624
4982
|
method: 'firmwareUpdateV4',
|
|
5625
4983
|
platform: 'web',
|
|
4984
|
+
targetsToUpdate: ['resource'],
|
|
5626
4985
|
},
|
|
5627
4986
|
});
|
|
5628
4987
|
method.init();
|
|
5629
4988
|
|
|
5630
|
-
const getSysResourceBinarySpy = jest
|
|
5631
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5632
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5633
4989
|
const getFirmwareLatestReleaseSpy = jest
|
|
5634
4990
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5635
4991
|
.mockReturnValue({
|
|
5636
4992
|
required: false,
|
|
5637
4993
|
version: [1, 0, 0],
|
|
5638
|
-
url: '
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
applicationP1: {
|
|
5648
|
-
target: 'APPLICATION_P1',
|
|
5649
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5650
|
-
},
|
|
5651
|
-
applicationP2: {
|
|
5652
|
-
target: 'APPLICATION_P2',
|
|
5653
|
-
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),
|
|
5654
5003
|
},
|
|
5655
|
-
|
|
5004
|
+
],
|
|
5656
5005
|
fingerprint: '',
|
|
5657
5006
|
changelog: {
|
|
5658
5007
|
'zh-CN': '',
|
|
@@ -5660,122 +5009,427 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5660
5009
|
},
|
|
5661
5010
|
});
|
|
5662
5011
|
|
|
5663
|
-
const
|
|
5012
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
5664
5013
|
deviceType: 'pro2',
|
|
5665
5014
|
firmwareVersion: '1.0.0',
|
|
5666
|
-
bootloaderVersion: '1.0.0',
|
|
5667
5015
|
capabilities: [],
|
|
5668
5016
|
});
|
|
5669
5017
|
|
|
5670
|
-
expect(
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
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);
|
|
5676
5030
|
|
|
5677
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5678
5031
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5679
5032
|
});
|
|
5680
5033
|
|
|
5681
|
-
test('
|
|
5034
|
+
test('rejects remote RESC bundle paths before download or bootloader entry', async () => {
|
|
5682
5035
|
const method = new FirmwareUpdateV4({
|
|
5683
5036
|
id: 1,
|
|
5684
5037
|
payload: {
|
|
5685
5038
|
method: 'firmwareUpdateV4',
|
|
5686
5039
|
platform: 'web',
|
|
5040
|
+
targetsToUpdate: ['resource'],
|
|
5687
5041
|
},
|
|
5688
5042
|
});
|
|
5689
5043
|
method.init();
|
|
5690
5044
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5691
5045
|
|
|
5692
|
-
const
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
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': '',
|
|
5699
5064
|
},
|
|
5700
|
-
|
|
5701
|
-
};
|
|
5065
|
+
});
|
|
5702
5066
|
|
|
5703
5067
|
(method as any).device = stubDevice({
|
|
5704
5068
|
originalDescriptor: { protocolType: 'V2' },
|
|
5705
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5069
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5706
5070
|
});
|
|
5707
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5711
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5712
|
-
bootloaderVersion: '1.0.0',
|
|
5713
|
-
bleVersion: '0.0.0',
|
|
5714
|
-
firmwareVersion: '1.0.0',
|
|
5071
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5072
|
+
bootloaderBinary: null,
|
|
5073
|
+
fwBinaryMap: [],
|
|
5715
5074
|
});
|
|
5075
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5716
5076
|
method.postTipMessage = jest.fn();
|
|
5717
5077
|
|
|
5718
|
-
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();
|
|
5719
5081
|
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5723
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5724
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5725
|
-
});
|
|
5082
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5083
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5726
5084
|
});
|
|
5727
5085
|
|
|
5728
|
-
test('
|
|
5086
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
5729
5087
|
const method = new FirmwareUpdateV4({
|
|
5730
5088
|
id: 1,
|
|
5731
5089
|
payload: {
|
|
5732
5090
|
method: 'firmwareUpdateV4',
|
|
5733
|
-
platform: 'web',
|
|
5734
5091
|
},
|
|
5735
5092
|
});
|
|
5736
|
-
|
|
5737
|
-
(method as any).
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
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
|
+
});
|
|
5746
5127
|
|
|
5747
|
-
await expect(
|
|
5128
|
+
await expect(
|
|
5129
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
+
deviceType: 'pro2',
|
|
5131
|
+
})
|
|
5132
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5748
5134
|
|
|
5749
|
-
|
|
5750
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5135
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5751
5136
|
});
|
|
5752
5137
|
|
|
5753
|
-
test('
|
|
5138
|
+
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5754
5139
|
const method = new FirmwareUpdateV4({
|
|
5755
5140
|
id: 1,
|
|
5756
5141
|
payload: {
|
|
5757
5142
|
method: 'firmwareUpdateV4',
|
|
5758
5143
|
platform: 'web',
|
|
5759
|
-
|
|
5144
|
+
targetsToUpdate: ['resource'],
|
|
5760
5145
|
},
|
|
5761
5146
|
});
|
|
5762
5147
|
method.init();
|
|
5763
5148
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5764
5149
|
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
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();
|
|
5779
5433
|
|
|
5780
5434
|
await method.run();
|
|
5781
5435
|
|
|
@@ -5875,7 +5529,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5875
5529
|
|
|
5876
5530
|
method.postTipMessage = jest.fn();
|
|
5877
5531
|
method.postProgressMessage = jest.fn();
|
|
5878
|
-
(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);
|
|
5879
5536
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5880
5537
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5881
5538
|
|
|
@@ -5891,7 +5548,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5891
5548
|
fwBinaryMap: [],
|
|
5892
5549
|
});
|
|
5893
5550
|
|
|
5894
|
-
expect((method as any).
|
|
5551
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5895
5552
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5896
5553
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5897
5554
|
});
|
|
@@ -5922,12 +5579,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5922
5579
|
});
|
|
5923
5580
|
method.postProgressMessage = jest.fn();
|
|
5924
5581
|
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
filePath: 'vol1:firmware.bin',
|
|
5928
|
-
processedSize: 0,
|
|
5929
|
-
totalSize: 4097,
|
|
5582
|
+
const source = await openFirmwareByteSource({
|
|
5583
|
+
binary: new Uint8Array(4097).buffer,
|
|
5930
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
|
+
}
|
|
5931
5595
|
|
|
5932
5596
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5933
5597
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5984,12 +5648,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5984
5648
|
method.postProgressMessage = jest.fn();
|
|
5985
5649
|
|
|
5986
5650
|
try {
|
|
5987
|
-
|
|
5988
|
-
|
|
5651
|
+
const source = await openFirmwareByteSource({
|
|
5652
|
+
binary: new Uint8Array(8001).buffer,
|
|
5653
|
+
});
|
|
5654
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
+
source,
|
|
5989
5656
|
filePath: 'vol0:/firmware.bin',
|
|
5990
5657
|
processedSize: 0,
|
|
5991
5658
|
totalSize: 8001,
|
|
5992
5659
|
});
|
|
5660
|
+
await source?.close();
|
|
5993
5661
|
} finally {
|
|
5994
5662
|
setTimeoutSpy.mockRestore();
|
|
5995
5663
|
}
|
|
@@ -6024,16 +5692,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6024
5692
|
});
|
|
6025
5693
|
method.postProgressMessage = jest.fn();
|
|
6026
5694
|
method.postTipMessage = jest.fn();
|
|
5695
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
6027
5696
|
|
|
5697
|
+
const source = await openFirmwareByteSource({
|
|
5698
|
+
binary: new Uint8Array(4097).buffer,
|
|
5699
|
+
});
|
|
6028
5700
|
await expect(
|
|
6029
|
-
(method as any).
|
|
6030
|
-
|
|
5701
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
5702
|
+
source,
|
|
6031
5703
|
filePath: 'vol0:/firmware.bin',
|
|
6032
5704
|
processedSize: 0,
|
|
6033
5705
|
totalSize: 4097,
|
|
6034
5706
|
})
|
|
6035
5707
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
6036
|
-
|
|
5708
|
+
await source?.close();
|
|
5709
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6037
5710
|
});
|
|
6038
5711
|
|
|
6039
5712
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -6066,12 +5739,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6066
5739
|
});
|
|
6067
5740
|
method.postProgressMessage = jest.fn();
|
|
6068
5741
|
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
6072
|
-
processedSize: 0,
|
|
6073
|
-
totalSize: 1801,
|
|
5742
|
+
const source = await openFirmwareByteSource({
|
|
5743
|
+
binary: new Uint8Array(1801).buffer,
|
|
6074
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
|
+
}
|
|
6075
5755
|
|
|
6076
5756
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
6077
5757
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -6188,9 +5868,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6188
5868
|
const typedCall = jest
|
|
6189
5869
|
.fn()
|
|
6190
5870
|
.mockRejectedValue(
|
|
6191
|
-
|
|
6192
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6193
|
-
'NotFoundError'
|
|
5871
|
+
Object.assign(
|
|
5872
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
5873
|
+
{ name: 'NotFoundError' }
|
|
6194
5874
|
)
|
|
6195
5875
|
);
|
|
6196
5876
|
|
|
@@ -6355,63 +6035,20 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6355
6035
|
});
|
|
6356
6036
|
|
|
6357
6037
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6367
|
-
],
|
|
6368
|
-
resource,
|
|
6369
|
-
])
|
|
6370
|
-
);
|
|
6371
|
-
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6372
|
-
if (requestType === 'ResourceInventoryGet') {
|
|
6373
|
-
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6374
|
-
}
|
|
6375
|
-
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6376
|
-
const resource = resourceByPath.get(payload.path);
|
|
6377
|
-
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6378
|
-
return {
|
|
6379
|
-
message: {
|
|
6380
|
-
exist: exists,
|
|
6381
|
-
directory: false,
|
|
6382
|
-
size: exists ? resource?.size : 0,
|
|
6383
|
-
},
|
|
6384
|
-
};
|
|
6385
|
-
}
|
|
6386
|
-
if (requestType === 'FilesystemFileRead') {
|
|
6387
|
-
const resource = resourceByPath.get(payload.file.path);
|
|
6388
|
-
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6389
|
-
const header = new Uint8Array(0x52a0);
|
|
6390
|
-
const view = new DataView(header.buffer);
|
|
6391
|
-
'OKPP'.split('').forEach((char, index) => {
|
|
6392
|
-
header[index] = char.charCodeAt(0);
|
|
6393
|
-
});
|
|
6394
|
-
'RESC'.split('').forEach((char, index) => {
|
|
6395
|
-
header[0x08 + index] = char.charCodeAt(0);
|
|
6396
|
-
});
|
|
6397
|
-
view.setUint32(0x0c, header.byteLength, true);
|
|
6398
|
-
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6399
|
-
header[0x240 + index] = Number.parseInt(
|
|
6400
|
-
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6401
|
-
16
|
|
6402
|
-
);
|
|
6403
|
-
}
|
|
6404
|
-
const offset = Number(payload.file.offset);
|
|
6405
|
-
const chunkLength = Number(payload.chunk_len);
|
|
6406
|
-
return {
|
|
6407
|
-
message: {
|
|
6408
|
-
data: header.slice(offset, offset + chunkLength),
|
|
6409
|
-
},
|
|
6410
|
-
};
|
|
6411
|
-
}
|
|
6412
|
-
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
|
+
},
|
|
6413
6046
|
});
|
|
6414
|
-
|
|
6047
|
+
|
|
6048
|
+
method.init();
|
|
6049
|
+
|
|
6050
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
+
});
|
|
6415
6052
|
|
|
6416
6053
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6417
6054
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6422,10 +6059,16 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6422
6059
|
).not.toThrow();
|
|
6423
6060
|
});
|
|
6424
6061
|
|
|
6425
|
-
test('
|
|
6426
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).
|
|
6427
|
-
|
|
6428
|
-
|
|
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
|
+
);
|
|
6429
6072
|
});
|
|
6430
6073
|
|
|
6431
6074
|
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
@@ -6458,7 +6101,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6458
6101
|
);
|
|
6459
6102
|
});
|
|
6460
6103
|
|
|
6461
|
-
test('
|
|
6104
|
+
test('rejects firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6462
6105
|
const method = new FirmwareUpdateV4({
|
|
6463
6106
|
id: 1,
|
|
6464
6107
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6471,209 +6114,39 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6471
6114
|
getCommands: () => ({ typedCall }),
|
|
6472
6115
|
});
|
|
6473
6116
|
|
|
6474
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6475
|
-
|
|
6476
|
-
});
|
|
6477
|
-
|
|
6478
|
-
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6479
|
-
const method = new FirmwareUpdateV4({
|
|
6480
|
-
id: 1,
|
|
6481
|
-
payload: {
|
|
6482
|
-
method: 'firmwareUpdateV4',
|
|
6483
|
-
platform: 'web',
|
|
6484
|
-
targetsToUpdate: ['resource'],
|
|
6485
|
-
},
|
|
6117
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).rejects.toMatchObject({
|
|
6118
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
6486
6119
|
});
|
|
6487
|
-
method.init();
|
|
6488
|
-
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6489
|
-
(type, index) => ({
|
|
6490
|
-
type,
|
|
6491
|
-
url: `https://example.com/${type}.okpkg`,
|
|
6492
|
-
size: 0x52a0 + index + 1,
|
|
6493
|
-
fileHash: 'a'.repeat(64),
|
|
6494
|
-
headerHash: index.toString(16).padStart(128, '0'),
|
|
6495
|
-
})
|
|
6496
|
-
);
|
|
6497
|
-
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
6498
|
-
(method as any).device = stubDevice({
|
|
6499
|
-
getCommands: () => ({ typedCall }),
|
|
6500
|
-
});
|
|
6501
|
-
const resourcesSpy = jest
|
|
6502
|
-
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6503
|
-
.mockReturnValue(stable as any);
|
|
6504
|
-
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6505
|
-
|
|
6506
|
-
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
6507
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6508
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
6509
|
-
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
6510
|
-
resourcesSpy.mockRestore();
|
|
6511
|
-
});
|
|
6512
|
-
|
|
6513
|
-
test('downloads only changed resources from loader inventory', async () => {
|
|
6514
|
-
const method = new FirmwareUpdateV4({
|
|
6515
|
-
id: 1,
|
|
6516
|
-
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6517
|
-
});
|
|
6518
|
-
method.init();
|
|
6519
|
-
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6520
|
-
(type, index) => ({
|
|
6521
|
-
type,
|
|
6522
|
-
url: `https://example.com/${type}.okpkg`,
|
|
6523
|
-
size: 0x52a0 + index + 1,
|
|
6524
|
-
fileHash: 'a'.repeat(64),
|
|
6525
|
-
headerHash: index.toString(16).padStart(128, '0'),
|
|
6526
|
-
})
|
|
6527
|
-
);
|
|
6528
|
-
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6529
|
-
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6530
|
-
const resourcesSpy = jest
|
|
6531
|
-
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6532
|
-
.mockReturnValue(stable as any);
|
|
6533
|
-
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6534
|
-
Promise.resolve({
|
|
6535
|
-
name: `${resource.type}.okpkg`,
|
|
6536
|
-
binary: new ArrayBuffer(resource.size),
|
|
6537
|
-
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6538
|
-
})
|
|
6539
|
-
);
|
|
6540
|
-
|
|
6541
|
-
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6542
|
-
|
|
6543
|
-
expect(bundles).toHaveLength(1);
|
|
6544
|
-
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6545
|
-
resourcesSpy.mockRestore();
|
|
6546
6120
|
});
|
|
6547
6121
|
|
|
6548
|
-
test('uses
|
|
6549
|
-
const method = new FirmwareUpdateV4({
|
|
6550
|
-
id: 1,
|
|
6551
|
-
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
6552
|
-
});
|
|
6553
|
-
method.init();
|
|
6554
|
-
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
6555
|
-
(type, index) => ({
|
|
6556
|
-
type,
|
|
6557
|
-
url: `https://example.com/${type}.okpkg`,
|
|
6558
|
-
size: 0x52a0 + index + 1,
|
|
6559
|
-
fileHash: 'a'.repeat(64),
|
|
6560
|
-
headerHash: index.toString(16).padStart(128, '0'),
|
|
6561
|
-
})
|
|
6562
|
-
);
|
|
6563
|
-
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
6564
|
-
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
6565
|
-
const resourcesSpy = jest
|
|
6566
|
-
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6567
|
-
.mockReturnValue(stable as any);
|
|
6568
|
-
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
6569
|
-
Promise.resolve({
|
|
6570
|
-
name: `${resource.type}.okpkg`,
|
|
6571
|
-
binary: new ArrayBuffer(resource.size),
|
|
6572
|
-
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
6573
|
-
})
|
|
6574
|
-
);
|
|
6575
|
-
|
|
6576
|
-
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
6577
|
-
|
|
6578
|
-
expect(bundles).toHaveLength(1);
|
|
6579
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
6580
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
6581
|
-
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
6582
|
-
resourcesSpy.mockRestore();
|
|
6583
|
-
});
|
|
6584
|
-
|
|
6585
|
-
const buildBootResourcesHeader = ({
|
|
6586
|
-
type = 'CRAT',
|
|
6587
|
-
payloadHash = 'ab'.repeat(64),
|
|
6588
|
-
headerHash = 'cd'.repeat(64),
|
|
6589
|
-
}: {
|
|
6590
|
-
type?: string;
|
|
6591
|
-
payloadHash?: string;
|
|
6592
|
-
headerHash?: string;
|
|
6593
|
-
} = {}) => {
|
|
6594
|
-
const header = new Uint8Array(0x52a0);
|
|
6595
|
-
const view = new DataView(header.buffer);
|
|
6596
|
-
'OKPP'.split('').forEach((char, index) => {
|
|
6597
|
-
header[index] = char.charCodeAt(0);
|
|
6598
|
-
});
|
|
6599
|
-
type.split('').forEach((char, index) => {
|
|
6600
|
-
header[0x08 + index] = char.charCodeAt(0);
|
|
6601
|
-
});
|
|
6602
|
-
view.setUint32(0x0c, header.byteLength, true);
|
|
6603
|
-
const writeHex = (offset: number, hex: string) => {
|
|
6604
|
-
for (let index = 0; index < hex.length / 2; index++) {
|
|
6605
|
-
header[offset + index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
6606
|
-
}
|
|
6607
|
-
};
|
|
6608
|
-
writeHex(0x200, payloadHash);
|
|
6609
|
-
writeHex(0x240, headerHash);
|
|
6610
|
-
return header;
|
|
6611
|
-
};
|
|
6612
|
-
|
|
6613
|
-
test('does not resolve boot resources unless the optional target is selected', async () => {
|
|
6614
|
-
const method = new FirmwareUpdateV4({
|
|
6615
|
-
id: 1,
|
|
6616
|
-
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
6617
|
-
});
|
|
6618
|
-
method.init();
|
|
6619
|
-
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6620
|
-
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
6621
|
-
|
|
6622
|
-
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
6623
|
-
|
|
6624
|
-
expect(configSpy).not.toHaveBeenCalled();
|
|
6625
|
-
expect(downloadSpy).not.toHaveBeenCalled();
|
|
6626
|
-
});
|
|
6627
|
-
|
|
6628
|
-
test('downloads and maps the selected boot resources CRATE target', async () => {
|
|
6629
|
-
const payloadHash = 'ab'.repeat(64);
|
|
6630
|
-
const headerHash = 'cd'.repeat(64);
|
|
6631
|
-
const bytes = buildBootResourcesHeader({ payloadHash, headerHash });
|
|
6632
|
-
const binary = bytes.buffer as ArrayBuffer;
|
|
6633
|
-
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
6634
|
-
const resource = {
|
|
6635
|
-
required: false as const,
|
|
6636
|
-
target: 'CRATE' as const,
|
|
6637
|
-
url: 'https://example.com/boot-resources.crate.okpkg',
|
|
6638
|
-
size: bytes.byteLength,
|
|
6639
|
-
fileHash,
|
|
6640
|
-
payloadHash,
|
|
6641
|
-
headerHash,
|
|
6642
|
-
};
|
|
6122
|
+
test('uses the BLE read limit when reading an existing firmware bundle header', async () => {
|
|
6643
6123
|
const method = new FirmwareUpdateV4({
|
|
6644
6124
|
id: 1,
|
|
6645
6125
|
payload: {
|
|
6646
6126
|
method: 'firmwareUpdateV4',
|
|
6647
|
-
platform: '
|
|
6648
|
-
|
|
6127
|
+
platform: 'native',
|
|
6128
|
+
chunkSize: 1800,
|
|
6649
6129
|
},
|
|
6650
6130
|
});
|
|
6651
6131
|
method.init();
|
|
6652
|
-
|
|
6653
|
-
jest.
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
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}`);
|
|
6660
6142
|
});
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
test('rejects a non-CRATE manual boot resources package', async () => {
|
|
6664
|
-
const method = new FirmwareUpdateV4({
|
|
6665
|
-
id: 1,
|
|
6666
|
-
payload: {
|
|
6667
|
-
method: 'firmwareUpdateV4',
|
|
6668
|
-
platform: 'web',
|
|
6669
|
-
bootResourcesBinary: buildBootResourcesHeader({ type: 'RESC' }).buffer,
|
|
6670
|
-
},
|
|
6143
|
+
(method as any).device = stubDevice({
|
|
6144
|
+
getCommands: () => ({ typedCall }),
|
|
6671
6145
|
});
|
|
6672
|
-
method.init();
|
|
6673
6146
|
|
|
6674
|
-
await expect((method as any).
|
|
6675
|
-
|
|
6676
|
-
);
|
|
6147
|
+
await expect((method as any).readProtocolV2DeviceFileHeader('bundle.okpp')).resolves.toBeNull();
|
|
6148
|
+
expect(readChunkLengths.length).toBeGreaterThan(1);
|
|
6149
|
+
expect(Math.max(...readChunkLengths)).toBe(900);
|
|
6677
6150
|
});
|
|
6678
6151
|
});
|
|
6679
6152
|
|
|
@@ -6800,245 +6273,326 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6800
6273
|
});
|
|
6801
6274
|
});
|
|
6802
6275
|
|
|
6803
|
-
test.each([
|
|
6804
|
-
['deviceInfoGet', DeviceInfoGet],
|
|
6805
|
-
['deviceStatusGet', DeviceStatusGet],
|
|
6806
|
-
])('does not auto-unlock the read-only %s method', (_name, MethodClass) => {
|
|
6807
|
-
const method = new MethodClass({
|
|
6808
|
-
id: 1,
|
|
6809
|
-
payload: { method: _name },
|
|
6810
|
-
} as any);
|
|
6811
|
-
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
|
+
};
|
|
6812
6427
|
|
|
6813
|
-
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();
|
|
6814
6433
|
});
|
|
6815
6434
|
|
|
6816
|
-
test('
|
|
6435
|
+
test('restores the expected hidden-wallet session after pre-unlock without selecting Attach PIN', async () => {
|
|
6817
6436
|
const calls: string[] = [];
|
|
6818
|
-
const features = { unlocked: true };
|
|
6819
6437
|
const method = {
|
|
6820
|
-
name: '
|
|
6821
|
-
|
|
6438
|
+
name: 'evmSignMessage',
|
|
6439
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6822
6440
|
useDevicePassphraseState: true,
|
|
6823
|
-
|
|
6441
|
+
unlockPolicy: 'retry-on-locked',
|
|
6824
6442
|
run: jest.fn(() => {
|
|
6825
6443
|
calls.push('run');
|
|
6826
|
-
return Promise.resolve({
|
|
6444
|
+
return Promise.resolve({ message: 'ok' });
|
|
6827
6445
|
}),
|
|
6828
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
|
+
};
|
|
6829
6469
|
const device = {
|
|
6830
6470
|
features,
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
calls.push('status');
|
|
6834
|
-
return Promise.resolve({ message: { unlocked: true } });
|
|
6835
|
-
}),
|
|
6836
|
-
},
|
|
6471
|
+
passphraseState: 'hidden-state',
|
|
6472
|
+
commands: { typedCall },
|
|
6837
6473
|
isProtocolV2: () => true,
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
features.
|
|
6842
|
-
return
|
|
6474
|
+
unlockDevice: jest.fn(() => {
|
|
6475
|
+
calls.push('unlock-main');
|
|
6476
|
+
features.unlocked = true;
|
|
6477
|
+
features.unlockedAttachPin = false;
|
|
6478
|
+
return Promise.resolve();
|
|
6843
6479
|
}),
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
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')),
|
|
6849
6485
|
};
|
|
6850
6486
|
|
|
6851
|
-
await expect(
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
).
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
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
|
+
]);
|
|
6861
6498
|
});
|
|
6862
6499
|
|
|
6863
|
-
test('
|
|
6864
|
-
const calls: string[] = [];
|
|
6865
|
-
const identityError = new Error('Unexpected device');
|
|
6500
|
+
test('does not restore a hidden-wallet session for a standard-wallet pre-unlock', async () => {
|
|
6866
6501
|
const method = {
|
|
6867
|
-
name: '
|
|
6868
|
-
|
|
6502
|
+
name: 'evmGetAddress',
|
|
6503
|
+
payload: { useEmptyPassphrase: true },
|
|
6869
6504
|
useDevicePassphraseState: true,
|
|
6870
|
-
|
|
6505
|
+
unlockPolicy: 'retry-on-locked',
|
|
6506
|
+
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6871
6507
|
};
|
|
6872
|
-
const features = { unlocked: false };
|
|
6873
6508
|
const device = {
|
|
6874
|
-
features,
|
|
6875
|
-
|
|
6876
|
-
typedCall: jest.fn(() => {
|
|
6877
|
-
calls.push('status');
|
|
6878
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6879
|
-
}),
|
|
6880
|
-
},
|
|
6509
|
+
features: { unlocked: false },
|
|
6510
|
+
passphraseState: 'stale-hidden-state',
|
|
6881
6511
|
isProtocolV2: () => true,
|
|
6882
|
-
|
|
6883
|
-
isRomloader: () => false,
|
|
6884
|
-
updateProtocolV2Status: jest.fn(() => features),
|
|
6885
|
-
unlockDevice: jest.fn(),
|
|
6512
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6886
6513
|
};
|
|
6887
6514
|
|
|
6888
|
-
await expect(
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
},
|
|
6894
|
-
})
|
|
6895
|
-
).rejects.toBe(identityError);
|
|
6896
|
-
|
|
6897
|
-
expect(calls).toEqual(['status', 'identity']);
|
|
6898
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6899
|
-
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);
|
|
6900
6520
|
});
|
|
6901
6521
|
|
|
6902
|
-
test('unlocks before
|
|
6522
|
+
test('refreshes status and unlocks before a protected Protocol V2 method', async () => {
|
|
6903
6523
|
const calls: string[] = [];
|
|
6904
|
-
const features = { unlocked: true };
|
|
6905
6524
|
const method = {
|
|
6906
|
-
name: '
|
|
6525
|
+
name: 'deviceSettingsPageShow',
|
|
6907
6526
|
unlockPolicy: 'unlock-before-run',
|
|
6908
|
-
protocolV2UiInteraction: { reason: '
|
|
6527
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6909
6528
|
run: jest.fn(() => {
|
|
6910
6529
|
calls.push('run');
|
|
6911
|
-
return Promise.resolve({
|
|
6530
|
+
return Promise.resolve({ message: 'ok' });
|
|
6912
6531
|
}),
|
|
6913
6532
|
};
|
|
6533
|
+
const features = { unlocked: true };
|
|
6914
6534
|
const device = {
|
|
6915
6535
|
features,
|
|
6916
6536
|
commands: {
|
|
6917
6537
|
typedCall: jest.fn(() => {
|
|
6918
|
-
calls.push('status');
|
|
6538
|
+
calls.push('refresh-status');
|
|
6919
6539
|
return Promise.resolve({ message: { unlocked: false } });
|
|
6920
6540
|
}),
|
|
6921
6541
|
},
|
|
6922
6542
|
isProtocolV2: () => true,
|
|
6923
|
-
isBootloader: () => false,
|
|
6924
|
-
isRomloader: () => false,
|
|
6925
6543
|
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6926
|
-
features.unlocked = status.unlocked
|
|
6544
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6927
6545
|
return features;
|
|
6928
6546
|
}),
|
|
6929
6547
|
unlockDevice: jest.fn(() => {
|
|
6930
6548
|
calls.push('unlock');
|
|
6931
6549
|
features.unlocked = true;
|
|
6932
|
-
return Promise.resolve(
|
|
6550
|
+
return Promise.resolve();
|
|
6933
6551
|
}),
|
|
6934
6552
|
};
|
|
6935
6553
|
const uiCoordinator = {
|
|
6936
6554
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6937
|
-
enterUnlockInteraction: jest.fn(() =>
|
|
6938
|
-
|
|
6939
|
-
return undefined;
|
|
6940
|
-
}),
|
|
6555
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6556
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6941
6557
|
};
|
|
6942
6558
|
|
|
6943
6559
|
await expect(
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
return Promise.resolve();
|
|
6949
|
-
},
|
|
6950
|
-
})
|
|
6951
|
-
).resolves.toEqual({ signature: 'signed' });
|
|
6952
|
-
|
|
6953
|
-
expect(calls).toEqual([
|
|
6954
|
-
'status',
|
|
6955
|
-
'unlock-prompt',
|
|
6956
|
-
'unlock',
|
|
6957
|
-
'wallet-session',
|
|
6958
|
-
'method-prompt',
|
|
6959
|
-
'run',
|
|
6960
|
-
]);
|
|
6961
|
-
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', {});
|
|
6962
6564
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6963
6567
|
});
|
|
6964
6568
|
|
|
6965
|
-
test('
|
|
6966
|
-
const
|
|
6967
|
-
name: 'evmGetAddress',
|
|
6968
|
-
unlockPolicy: 'unlock-before-run',
|
|
6969
|
-
run: jest.fn(),
|
|
6970
|
-
};
|
|
6971
|
-
const device = {
|
|
6972
|
-
features: { unlocked: undefined },
|
|
6973
|
-
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6974
|
-
isProtocolV2: () => true,
|
|
6975
|
-
isBootloader: () => false,
|
|
6976
|
-
isRomloader: () => false,
|
|
6977
|
-
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6978
|
-
unlockDevice: jest.fn(),
|
|
6979
|
-
};
|
|
6980
|
-
|
|
6981
|
-
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6982
|
-
errorCode: HardwareErrorCode.RuntimeError,
|
|
6983
|
-
});
|
|
6984
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6985
|
-
expect(method.run).not.toHaveBeenCalled();
|
|
6986
|
-
});
|
|
6987
|
-
|
|
6988
|
-
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6989
|
-
const error = deviceLockedError();
|
|
6569
|
+
test('uses refreshed unlocked status instead of a stale locked cache', async () => {
|
|
6570
|
+
const features = { unlocked: false };
|
|
6990
6571
|
const method = {
|
|
6991
|
-
name: '
|
|
6572
|
+
name: 'deviceSettings',
|
|
6992
6573
|
unlockPolicy: 'unlock-before-run',
|
|
6993
|
-
run: jest.fn().
|
|
6574
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6994
6575
|
};
|
|
6995
|
-
const features = { unlocked: true };
|
|
6996
6576
|
const device = {
|
|
6997
6577
|
features,
|
|
6998
|
-
commands: {
|
|
6578
|
+
commands: {
|
|
6579
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
+
},
|
|
6999
6581
|
isProtocolV2: () => true,
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
6582
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6583
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6584
|
+
return features;
|
|
6585
|
+
}),
|
|
7003
6586
|
unlockDevice: jest.fn(),
|
|
7004
6587
|
};
|
|
7005
6588
|
|
|
7006
|
-
await expect(
|
|
6589
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6590
|
+
message: 'ok',
|
|
6591
|
+
});
|
|
7007
6592
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7008
6593
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7009
6594
|
});
|
|
7010
6595
|
|
|
7011
|
-
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
7012
|
-
const features = { unlocked: true };
|
|
7013
|
-
const context = createProtocolV2UnlockContext();
|
|
7014
|
-
const firstMethod = {
|
|
7015
|
-
name: 'allNetworkGetAddress',
|
|
7016
|
-
unlockPolicy: 'unlock-before-run',
|
|
7017
|
-
run: jest.fn().mockResolvedValue({ root: true }),
|
|
7018
|
-
};
|
|
7019
|
-
const nestedMethod = {
|
|
7020
|
-
name: 'evmGetAddress',
|
|
7021
|
-
unlockPolicy: 'unlock-before-run',
|
|
7022
|
-
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
7023
|
-
};
|
|
7024
|
-
const device = {
|
|
7025
|
-
features,
|
|
7026
|
-
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
7027
|
-
isProtocolV2: () => true,
|
|
7028
|
-
isBootloader: () => false,
|
|
7029
|
-
isRomloader: () => false,
|
|
7030
|
-
updateProtocolV2Status: jest.fn(() => features),
|
|
7031
|
-
unlockDevice: jest.fn(),
|
|
7032
|
-
};
|
|
7033
|
-
|
|
7034
|
-
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
7035
|
-
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
7036
|
-
|
|
7037
|
-
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
7038
|
-
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
7039
|
-
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
7040
|
-
});
|
|
7041
|
-
|
|
7042
6596
|
test.each(['bootloader', 'romloader'] as const)(
|
|
7043
6597
|
'skips DeviceStatus and unlock in Protocol V2 %s mode',
|
|
7044
6598
|
async mode => {
|
|
@@ -7056,7 +6610,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7056
6610
|
unlockDevice: jest.fn(),
|
|
7057
6611
|
};
|
|
7058
6612
|
|
|
7059
|
-
await expect(
|
|
6613
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
7060
6614
|
message: 'updating',
|
|
7061
6615
|
});
|
|
7062
6616
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -7065,30 +6619,157 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
7065
6619
|
}
|
|
7066
6620
|
);
|
|
7067
6621
|
|
|
7068
|
-
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 () => {
|
|
7069
6653
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
7070
|
-
[false, '
|
|
6654
|
+
[false, 'retry-on-locked'],
|
|
7071
6655
|
[true, 'none'],
|
|
7072
6656
|
] as const) {
|
|
6657
|
+
const error = deviceLockedError();
|
|
7073
6658
|
const method = {
|
|
7074
6659
|
unlockPolicy,
|
|
7075
|
-
|
|
7076
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6660
|
+
run: jest.fn().mockRejectedValue(error),
|
|
7077
6661
|
};
|
|
7078
6662
|
const device = {
|
|
7079
|
-
commands: { typedCall: jest.fn() },
|
|
7080
6663
|
isProtocolV2: () => isProtocolV2,
|
|
7081
6664
|
unlockDevice: jest.fn(),
|
|
7082
6665
|
};
|
|
7083
6666
|
|
|
7084
|
-
await expect(
|
|
7085
|
-
message: 'ok',
|
|
7086
|
-
});
|
|
7087
|
-
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6667
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
7088
6668
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
7089
6669
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
7090
6670
|
}
|
|
7091
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
|
+
});
|
|
7092
6773
|
});
|
|
7093
6774
|
|
|
7094
6775
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -7220,9 +6901,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7220
6901
|
|
|
7221
6902
|
await method.run();
|
|
7222
6903
|
|
|
7223
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7224
|
-
eventless_wallet_session: true,
|
|
7225
|
-
});
|
|
6904
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
7226
6905
|
});
|
|
7227
6906
|
|
|
7228
6907
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -7298,29 +6977,6 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
7298
6977
|
|
|
7299
6978
|
await expect(method.run()).resolves.toBe(features);
|
|
7300
6979
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
7301
|
-
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
7302
|
-
emitUiEvent: false,
|
|
7303
|
-
});
|
|
7304
|
-
});
|
|
7305
|
-
|
|
7306
|
-
test('forwards an explicit PIN type when unlocking a Protocol V2 device', async () => {
|
|
7307
|
-
const method = new DeviceUnlock({
|
|
7308
|
-
id: 1,
|
|
7309
|
-
payload: {
|
|
7310
|
-
method: 'deviceUnlock',
|
|
7311
|
-
pinType: DeviceSessionPinType.Any,
|
|
7312
|
-
},
|
|
7313
|
-
});
|
|
7314
|
-
method.init();
|
|
7315
|
-
|
|
7316
|
-
const unlockDevice = jest.fn().mockResolvedValue({ unlocked: true });
|
|
7317
|
-
(method as any).device = { unlockDevice };
|
|
7318
|
-
|
|
7319
|
-
await method.run();
|
|
7320
|
-
|
|
7321
|
-
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
7322
|
-
emitUiEvent: false,
|
|
7323
|
-
});
|
|
7324
6980
|
});
|
|
7325
6981
|
|
|
7326
6982
|
test.each([
|