@onekeyfe/hd-core 1.2.0-alpha.51 → 1.2.0-alpha.53
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 +78 -0
- package/__tests__/DeviceCommands.test.ts +45 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/deviceUploadNft.test.ts +293 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +446 -33
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +284 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
- package/__tests__/protocol-v2.test.ts +1349 -1005
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +86 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +11 -25
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +2 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -9
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +4 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -2
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +29 -6
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +214 -231
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2559 -3473
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -4
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +2 -70
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +22 -13
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +54 -57
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -313
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +566 -965
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +54 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -122
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -0
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +167 -83
- package/src/core/uiPromiseRegistry.ts +41 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +67 -38
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +258 -42
- package/src/device/DeviceCommands.ts +7 -1
- package/src/device/DevicePool.ts +7 -2
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -3
- package/src/protocols/protocol-v2/resources.ts +359 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +118 -0
- package/src/protocols/protocol-v2/walletSession.ts +180 -30
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +2 -4
- package/src/types/api/detectDeviceConnectProtocol.ts +7 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +3 -76
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +3 -0
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +42 -13
- package/src/utils/deviceFeaturesUtils.ts +8 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DeviceSettingsPage,
|
|
6
6
|
DeviceType,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
10
11
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -61,6 +62,7 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
62
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
63
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
64
|
import { DevicePool } from '../src/device/DevicePool';
|
|
65
|
+
import { DEVICE } from '../src/events';
|
|
64
66
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
65
67
|
import {
|
|
66
68
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -74,11 +76,15 @@ import {
|
|
|
74
76
|
requestProtocolV2ProtocolInfo,
|
|
75
77
|
supportsProtocolV2Message,
|
|
76
78
|
} from '../src/protocols/protocol-v2/features';
|
|
79
|
+
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
77
80
|
import {
|
|
78
81
|
getProtocolV2WalletSession,
|
|
79
82
|
refreshProtocolV2DeviceStatus,
|
|
80
83
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
81
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
createProtocolV2UnlockContext,
|
|
86
|
+
runMethodWithUnlockPolicy,
|
|
87
|
+
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
82
88
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
83
89
|
import {
|
|
84
90
|
buildProtocolV1FeaturesPayload,
|
|
@@ -92,7 +98,6 @@ import {
|
|
|
92
98
|
getMethodVersionRange,
|
|
93
99
|
} from '../src/utils';
|
|
94
100
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
-
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
96
101
|
import {
|
|
97
102
|
getPassphraseState,
|
|
98
103
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -241,7 +246,7 @@ describe('UploadPortfolio', () => {
|
|
|
241
246
|
(method as any).device = device;
|
|
242
247
|
|
|
243
248
|
method.init();
|
|
244
|
-
await
|
|
249
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
245
250
|
|
|
246
251
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
247
252
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -417,14 +422,16 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
417
422
|
}
|
|
418
423
|
return d.features;
|
|
419
424
|
};
|
|
425
|
+
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
426
|
+
requestProtocolV2ProtocolInfo({
|
|
427
|
+
commands: d.getCommands?.() ?? d.commands,
|
|
428
|
+
timeoutMs,
|
|
429
|
+
});
|
|
420
430
|
d.probeProtocolV2RuntimeState ??= async (
|
|
421
431
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
422
432
|
timeoutMs?: number
|
|
423
433
|
) => {
|
|
424
|
-
const protocolInfo = await
|
|
425
|
-
commands: d.getCommands(),
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
434
|
+
const protocolInfo = await d.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
428
435
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
429
436
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
430
437
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -781,6 +788,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
781
788
|
if (request === 'DeviceStatusGet') {
|
|
782
789
|
return { type: 'DeviceStatus', message: { unlocked: true } };
|
|
783
790
|
}
|
|
791
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
792
|
+
return { type: 'Success', message: {} };
|
|
793
|
+
}
|
|
784
794
|
if (request === 'DeviceSessionGet') {
|
|
785
795
|
return {
|
|
786
796
|
type: 'DeviceSession',
|
|
@@ -805,7 +815,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
815
|
).resolves.toEqual({
|
|
806
816
|
passphraseState: 'state-1',
|
|
807
817
|
newSession: 'session-1',
|
|
808
|
-
unlockedAttachPin:
|
|
818
|
+
unlockedAttachPin: false,
|
|
809
819
|
resumed: false,
|
|
810
820
|
});
|
|
811
821
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -814,7 +824,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
814
824
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
815
825
|
type: DeviceSessionPinType.Main,
|
|
816
826
|
});
|
|
817
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
827
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
828
|
+
passphrase: '',
|
|
829
|
+
on_device: false,
|
|
830
|
+
});
|
|
831
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
832
|
+
seed_domains: [],
|
|
833
|
+
});
|
|
818
834
|
device.passphraseState = 'state-1';
|
|
819
835
|
expect(device.getInternalState()).toBe('session-1');
|
|
820
836
|
});
|
|
@@ -847,6 +863,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
847
863
|
},
|
|
848
864
|
};
|
|
849
865
|
}
|
|
866
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
850
867
|
if (request === 'DeviceSessionGet') {
|
|
851
868
|
return {
|
|
852
869
|
message: {
|
|
@@ -863,9 +880,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
880
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
864
881
|
|
|
865
882
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
866
|
-
expect(
|
|
883
|
+
expect(
|
|
884
|
+
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
885
|
+
).toHaveLength(1);
|
|
886
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
887
|
+
seed_domains: [],
|
|
888
|
+
});
|
|
867
889
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
868
890
|
session_id: 'standard-session',
|
|
891
|
+
btc_test_address: 'standard-state',
|
|
892
|
+
seed_domains: [],
|
|
869
893
|
});
|
|
870
894
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
871
895
|
passphraseState: 'standard-state',
|
|
@@ -907,6 +931,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
907
931
|
},
|
|
908
932
|
};
|
|
909
933
|
}
|
|
934
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
910
935
|
if (request === 'DeviceSessionAskPin') {
|
|
911
936
|
return { type: 'Success', message: { message: 'PIN verified' } };
|
|
912
937
|
}
|
|
@@ -1001,6 +1026,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1001
1026
|
if (request === 'DeviceStatusGet') {
|
|
1002
1027
|
return { message: { device_id: deviceId, unlocked: true, passphrase_enabled: true } };
|
|
1003
1028
|
}
|
|
1029
|
+
if (request === 'DeviceSessionAskPassphrase') return { message: {} };
|
|
1004
1030
|
if (request === 'DeviceSessionGet') {
|
|
1005
1031
|
sessionGetCalls += 1;
|
|
1006
1032
|
return {
|
|
@@ -1227,7 +1253,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1227
1253
|
passphrase: 'host hidden wallet',
|
|
1228
1254
|
on_device: false,
|
|
1229
1255
|
});
|
|
1230
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1256
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1257
|
+
seed_domains: [],
|
|
1258
|
+
});
|
|
1231
1259
|
});
|
|
1232
1260
|
|
|
1233
1261
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1335,6 +1363,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1335
1363
|
|
|
1336
1364
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1337
1365
|
session_id: 'session-a',
|
|
1366
|
+
btc_test_address: 'state-a',
|
|
1367
|
+
seed_domains: [],
|
|
1338
1368
|
});
|
|
1339
1369
|
expect(device.getInternalState()).toBe('session-b');
|
|
1340
1370
|
});
|
|
@@ -1402,7 +1432,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1402
1432
|
});
|
|
1403
1433
|
expect(typedCall.mock.calls).toEqual([
|
|
1404
1434
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1405
|
-
[
|
|
1435
|
+
[
|
|
1436
|
+
'DeviceSessionGet',
|
|
1437
|
+
'DeviceSession',
|
|
1438
|
+
{
|
|
1439
|
+
session_id: 'session-a',
|
|
1440
|
+
btc_test_address: 'state-a',
|
|
1441
|
+
seed_domains: [],
|
|
1442
|
+
},
|
|
1443
|
+
],
|
|
1406
1444
|
]);
|
|
1407
1445
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1408
1446
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1450,7 +1488,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1450
1488
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1451
1489
|
],
|
|
1452
1490
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1453
|
-
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1491
|
+
['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
|
|
1454
1492
|
]);
|
|
1455
1493
|
});
|
|
1456
1494
|
|
|
@@ -1553,6 +1591,73 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1591
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1592
|
});
|
|
1555
1593
|
|
|
1594
|
+
test.each([false, true])(
|
|
1595
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1596
|
+
async lockFails => {
|
|
1597
|
+
const deviceId = 'attach-mismatch-device';
|
|
1598
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1599
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1600
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1601
|
+
{
|
|
1602
|
+
status: {
|
|
1603
|
+
device_id: deviceId,
|
|
1604
|
+
unlocked: true,
|
|
1605
|
+
passphrase_enabled: true,
|
|
1606
|
+
attach_to_pin_enabled: true,
|
|
1607
|
+
unlocked_by_attach_to_pin: false,
|
|
1608
|
+
},
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
device.passphraseState = 'expected-state';
|
|
1612
|
+
|
|
1613
|
+
const typedCall = jest.fn((request: string) => {
|
|
1614
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1615
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1616
|
+
if (request === 'DeviceStatusGet') {
|
|
1617
|
+
return {
|
|
1618
|
+
message: {
|
|
1619
|
+
device_id: deviceId,
|
|
1620
|
+
unlocked: true,
|
|
1621
|
+
passphrase_enabled: true,
|
|
1622
|
+
attach_to_pin_enabled: true,
|
|
1623
|
+
unlocked_by_attach_to_pin: true,
|
|
1624
|
+
},
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (request === 'DeviceSessionGet') {
|
|
1628
|
+
return {
|
|
1629
|
+
message: {
|
|
1630
|
+
btc_test_address: 'wrong-attach-state',
|
|
1631
|
+
session_id: 'wrong-attach-session',
|
|
1632
|
+
},
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (request === 'LockDevice') {
|
|
1636
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1637
|
+
return { message: { message: 'Device locked' } };
|
|
1638
|
+
}
|
|
1639
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1640
|
+
});
|
|
1641
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1642
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1643
|
+
|
|
1644
|
+
await expect(
|
|
1645
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1646
|
+
).rejects.toMatchObject({
|
|
1647
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1651
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1652
|
+
btc_test_address: 'expected-state',
|
|
1653
|
+
seed_domains: [],
|
|
1654
|
+
});
|
|
1655
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1656
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1657
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1556
1661
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1662
|
const device = Device.fromDescriptor({
|
|
1558
1663
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1714,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1714
|
}),
|
|
1610
1715
|
{ cancelDeviceOnReject: false }
|
|
1611
1716
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1717
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1718
|
+
DeviceSessionPinType.AttachToPin,
|
|
1719
|
+
expect.objectContaining({
|
|
1720
|
+
emitUiEvent: false,
|
|
1721
|
+
interaction: expect.objectContaining({
|
|
1722
|
+
phase: 'pin',
|
|
1723
|
+
transition: 'start',
|
|
1724
|
+
protocol: 'V2',
|
|
1725
|
+
}),
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1613
1728
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1729
|
});
|
|
1615
1730
|
|
|
@@ -1893,12 +2008,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
2008
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
2009
|
});
|
|
1895
2010
|
|
|
1896
|
-
test('
|
|
2011
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
2012
|
const typedCall = jest
|
|
1898
2013
|
.fn()
|
|
1899
2014
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
2015
|
.mockResolvedValueOnce({
|
|
1903
2016
|
message: {
|
|
1904
2017
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2039,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2039
|
await expect(
|
|
1927
2040
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2041
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2042
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2043
|
+
btc_test_address: 'expected-state',
|
|
2044
|
+
seed_domains: [],
|
|
2045
|
+
});
|
|
2046
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2047
|
});
|
|
1940
2048
|
|
|
1941
2049
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,8 +2109,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2109
|
true
|
|
2002
2110
|
);
|
|
2003
2111
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
[
|
|
2005
|
-
|
|
2112
|
+
[
|
|
2113
|
+
'DeviceSessionGet',
|
|
2114
|
+
'DeviceSession',
|
|
2115
|
+
{
|
|
2116
|
+
session_id: 'session-pro2-app',
|
|
2117
|
+
btc_test_address: 'state-pro2-app',
|
|
2118
|
+
seed_domains: [],
|
|
2119
|
+
},
|
|
2120
|
+
],
|
|
2006
2121
|
]);
|
|
2007
2122
|
});
|
|
2008
2123
|
|
|
@@ -2191,7 +2306,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2191
2306
|
passphrase: 'host hidden wallet',
|
|
2192
2307
|
on_device: false,
|
|
2193
2308
|
});
|
|
2194
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2309
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2310
|
+
seed_domains: [],
|
|
2311
|
+
});
|
|
2195
2312
|
});
|
|
2196
2313
|
|
|
2197
2314
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2269,12 +2386,83 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2386
|
);
|
|
2270
2387
|
|
|
2271
2388
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2390
|
+
btc_test_address: 'expected-state',
|
|
2391
|
+
seed_domains: [],
|
|
2392
|
+
});
|
|
2393
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2394
|
passphrase: 'host hidden wallet',
|
|
2274
2395
|
on_device: false,
|
|
2275
2396
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2397
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2398
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2399
|
+
seed_domains: [],
|
|
2400
|
+
});
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
2404
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2405
|
+
const typedCall = jest.fn((request: string) => {
|
|
2406
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2407
|
+
return { message: { version: 2 } };
|
|
2408
|
+
}
|
|
2409
|
+
if (request === 'DeviceStatusGet') {
|
|
2410
|
+
return {
|
|
2411
|
+
message: {
|
|
2412
|
+
device_id: 'attach-wallet-device',
|
|
2413
|
+
unlocked: true,
|
|
2414
|
+
passphrase_enabled: true,
|
|
2415
|
+
attach_to_pin_enabled: true,
|
|
2416
|
+
unlocked_by_attach_to_pin: true,
|
|
2417
|
+
},
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
if (request === 'DeviceSessionAskPin' || request === 'DeviceSessionAskPassphrase') {
|
|
2421
|
+
return { message: { message: 'Wallet selected' } };
|
|
2422
|
+
}
|
|
2423
|
+
if (request === 'DeviceSessionGet') {
|
|
2424
|
+
return {
|
|
2425
|
+
message: {
|
|
2426
|
+
session_id: 'main-wallet-session',
|
|
2427
|
+
btc_test_address: 'main-wallet-state',
|
|
2428
|
+
},
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
if (request === 'LockDevice') {
|
|
2432
|
+
return { message: { message: 'Device locked' } };
|
|
2433
|
+
}
|
|
2434
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
2438
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
2439
|
+
{
|
|
2440
|
+
status: {
|
|
2441
|
+
device_id: 'attach-wallet-device',
|
|
2442
|
+
unlocked: true,
|
|
2443
|
+
passphrase_enabled: true,
|
|
2444
|
+
attach_to_pin_enabled: true,
|
|
2445
|
+
// 模拟缓存滞后:设备状态刷新后才确认当前由 Attach PIN 解锁。
|
|
2446
|
+
unlocked_by_attach_to_pin: false,
|
|
2447
|
+
},
|
|
2448
|
+
}
|
|
2449
|
+
);
|
|
2450
|
+
(device as any).commands = { typedCall };
|
|
2451
|
+
const clearInternalState = jest.spyOn(device, 'clearInternalState');
|
|
2452
|
+
|
|
2453
|
+
await expect(device.checkPassphraseStateSafety(undefined, true, false)).rejects.toMatchObject({
|
|
2454
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2458
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
2459
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2460
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2461
|
+
'DeviceSessionAskPassphrase',
|
|
2462
|
+
'Success',
|
|
2463
|
+
expect.anything()
|
|
2464
|
+
);
|
|
2465
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
2278
2466
|
});
|
|
2279
2467
|
|
|
2280
2468
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2307,10 +2495,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2307
2495
|
await expect(
|
|
2308
2496
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2309
2497
|
).resolves.toBe(true);
|
|
2310
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2498
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
2311
2499
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2500
|
eventless_wallet_session: true,
|
|
2313
2501
|
});
|
|
2502
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2503
|
+
seed_domains: [],
|
|
2504
|
+
});
|
|
2505
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2506
|
+
passphrase: '',
|
|
2507
|
+
on_device: false,
|
|
2508
|
+
});
|
|
2509
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2510
|
+
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
2511
|
+
2
|
|
2512
|
+
);
|
|
2513
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2314
2514
|
});
|
|
2315
2515
|
|
|
2316
2516
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2681,7 +2881,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2881
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2882
|
}
|
|
2683
2883
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2884
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2885
|
+
eventless_wallet_session: true,
|
|
2886
|
+
});
|
|
2685
2887
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2888
|
});
|
|
2687
2889
|
|
|
@@ -2719,7 +2921,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2921
|
}),
|
|
2720
2922
|
expect.anything()
|
|
2721
2923
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2924
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2925
|
+
eventless_wallet_session: true,
|
|
2926
|
+
});
|
|
2723
2927
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2928
|
expect(device.features).toMatchObject({
|
|
2725
2929
|
deviceId: null,
|
|
@@ -2985,10 +3189,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3189
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3190
|
},
|
|
2987
3191
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3192
|
.mockResolvedValueOnce({
|
|
2993
3193
|
type: 'DeviceStatus',
|
|
2994
3194
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3210,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3210
|
firmwareVersion: '1.2.4',
|
|
3011
3211
|
passphraseProtection: null,
|
|
3012
3212
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3213
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3214
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3215
|
4,
|
|
3016
3216
|
'DeviceInfoGet',
|
|
@@ -3124,9 +3324,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3324
|
status: { init_states: true },
|
|
3125
3325
|
}
|
|
3126
3326
|
);
|
|
3327
|
+
const pinOnDevice = jest.fn();
|
|
3328
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3329
|
|
|
3128
3330
|
const features = await device.unlockDevice();
|
|
3129
3331
|
|
|
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
|
+
);
|
|
3130
3344
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3345
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3346
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3386,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3386
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3387
|
});
|
|
3174
3388
|
(device as any).commands = { typedCall };
|
|
3389
|
+
const pinOnDevice = jest.fn();
|
|
3390
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3391
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3392
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3393
|
|
|
3394
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3395
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3396
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3397
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3577,6 +3794,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3794
|
|
|
3578
3795
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3796
|
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
|
+
});
|
|
3580
3806
|
|
|
3581
3807
|
const buildOkppHeader = ({
|
|
3582
3808
|
type = 'RESC',
|
|
@@ -3701,7 +3927,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3701
3927
|
specific: true,
|
|
3702
3928
|
},
|
|
3703
3929
|
},
|
|
3704
|
-
{ timeoutMs:
|
|
3930
|
+
{ timeoutMs: 30000 }
|
|
3705
3931
|
);
|
|
3706
3932
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
3707
3933
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
@@ -3717,7 +3943,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3943
|
});
|
|
3718
3944
|
});
|
|
3719
3945
|
|
|
3720
|
-
test('
|
|
3946
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3947
|
const method = new FirmwareUpdateV4({
|
|
3722
3948
|
id: 1,
|
|
3723
3949
|
payload: {
|
|
@@ -3752,7 +3978,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3978
|
|
|
3753
3979
|
await method.run();
|
|
3754
3980
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3981
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3982
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3983
|
fwBinaryMap: [
|
|
3758
3984
|
{
|
|
@@ -3763,6 +3989,69 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3763
3989
|
],
|
|
3764
3990
|
bootloaderBinary: null,
|
|
3765
3991
|
});
|
|
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']);
|
|
3766
4055
|
});
|
|
3767
4056
|
|
|
3768
4057
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -3814,6 +4103,33 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3814
4103
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3815
4104
|
});
|
|
3816
4105
|
|
|
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
|
+
|
|
3817
4133
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
3818
4134
|
const method = new FirmwareUpdateV4({
|
|
3819
4135
|
id: 1,
|
|
@@ -3866,6 +4182,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
4182
|
},
|
|
3867
4183
|
});
|
|
3868
4184
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4185
|
+
const prepareResourceBundles = jest
|
|
4186
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4187
|
+
.mockResolvedValue(undefined);
|
|
3869
4188
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
4189
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
4190
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +4197,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
4197
|
|
|
3879
4198
|
await method.run();
|
|
3880
4199
|
|
|
4200
|
+
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
3881
4201
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
4202
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
4203
|
bootloaderBinary: null,
|
|
@@ -3960,7 +4280,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3960
4280
|
getCommands: () => commands,
|
|
3961
4281
|
mainId: 'usb-path',
|
|
3962
4282
|
});
|
|
3963
|
-
const cachedDevice = {
|
|
4283
|
+
const cachedDevice = {
|
|
4284
|
+
getConnectId: () => 'usb-path',
|
|
4285
|
+
getCurrentSerialNo: () => '',
|
|
4286
|
+
};
|
|
3964
4287
|
const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
3965
4288
|
devices: {},
|
|
3966
4289
|
deviceList: [cachedDevice],
|
|
@@ -3977,36 +4300,212 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3977
4300
|
expect(initialize).not.toHaveBeenCalled();
|
|
3978
4301
|
});
|
|
3979
4302
|
|
|
3980
|
-
test('
|
|
4303
|
+
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
3981
4304
|
const method = new FirmwareUpdateV4({
|
|
3982
4305
|
id: 1,
|
|
3983
4306
|
payload: {
|
|
3984
4307
|
method: 'firmwareUpdateV4',
|
|
3985
4308
|
},
|
|
3986
4309
|
});
|
|
3987
|
-
const commands = { disposed:
|
|
3988
|
-
const
|
|
4310
|
+
const commands = { disposed: true, mainId: '' };
|
|
4311
|
+
const updateFromCache = jest.fn();
|
|
3989
4312
|
const device = stubDevice({
|
|
3990
|
-
originalDescriptor: {
|
|
3991
|
-
id: 'usb-id',
|
|
3992
|
-
path: 'usb-path',
|
|
3993
|
-
session: 'usb-session',
|
|
3994
|
-
protocolType: 'V2',
|
|
3995
|
-
},
|
|
4313
|
+
originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
|
|
3996
4314
|
deviceConnector: {
|
|
3997
4315
|
enumerate: jest.fn().mockResolvedValue({
|
|
3998
4316
|
descriptors: [
|
|
3999
|
-
{
|
|
4000
|
-
|
|
4001
|
-
path: 'usb-path',
|
|
4002
|
-
session: 'usb-session',
|
|
4003
|
-
protocolType: 'V2',
|
|
4004
|
-
},
|
|
4317
|
+
{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
|
|
4318
|
+
{ id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
|
|
4005
4319
|
],
|
|
4006
4320
|
}),
|
|
4007
4321
|
},
|
|
4008
|
-
|
|
4009
|
-
|
|
4322
|
+
updateFromCache,
|
|
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
|
+
},
|
|
4504
|
+
],
|
|
4505
|
+
}),
|
|
4506
|
+
},
|
|
4507
|
+
updateDescriptor: jest.fn(),
|
|
4508
|
+
updateFromCache: jest.fn(),
|
|
4010
4509
|
hasDeviceAcquire: jest.fn(() => true),
|
|
4011
4510
|
acquire,
|
|
4012
4511
|
commands,
|
|
@@ -4071,7 +4570,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4071
4570
|
});
|
|
4072
4571
|
method.postTipMessage = jest.fn();
|
|
4073
4572
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4074
|
-
|
|
4573
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4574
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4575
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
4075
4576
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4076
4577
|
message: 'Device rebooted successfully',
|
|
4077
4578
|
});
|
|
@@ -4080,9 +4581,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4080
4581
|
|
|
4081
4582
|
expect(method.postTipMessage).not.toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
4082
4583
|
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4584
|
+
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4083
4585
|
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
4084
4586
|
});
|
|
4085
4587
|
|
|
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
|
+
|
|
4086
4607
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
4087
4608
|
const method = new FirmwareUpdateV4({
|
|
4088
4609
|
id: 1,
|
|
@@ -4125,6 +4646,27 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4125
4646
|
});
|
|
4126
4647
|
});
|
|
4127
4648
|
|
|
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
|
+
|
|
4128
4670
|
test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
|
|
4129
4671
|
const method = new FirmwareUpdateV4({
|
|
4130
4672
|
id: 1,
|
|
@@ -4135,9 +4677,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4677
|
const typedCall = jest
|
|
4136
4678
|
.fn()
|
|
4137
4679
|
.mockRejectedValue(
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4680
|
+
new DOMException(
|
|
4681
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4682
|
+
'NotFoundError'
|
|
4141
4683
|
)
|
|
4142
4684
|
);
|
|
4143
4685
|
|
|
@@ -4215,7 +4757,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4215
4757
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4216
4758
|
]);
|
|
4217
4759
|
|
|
4218
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(
|
|
4760
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
4219
4761
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
4220
4762
|
'DeviceFirmwareUpdateStatusGet',
|
|
4221
4763
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -4253,6 +4795,46 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4253
4795
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4254
4796
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4255
4797
|
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');
|
|
4256
4838
|
});
|
|
4257
4839
|
|
|
4258
4840
|
test.each([
|
|
@@ -4318,77 +4900,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4900
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4901
|
});
|
|
4320
4902
|
|
|
4321
|
-
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
4322
|
-
const method = new FirmwareUpdateV4({
|
|
4323
|
-
id: 1,
|
|
4324
|
-
payload: {
|
|
4325
|
-
method: 'firmwareUpdateV4',
|
|
4326
|
-
platform: 'desktop',
|
|
4327
|
-
expectedTargetVersions: {
|
|
4328
|
-
app_v1: '2.0.0',
|
|
4329
|
-
},
|
|
4330
|
-
},
|
|
4331
|
-
});
|
|
4332
|
-
method.init();
|
|
4333
|
-
method.postProgressMessage = jest.fn();
|
|
4334
|
-
|
|
4335
|
-
expect(() =>
|
|
4336
|
-
(method as any).assertProtocolV2TargetStatus(
|
|
4337
|
-
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
4338
|
-
new Set([4])
|
|
4339
|
-
)
|
|
4340
|
-
).toThrow('expected 131072');
|
|
4341
|
-
});
|
|
4342
|
-
|
|
4343
|
-
test('does not fail a completed multi-app install when final app versions are unobservable', () => {
|
|
4344
|
-
const method = new FirmwareUpdateV4({
|
|
4345
|
-
id: 1,
|
|
4346
|
-
payload: {
|
|
4347
|
-
method: 'firmwareUpdateV4',
|
|
4348
|
-
platform: 'desktop',
|
|
4349
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4350
|
-
expectedTargetVersions: {
|
|
4351
|
-
app_v1: '1.0.0',
|
|
4352
|
-
app_v2: '2.0.0',
|
|
4353
|
-
},
|
|
4354
|
-
},
|
|
4355
|
-
});
|
|
4356
|
-
method.init();
|
|
4357
|
-
(method as any).protocolV2LatestFinalFeatures = {
|
|
4358
|
-
major_version: 3,
|
|
4359
|
-
minor_version: 0,
|
|
4360
|
-
patch_version: 0,
|
|
4361
|
-
};
|
|
4362
|
-
(method as any).protocolV2FinalStatusVerified = true;
|
|
4363
|
-
|
|
4364
|
-
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
4365
|
-
});
|
|
4366
|
-
|
|
4367
|
-
test('rejects unobservable final versions after the status endpoint fallback', () => {
|
|
4368
|
-
const method = new FirmwareUpdateV4({
|
|
4369
|
-
id: 1,
|
|
4370
|
-
payload: {
|
|
4371
|
-
method: 'firmwareUpdateV4',
|
|
4372
|
-
platform: 'desktop',
|
|
4373
|
-
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4374
|
-
expectedTargetVersions: {
|
|
4375
|
-
app_v1: '1.0.0',
|
|
4376
|
-
app_v2: '2.0.0',
|
|
4377
|
-
},
|
|
4378
|
-
},
|
|
4379
|
-
});
|
|
4380
|
-
method.init();
|
|
4381
|
-
(method as any).protocolV2LatestFinalFeatures = {
|
|
4382
|
-
major_version: 3,
|
|
4383
|
-
minor_version: 0,
|
|
4384
|
-
patch_version: 0,
|
|
4385
|
-
};
|
|
4386
|
-
|
|
4387
|
-
expect(() => (method as any).assertExpectedProtocolV2Versions()).toThrow(
|
|
4388
|
-
'has no observable final version after status fallback'
|
|
4389
|
-
);
|
|
4390
|
-
});
|
|
4391
|
-
|
|
4392
4903
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4393
4904
|
const method = new FirmwareUpdateV4({
|
|
4394
4905
|
id: 1,
|
|
@@ -4431,15 +4942,160 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4431
4942
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4432
4943
|
});
|
|
4433
4944
|
|
|
4434
|
-
test('
|
|
4945
|
+
test('fails clearly when a requested target stays absent from the full status dump', async () => {
|
|
4435
4946
|
const method = new FirmwareUpdateV4({
|
|
4436
4947
|
id: 1,
|
|
4437
4948
|
payload: {
|
|
4438
4949
|
method: 'firmwareUpdateV4',
|
|
4439
4950
|
},
|
|
4440
4951
|
});
|
|
4441
|
-
|
|
4442
|
-
|
|
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', () => {
|
|
5091
|
+
const method = new FirmwareUpdateV4({
|
|
5092
|
+
id: 1,
|
|
5093
|
+
payload: {
|
|
5094
|
+
method: 'firmwareUpdateV4',
|
|
5095
|
+
},
|
|
5096
|
+
});
|
|
5097
|
+
method.postProgressMessage = jest.fn();
|
|
5098
|
+
|
|
4443
5099
|
const expectedTargetIds = new Set([4]);
|
|
4444
5100
|
|
|
4445
5101
|
expect(
|
|
@@ -4465,7 +5121,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4465
5121
|
).toBe(true);
|
|
4466
5122
|
|
|
4467
5123
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4468
|
-
expect(
|
|
5124
|
+
expect(
|
|
4469
5125
|
(method as any).assertProtocolV2TargetStatus(
|
|
4470
5126
|
[
|
|
4471
5127
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4473,8 +5129,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4473
5129
|
],
|
|
4474
5130
|
new Set([4, 10])
|
|
4475
5131
|
)
|
|
4476
|
-
).
|
|
4477
|
-
expect(method.postProgressMessage).
|
|
5132
|
+
).toBe(false);
|
|
5133
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4478
5134
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4479
5135
|
|
|
4480
5136
|
expect(
|
|
@@ -4563,7 +5219,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4563
5219
|
path: true,
|
|
4564
5220
|
},
|
|
4565
5221
|
},
|
|
4566
|
-
{ timeoutMs:
|
|
5222
|
+
{ timeoutMs: 15000 }
|
|
4567
5223
|
);
|
|
4568
5224
|
});
|
|
4569
5225
|
|
|
@@ -4578,14 +5234,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4578
5234
|
const writtenPaths: string[] = [];
|
|
4579
5235
|
method.postTipMessage = jest.fn();
|
|
4580
5236
|
method.postProgressMessage = jest.fn();
|
|
4581
|
-
(method as any).
|
|
5237
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4582
5238
|
writtenPaths.push(params.filePath);
|
|
4583
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
5239
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4584
5240
|
});
|
|
4585
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4586
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4587
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
4588
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4589
5241
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4590
5242
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4591
5243
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4625,12 +5277,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
5277
|
'vol0:/coprocessor.bin',
|
|
4626
5278
|
1
|
|
4627
5279
|
);
|
|
4628
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
4629
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4630
|
-
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4631
|
-
});
|
|
4632
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
5280
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
5281
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4633
5282
|
targets: [
|
|
5283
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4634
5284
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4635
5285
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4636
5286
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4640,7 +5290,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4640
5290
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4641
5291
|
});
|
|
4642
5292
|
|
|
4643
|
-
test('announces
|
|
5293
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4644
5294
|
const method = new FirmwareUpdateV4({
|
|
4645
5295
|
id: 1,
|
|
4646
5296
|
payload: {
|
|
@@ -4650,14 +5300,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4650
5300
|
|
|
4651
5301
|
method.postTipMessage = jest.fn();
|
|
4652
5302
|
method.postProgressMessage = jest.fn();
|
|
4653
|
-
(method as any).
|
|
5303
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4654
5304
|
.fn()
|
|
4655
5305
|
.mockImplementation(params =>
|
|
4656
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5306
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4657
5307
|
);
|
|
4658
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4659
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4660
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4661
5308
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4662
5309
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4663
5310
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4677,22 +5324,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
5324
|
{
|
|
4678
5325
|
fileName: 'application_p1.bin',
|
|
4679
5326
|
binary: new Uint8Array([3]).buffer,
|
|
4680
|
-
targetId:
|
|
5327
|
+
targetId: 3,
|
|
4681
5328
|
},
|
|
4682
5329
|
],
|
|
4683
5330
|
});
|
|
4684
5331
|
|
|
4685
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
5332
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4686
5333
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4687
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4688
|
-
expect(method.
|
|
4689
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5334
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5335
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4690
5336
|
1,
|
|
4691
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
5337
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4692
5338
|
);
|
|
4693
|
-
expect((method as any).
|
|
5339
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4694
5340
|
2,
|
|
4695
|
-
expect.objectContaining({ processedSize:
|
|
5341
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4696
5342
|
);
|
|
4697
5343
|
});
|
|
4698
5344
|
|
|
@@ -4706,8 +5352,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4706
5352
|
|
|
4707
5353
|
method.postTipMessage = jest.fn();
|
|
4708
5354
|
method.postProgressMessage = jest.fn();
|
|
4709
|
-
(method as any).
|
|
4710
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5355
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4711
5356
|
(method as any).device = stubDevice({
|
|
4712
5357
|
getCommands: () => ({
|
|
4713
5358
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4753,14 +5398,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4753
5398
|
|
|
4754
5399
|
method.postTipMessage = jest.fn();
|
|
4755
5400
|
method.postProgressMessage = jest.fn();
|
|
4756
|
-
(method as any).
|
|
5401
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4757
5402
|
.fn()
|
|
4758
5403
|
.mockImplementation(params =>
|
|
4759
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
5404
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4760
5405
|
);
|
|
4761
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4762
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4763
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4764
5406
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4765
5407
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4766
5408
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4975,33 +5617,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4975
5617
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4976
5618
|
});
|
|
4977
5619
|
|
|
4978
|
-
test('
|
|
5620
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
4979
5621
|
const method = new FirmwareUpdateV4({
|
|
4980
5622
|
id: 1,
|
|
4981
5623
|
payload: {
|
|
4982
5624
|
method: 'firmwareUpdateV4',
|
|
4983
5625
|
platform: 'web',
|
|
4984
|
-
targetsToUpdate: ['resource'],
|
|
4985
5626
|
},
|
|
4986
5627
|
});
|
|
4987
5628
|
method.init();
|
|
4988
5629
|
|
|
5630
|
+
const getSysResourceBinarySpy = jest
|
|
5631
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5632
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
4989
5633
|
const getFirmwareLatestReleaseSpy = jest
|
|
4990
5634
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4991
5635
|
.mockReturnValue({
|
|
4992
5636
|
required: false,
|
|
4993
5637
|
version: [1, 0, 0],
|
|
4994
|
-
url: '',
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
headerHash: 'cd'.repeat(64),
|
|
5638
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5639
|
+
bootloaderVersion: [1, 0, 0],
|
|
5640
|
+
upgradeType: 'payload-package-set',
|
|
5641
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5642
|
+
components: {
|
|
5643
|
+
bootloader: {
|
|
5644
|
+
target: 'BOOTLOADER',
|
|
5645
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5003
5646
|
},
|
|
5004
|
-
|
|
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',
|
|
5654
|
+
},
|
|
5655
|
+
},
|
|
5005
5656
|
fingerprint: '',
|
|
5006
5657
|
changelog: {
|
|
5007
5658
|
'zh-CN': '',
|
|
@@ -5009,408 +5660,103 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5009
5660
|
},
|
|
5010
5661
|
});
|
|
5011
5662
|
|
|
5012
|
-
const
|
|
5663
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5013
5664
|
deviceType: 'pro2',
|
|
5014
5665
|
firmwareVersion: '1.0.0',
|
|
5666
|
+
bootloaderVersion: '1.0.0',
|
|
5015
5667
|
capabilities: [],
|
|
5016
5668
|
});
|
|
5017
5669
|
|
|
5018
|
-
expect(
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
version: [2, 0, 0],
|
|
5025
|
-
payloadHash: 'ab'.repeat(64),
|
|
5026
|
-
headerHash: 'cd'.repeat(64),
|
|
5027
|
-
},
|
|
5028
|
-
]);
|
|
5029
|
-
expect(bundles[0].binary.byteLength).toBe(0);
|
|
5670
|
+
expect(remoteBinaries).toEqual({
|
|
5671
|
+
bootloaderBinary: null,
|
|
5672
|
+
fwBinaryMap: [],
|
|
5673
|
+
installItems: [],
|
|
5674
|
+
});
|
|
5675
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5030
5676
|
|
|
5677
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5031
5678
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5032
5679
|
});
|
|
5033
5680
|
|
|
5034
|
-
test('
|
|
5681
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5035
5682
|
const method = new FirmwareUpdateV4({
|
|
5036
5683
|
id: 1,
|
|
5037
5684
|
payload: {
|
|
5038
5685
|
method: 'firmwareUpdateV4',
|
|
5039
5686
|
platform: 'web',
|
|
5040
|
-
targetsToUpdate: ['resource'],
|
|
5041
5687
|
},
|
|
5042
5688
|
});
|
|
5043
5689
|
method.init();
|
|
5044
5690
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5045
5691
|
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
resourceBundles: [
|
|
5054
|
-
{
|
|
5055
|
-
name: 'images',
|
|
5056
|
-
url: 'https://example.com/images.okpkg',
|
|
5057
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5058
|
-
},
|
|
5059
|
-
],
|
|
5060
|
-
fingerprint: '',
|
|
5061
|
-
changelog: {
|
|
5062
|
-
'zh-CN': '',
|
|
5063
|
-
'en-US': '',
|
|
5692
|
+
const remoteBinaries = {
|
|
5693
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5694
|
+
fwBinaryMap: [
|
|
5695
|
+
{
|
|
5696
|
+
fileName: 'application_p1.bin',
|
|
5697
|
+
binary: new Uint8Array([2]).buffer,
|
|
5698
|
+
targetId: 3,
|
|
5064
5699
|
},
|
|
5065
|
-
|
|
5700
|
+
],
|
|
5701
|
+
};
|
|
5066
5702
|
|
|
5067
5703
|
(method as any).device = stubDevice({
|
|
5068
5704
|
originalDescriptor: { protocolType: 'V2' },
|
|
5069
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5705
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5070
5706
|
});
|
|
5071
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5072
|
-
|
|
5073
|
-
|
|
5707
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5708
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5709
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
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',
|
|
5074
5715
|
});
|
|
5075
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5076
5716
|
method.postTipMessage = jest.fn();
|
|
5077
5717
|
|
|
5078
|
-
await
|
|
5079
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5718
|
+
await method.run();
|
|
5081
5719
|
|
|
5082
|
-
|
|
5083
|
-
|
|
5720
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5721
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5722
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5723
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5724
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5725
|
+
});
|
|
5084
5726
|
});
|
|
5085
5727
|
|
|
5086
|
-
test('
|
|
5728
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5087
5729
|
const method = new FirmwareUpdateV4({
|
|
5088
5730
|
id: 1,
|
|
5089
5731
|
payload: {
|
|
5090
5732
|
method: 'firmwareUpdateV4',
|
|
5733
|
+
platform: 'web',
|
|
5091
5734
|
},
|
|
5092
5735
|
});
|
|
5093
|
-
|
|
5094
|
-
(method as any).
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
},
|
|
5104
|
-
},
|
|
5105
|
-
],
|
|
5106
|
-
};
|
|
5107
|
-
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
-
.mockReturnValue({
|
|
5111
|
-
required: false,
|
|
5112
|
-
version: [1, 0, 0],
|
|
5113
|
-
url: '',
|
|
5114
|
-
resourceBundles: [
|
|
5115
|
-
{
|
|
5116
|
-
name: 'images',
|
|
5117
|
-
url: 'https://example.com/images.okpkg',
|
|
5118
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
-
},
|
|
5120
|
-
],
|
|
5121
|
-
fingerprint: '',
|
|
5122
|
-
changelog: {
|
|
5123
|
-
'zh-CN': '',
|
|
5124
|
-
'en-US': '',
|
|
5125
|
-
},
|
|
5126
|
-
});
|
|
5736
|
+
method.init();
|
|
5737
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5738
|
+
(method as any).device = stubDevice({
|
|
5739
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5740
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5741
|
+
});
|
|
5742
|
+
forceReloadDataSpy.mockRejectedValue(new Error('latest config unavailable'));
|
|
5743
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5744
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5745
|
+
method.postTipMessage = jest.fn();
|
|
5127
5746
|
|
|
5128
|
-
await expect(
|
|
5129
|
-
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
-
deviceType: 'pro2',
|
|
5131
|
-
})
|
|
5132
|
-
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
-
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5747
|
+
await expect(method.run()).rejects.toThrow('latest config unavailable');
|
|
5134
5748
|
|
|
5135
|
-
|
|
5749
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5750
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5136
5751
|
});
|
|
5137
5752
|
|
|
5138
|
-
test('
|
|
5753
|
+
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5139
5754
|
const method = new FirmwareUpdateV4({
|
|
5140
5755
|
id: 1,
|
|
5141
5756
|
payload: {
|
|
5142
5757
|
method: 'firmwareUpdateV4',
|
|
5143
5758
|
platform: 'web',
|
|
5144
|
-
|
|
5145
|
-
},
|
|
5146
|
-
});
|
|
5147
|
-
method.init();
|
|
5148
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5149
|
-
|
|
5150
|
-
const order: string[] = [];
|
|
5151
|
-
const resourceBinary = buildOkppHeader().buffer as ArrayBuffer;
|
|
5152
|
-
const getSysResourceBinarySpy = jest
|
|
5153
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5154
|
-
.mockImplementation(() => {
|
|
5155
|
-
order.push('download');
|
|
5156
|
-
return Promise.resolve({ binary: resourceBinary });
|
|
5157
|
-
});
|
|
5158
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5159
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5160
|
-
.mockReturnValue({
|
|
5161
|
-
required: false,
|
|
5162
|
-
version: [1, 0, 0],
|
|
5163
|
-
url: '',
|
|
5164
|
-
resourceBundles: [
|
|
5165
|
-
{
|
|
5166
|
-
name: 'images',
|
|
5167
|
-
url: 'https://example.com/images.okpkg',
|
|
5168
|
-
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
5169
|
-
},
|
|
5170
|
-
],
|
|
5171
|
-
fingerprint: '',
|
|
5172
|
-
changelog: {
|
|
5173
|
-
'zh-CN': '',
|
|
5174
|
-
'en-US': '',
|
|
5175
|
-
},
|
|
5176
|
-
});
|
|
5177
|
-
|
|
5178
|
-
(method as any).device = stubDevice({
|
|
5179
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5180
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5181
|
-
});
|
|
5182
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5183
|
-
bootloaderBinary: null,
|
|
5184
|
-
fwBinaryMap: [],
|
|
5185
|
-
});
|
|
5186
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
5187
|
-
order.push('bootloader');
|
|
5188
|
-
return Promise.resolve(true);
|
|
5189
|
-
});
|
|
5190
|
-
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
5191
|
-
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
5192
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
5193
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5194
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5195
|
-
bootloaderVersion: '1.0.0',
|
|
5196
|
-
bleVersion: '0.0.0',
|
|
5197
|
-
firmwareVersion: '1.0.0',
|
|
5198
|
-
});
|
|
5199
|
-
method.postTipMessage = jest.fn();
|
|
5200
|
-
|
|
5201
|
-
await method.run();
|
|
5202
|
-
|
|
5203
|
-
expect(order).toEqual(['download', 'bootloader']);
|
|
5204
|
-
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
5205
|
-
bootloaderBinary: null,
|
|
5206
|
-
fwBinaryMap: [],
|
|
5207
|
-
resourceBundles: [
|
|
5208
|
-
{
|
|
5209
|
-
name: 'images',
|
|
5210
|
-
binary: resourceBinary,
|
|
5211
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5212
|
-
url: 'https://example.com/images.okpkg',
|
|
5213
|
-
},
|
|
5214
|
-
],
|
|
5215
|
-
});
|
|
5216
|
-
|
|
5217
|
-
executeProtocolV2UpdateSpy.mockRestore();
|
|
5218
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5219
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5220
|
-
});
|
|
5221
|
-
|
|
5222
|
-
test('rejects a remote RESC bundle without an OKPP header when metadata is absent', async () => {
|
|
5223
|
-
const method = new FirmwareUpdateV4({
|
|
5224
|
-
id: 1,
|
|
5225
|
-
payload: {
|
|
5226
|
-
method: 'firmwareUpdateV4',
|
|
5227
|
-
},
|
|
5228
|
-
});
|
|
5229
|
-
method.init();
|
|
5230
|
-
const getSysResourceBinarySpy = jest
|
|
5231
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5232
|
-
.mockResolvedValue({ binary: new Uint8Array([1, 2, 3]).buffer });
|
|
5233
|
-
|
|
5234
|
-
await expect(
|
|
5235
|
-
(method as any).materializeProtocolV2ResourceBundles([
|
|
5236
|
-
{
|
|
5237
|
-
name: 'images',
|
|
5238
|
-
binary: new ArrayBuffer(0),
|
|
5239
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5240
|
-
url: 'https://example.com/images.okpkg',
|
|
5241
|
-
},
|
|
5242
|
-
])
|
|
5243
|
-
).rejects.toThrow('Invalid Protocol V2 RESC bundle header: images');
|
|
5244
|
-
|
|
5245
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5246
|
-
});
|
|
5247
|
-
|
|
5248
|
-
test('rejects an empty remote RESC bundle before entering bootloader', async () => {
|
|
5249
|
-
const method = new FirmwareUpdateV4({
|
|
5250
|
-
id: 1,
|
|
5251
|
-
payload: {
|
|
5252
|
-
method: 'firmwareUpdateV4',
|
|
5253
|
-
platform: 'web',
|
|
5254
|
-
targetsToUpdate: ['resource'],
|
|
5255
|
-
},
|
|
5256
|
-
});
|
|
5257
|
-
method.init();
|
|
5258
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5259
|
-
|
|
5260
|
-
const getSysResourceBinarySpy = jest
|
|
5261
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5262
|
-
.mockResolvedValue({ binary: new ArrayBuffer(0) });
|
|
5263
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5264
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5265
|
-
.mockReturnValue({
|
|
5266
|
-
required: false,
|
|
5267
|
-
version: [1, 0, 0],
|
|
5268
|
-
url: '',
|
|
5269
|
-
resourceBundles: [
|
|
5270
|
-
{
|
|
5271
|
-
name: 'images',
|
|
5272
|
-
url: 'https://example.com/images.okpkg',
|
|
5273
|
-
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
5274
|
-
},
|
|
5275
|
-
],
|
|
5276
|
-
fingerprint: '',
|
|
5277
|
-
changelog: {
|
|
5278
|
-
'zh-CN': '',
|
|
5279
|
-
'en-US': '',
|
|
5280
|
-
},
|
|
5281
|
-
});
|
|
5282
|
-
(method as any).device = stubDevice({
|
|
5283
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5284
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
5285
|
-
});
|
|
5286
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
5287
|
-
bootloaderBinary: null,
|
|
5288
|
-
fwBinaryMap: [],
|
|
5289
|
-
});
|
|
5290
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5291
|
-
method.postTipMessage = jest.fn();
|
|
5292
|
-
|
|
5293
|
-
await expect(method.run()).rejects.toThrow('Protocol V2 RESC bundle is empty: images');
|
|
5294
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5295
|
-
|
|
5296
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5297
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5298
|
-
});
|
|
5299
|
-
|
|
5300
|
-
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5301
|
-
const method = new FirmwareUpdateV4({
|
|
5302
|
-
id: 1,
|
|
5303
|
-
payload: {
|
|
5304
|
-
method: 'firmwareUpdateV4',
|
|
5305
|
-
platform: 'web',
|
|
5306
|
-
},
|
|
5307
|
-
});
|
|
5308
|
-
method.init();
|
|
5309
|
-
|
|
5310
|
-
const getSysResourceBinarySpy = jest
|
|
5311
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5312
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5313
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5314
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5315
|
-
.mockReturnValue({
|
|
5316
|
-
required: false,
|
|
5317
|
-
version: [1, 0, 0],
|
|
5318
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5319
|
-
bootloaderVersion: [1, 0, 0],
|
|
5320
|
-
upgradeType: 'payload-package-set',
|
|
5321
|
-
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5322
|
-
components: {
|
|
5323
|
-
bootloader: {
|
|
5324
|
-
target: 'BOOTLOADER',
|
|
5325
|
-
url: 'https://example.com/bootloader.pp.bin',
|
|
5326
|
-
},
|
|
5327
|
-
applicationP1: {
|
|
5328
|
-
target: 'APPLICATION_P1',
|
|
5329
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5330
|
-
},
|
|
5331
|
-
applicationP2: {
|
|
5332
|
-
target: 'APPLICATION_P2',
|
|
5333
|
-
url: 'https://example.com/applicationP2.pp.bin',
|
|
5334
|
-
},
|
|
5335
|
-
},
|
|
5336
|
-
fingerprint: '',
|
|
5337
|
-
changelog: {
|
|
5338
|
-
'zh-CN': '',
|
|
5339
|
-
'en-US': '',
|
|
5340
|
-
},
|
|
5341
|
-
});
|
|
5342
|
-
|
|
5343
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5344
|
-
deviceType: 'pro2',
|
|
5345
|
-
firmwareVersion: '1.0.0',
|
|
5346
|
-
bootloaderVersion: '1.0.0',
|
|
5347
|
-
capabilities: [],
|
|
5348
|
-
});
|
|
5349
|
-
|
|
5350
|
-
expect(remoteBinaries).toEqual({
|
|
5351
|
-
bootloaderBinary: null,
|
|
5352
|
-
fwBinaryMap: [],
|
|
5353
|
-
installItems: [],
|
|
5354
|
-
});
|
|
5355
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5356
|
-
|
|
5357
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5358
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5359
|
-
});
|
|
5360
|
-
|
|
5361
|
-
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5362
|
-
const method = new FirmwareUpdateV4({
|
|
5363
|
-
id: 1,
|
|
5364
|
-
payload: {
|
|
5365
|
-
method: 'firmwareUpdateV4',
|
|
5366
|
-
platform: 'web',
|
|
5367
|
-
},
|
|
5368
|
-
});
|
|
5369
|
-
method.init();
|
|
5370
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5371
|
-
|
|
5372
|
-
const remoteBinaries = {
|
|
5373
|
-
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5374
|
-
fwBinaryMap: [
|
|
5375
|
-
{
|
|
5376
|
-
fileName: 'application_p1.bin',
|
|
5377
|
-
binary: new Uint8Array([2]).buffer,
|
|
5378
|
-
targetId: 3,
|
|
5379
|
-
},
|
|
5380
|
-
],
|
|
5381
|
-
};
|
|
5382
|
-
|
|
5383
|
-
(method as any).device = stubDevice({
|
|
5384
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5385
|
-
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5386
|
-
});
|
|
5387
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5388
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5389
|
-
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5390
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5391
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5392
|
-
bootloaderVersion: '1.0.0',
|
|
5393
|
-
bleVersion: '0.0.0',
|
|
5394
|
-
firmwareVersion: '1.0.0',
|
|
5395
|
-
});
|
|
5396
|
-
method.postTipMessage = jest.fn();
|
|
5397
|
-
|
|
5398
|
-
await method.run();
|
|
5399
|
-
|
|
5400
|
-
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5401
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5402
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5403
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5404
|
-
});
|
|
5405
|
-
});
|
|
5406
|
-
|
|
5407
|
-
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
5408
|
-
const method = new FirmwareUpdateV4({
|
|
5409
|
-
id: 1,
|
|
5410
|
-
payload: {
|
|
5411
|
-
method: 'firmwareUpdateV4',
|
|
5412
|
-
platform: 'web',
|
|
5413
|
-
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
5759
|
+
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
5414
5760
|
},
|
|
5415
5761
|
});
|
|
5416
5762
|
method.init();
|
|
@@ -5529,10 +5875,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5529
5875
|
|
|
5530
5876
|
method.postTipMessage = jest.fn();
|
|
5531
5877
|
method.postProgressMessage = jest.fn();
|
|
5532
|
-
(method as any).
|
|
5533
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5534
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5535
|
-
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5878
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5536
5879
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5537
5880
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5538
5881
|
|
|
@@ -5548,7 +5891,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5548
5891
|
fwBinaryMap: [],
|
|
5549
5892
|
});
|
|
5550
5893
|
|
|
5551
|
-
expect((method as any).
|
|
5894
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5552
5895
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5553
5896
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5554
5897
|
});
|
|
@@ -5579,19 +5922,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5579
5922
|
});
|
|
5580
5923
|
method.postProgressMessage = jest.fn();
|
|
5581
5924
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5925
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5926
|
+
payload: new Uint8Array(4097).buffer,
|
|
5927
|
+
filePath: 'vol1:firmware.bin',
|
|
5928
|
+
processedSize: 0,
|
|
5929
|
+
totalSize: 4097,
|
|
5584
5930
|
});
|
|
5585
|
-
try {
|
|
5586
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5587
|
-
source,
|
|
5588
|
-
filePath: 'vol1:firmware.bin',
|
|
5589
|
-
processedSize: 0,
|
|
5590
|
-
totalSize: 4097,
|
|
5591
|
-
});
|
|
5592
|
-
} finally {
|
|
5593
|
-
await source?.close();
|
|
5594
|
-
}
|
|
5595
5931
|
|
|
5596
5932
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5597
5933
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5648,16 +5984,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5648
5984
|
method.postProgressMessage = jest.fn();
|
|
5649
5985
|
|
|
5650
5986
|
try {
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
});
|
|
5654
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
-
source,
|
|
5987
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5988
|
+
payload: new Uint8Array(8001).buffer,
|
|
5656
5989
|
filePath: 'vol0:/firmware.bin',
|
|
5657
5990
|
processedSize: 0,
|
|
5658
5991
|
totalSize: 8001,
|
|
5659
5992
|
});
|
|
5660
|
-
await source?.close();
|
|
5661
5993
|
} finally {
|
|
5662
5994
|
setTimeoutSpy.mockRestore();
|
|
5663
5995
|
}
|
|
@@ -5692,21 +6024,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5692
6024
|
});
|
|
5693
6025
|
method.postProgressMessage = jest.fn();
|
|
5694
6026
|
method.postTipMessage = jest.fn();
|
|
5695
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5696
6027
|
|
|
5697
|
-
const source = await openFirmwareByteSource({
|
|
5698
|
-
binary: new Uint8Array(4097).buffer,
|
|
5699
|
-
});
|
|
5700
6028
|
await expect(
|
|
5701
|
-
(method as any).
|
|
5702
|
-
|
|
6029
|
+
(method as any).protocolV2WriteWholeFile({
|
|
6030
|
+
payload: new Uint8Array(4097).buffer,
|
|
5703
6031
|
filePath: 'vol0:/firmware.bin',
|
|
5704
6032
|
processedSize: 0,
|
|
5705
6033
|
totalSize: 4097,
|
|
5706
6034
|
})
|
|
5707
6035
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5708
|
-
|
|
5709
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
6036
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5710
6037
|
});
|
|
5711
6038
|
|
|
5712
6039
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5739,19 +6066,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5739
6066
|
});
|
|
5740
6067
|
method.postProgressMessage = jest.fn();
|
|
5741
6068
|
|
|
5742
|
-
|
|
5743
|
-
|
|
6069
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
6070
|
+
payload: new Uint8Array(1801).buffer,
|
|
6071
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
6072
|
+
processedSize: 0,
|
|
6073
|
+
totalSize: 1801,
|
|
5744
6074
|
});
|
|
5745
|
-
try {
|
|
5746
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5747
|
-
source,
|
|
5748
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
5749
|
-
processedSize: 0,
|
|
5750
|
-
totalSize: 1801,
|
|
5751
|
-
});
|
|
5752
|
-
} finally {
|
|
5753
|
-
await source?.close();
|
|
5754
|
-
}
|
|
5755
6075
|
|
|
5756
6076
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5757
6077
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -5868,9 +6188,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
6188
|
const typedCall = jest
|
|
5869
6189
|
.fn()
|
|
5870
6190
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6191
|
+
new DOMException(
|
|
6192
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
6193
|
+
'NotFoundError'
|
|
5874
6194
|
)
|
|
5875
6195
|
);
|
|
5876
6196
|
|
|
@@ -6035,20 +6355,63 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
6355
|
});
|
|
6036
6356
|
|
|
6037
6357
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6358
|
+
const createResourceFilesystemTypedCall = (
|
|
6359
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6360
|
+
missingTypes: string[] = []
|
|
6361
|
+
) => {
|
|
6362
|
+
const missing = new Set(missingTypes);
|
|
6363
|
+
const resourceByPath = new Map(
|
|
6364
|
+
stable.map(resource => [
|
|
6365
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
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}`);
|
|
6413
|
+
});
|
|
6414
|
+
};
|
|
6052
6415
|
|
|
6053
6416
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6054
6417
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
@@ -6059,16 +6422,10 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6059
6422
|
).not.toThrow();
|
|
6060
6423
|
});
|
|
6061
6424
|
|
|
6062
|
-
test('
|
|
6063
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
6064
|
-
|
|
6065
|
-
);
|
|
6066
|
-
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).toThrow(
|
|
6067
|
-
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6068
|
-
);
|
|
6069
|
-
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
6070
|
-
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
6071
|
-
);
|
|
6425
|
+
test('allows reconnect when either physical serial is unavailable', () => {
|
|
6426
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).not.toThrow();
|
|
6427
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).not.toThrow();
|
|
6428
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).not.toThrow();
|
|
6072
6429
|
});
|
|
6073
6430
|
|
|
6074
6431
|
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
@@ -6101,7 +6458,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6101
6458
|
);
|
|
6102
6459
|
});
|
|
6103
6460
|
|
|
6104
|
-
test('
|
|
6461
|
+
test('allows firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
6105
6462
|
const method = new FirmwareUpdateV4({
|
|
6106
6463
|
id: 1,
|
|
6107
6464
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -6114,39 +6471,209 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6114
6471
|
getCommands: () => ({ typedCall }),
|
|
6115
6472
|
});
|
|
6116
6473
|
|
|
6117
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
6118
|
-
|
|
6119
|
-
});
|
|
6474
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).resolves.toBeUndefined();
|
|
6475
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
6120
6476
|
});
|
|
6121
6477
|
|
|
6122
|
-
test('uses
|
|
6478
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
6123
6479
|
const method = new FirmwareUpdateV4({
|
|
6124
6480
|
id: 1,
|
|
6125
6481
|
payload: {
|
|
6126
6482
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
6483
|
+
platform: 'web',
|
|
6484
|
+
targetsToUpdate: ['resource'],
|
|
6129
6485
|
},
|
|
6130
6486
|
});
|
|
6131
6487
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
});
|
|
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);
|
|
6143
6498
|
(method as any).device = stubDevice({
|
|
6144
6499
|
getCommands: () => ({ typedCall }),
|
|
6145
6500
|
});
|
|
6501
|
+
const resourcesSpy = jest
|
|
6502
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
6503
|
+
.mockReturnValue(stable as any);
|
|
6504
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
6146
6505
|
|
|
6147
|
-
await expect((method as any).
|
|
6148
|
-
expect(
|
|
6149
|
-
expect(
|
|
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
|
+
});
|
|
6547
|
+
|
|
6548
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
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
|
+
};
|
|
6643
|
+
const method = new FirmwareUpdateV4({
|
|
6644
|
+
id: 1,
|
|
6645
|
+
payload: {
|
|
6646
|
+
method: 'firmwareUpdateV4',
|
|
6647
|
+
platform: 'web',
|
|
6648
|
+
targetsToUpdate: ['boot_resources'],
|
|
6649
|
+
},
|
|
6650
|
+
});
|
|
6651
|
+
method.init();
|
|
6652
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
6653
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
6654
|
+
|
|
6655
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual({
|
|
6656
|
+
fileName: 'boot_resources.crate.okpkg',
|
|
6657
|
+
binary,
|
|
6658
|
+
targetId: 1,
|
|
6659
|
+
kind: 'boot_resources',
|
|
6660
|
+
});
|
|
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
|
+
},
|
|
6671
|
+
});
|
|
6672
|
+
method.init();
|
|
6673
|
+
|
|
6674
|
+
await expect((method as any).prepareProtocolV2BootResources()).rejects.toThrow(
|
|
6675
|
+
'Invalid Pro2 boot resources CRATE header'
|
|
6676
|
+
);
|
|
6150
6677
|
});
|
|
6151
6678
|
});
|
|
6152
6679
|
|
|
@@ -6286,311 +6813,230 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6286
6813
|
expect(method.unlockPolicy).toBe('none');
|
|
6287
6814
|
});
|
|
6288
6815
|
|
|
6289
|
-
test('
|
|
6816
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6290
6817
|
const calls: string[] = [];
|
|
6818
|
+
const features = { unlocked: true };
|
|
6291
6819
|
const method = {
|
|
6292
|
-
name: '
|
|
6293
|
-
unlockPolicy: '
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
.
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
})
|
|
6301
|
-
.mockImplementationOnce(() => {
|
|
6302
|
-
calls.push('run-2');
|
|
6303
|
-
return Promise.resolve({ message: 'ok' });
|
|
6304
|
-
}),
|
|
6820
|
+
name: 'confluxSignMessageCIP23',
|
|
6821
|
+
unlockPolicy: 'none',
|
|
6822
|
+
useDevicePassphraseState: true,
|
|
6823
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6824
|
+
run: jest.fn(() => {
|
|
6825
|
+
calls.push('run');
|
|
6826
|
+
return Promise.resolve({ address: '0x1' });
|
|
6827
|
+
}),
|
|
6305
6828
|
};
|
|
6306
6829
|
const device = {
|
|
6830
|
+
features,
|
|
6831
|
+
commands: {
|
|
6832
|
+
typedCall: jest.fn(() => {
|
|
6833
|
+
calls.push('status');
|
|
6834
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6835
|
+
}),
|
|
6836
|
+
},
|
|
6307
6837
|
isProtocolV2: () => true,
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6838
|
+
isBootloader: () => false,
|
|
6839
|
+
isRomloader: () => false,
|
|
6840
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6841
|
+
features.unlocked = status.unlocked === true;
|
|
6842
|
+
return features;
|
|
6311
6843
|
}),
|
|
6844
|
+
unlockDevice: jest.fn(),
|
|
6312
6845
|
};
|
|
6313
6846
|
const uiCoordinator = {
|
|
6314
6847
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6848
|
+
enterUnlockInteraction: jest.fn(),
|
|
6317
6849
|
};
|
|
6318
6850
|
|
|
6319
6851
|
await expect(
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
]);
|
|
6852
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6853
|
+
uiCoordinator: uiCoordinator as any,
|
|
6854
|
+
})
|
|
6855
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6856
|
+
|
|
6857
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6858
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6859
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6860
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6330
6861
|
});
|
|
6331
6862
|
|
|
6332
|
-
test('
|
|
6863
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6333
6864
|
const calls: string[] = [];
|
|
6865
|
+
const identityError = new Error('Unexpected device');
|
|
6334
6866
|
const method = {
|
|
6335
6867
|
name: 'evmSignMessage',
|
|
6336
|
-
|
|
6868
|
+
unlockPolicy: 'none',
|
|
6337
6869
|
useDevicePassphraseState: true,
|
|
6338
|
-
|
|
6339
|
-
run: jest
|
|
6340
|
-
.fn()
|
|
6341
|
-
.mockImplementationOnce(() => {
|
|
6342
|
-
calls.push('run-1');
|
|
6343
|
-
return Promise.reject(deviceLockedError());
|
|
6344
|
-
})
|
|
6345
|
-
.mockImplementationOnce(() => {
|
|
6346
|
-
calls.push('run-2');
|
|
6347
|
-
return Promise.resolve({ message: 'ok' });
|
|
6348
|
-
}),
|
|
6870
|
+
run: jest.fn(),
|
|
6349
6871
|
};
|
|
6350
|
-
const
|
|
6351
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6352
|
-
calls.push('negotiate-session');
|
|
6353
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6354
|
-
}
|
|
6355
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6356
|
-
calls.push('resume-hidden-session');
|
|
6357
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6358
|
-
return Promise.resolve({
|
|
6359
|
-
message: {
|
|
6360
|
-
session_id: 'hidden-session',
|
|
6361
|
-
btc_test_address: 'hidden-state',
|
|
6362
|
-
},
|
|
6363
|
-
});
|
|
6364
|
-
}
|
|
6365
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6366
|
-
});
|
|
6872
|
+
const features = { unlocked: false };
|
|
6367
6873
|
const device = {
|
|
6368
|
-
features
|
|
6369
|
-
|
|
6370
|
-
|
|
6874
|
+
features,
|
|
6875
|
+
commands: {
|
|
6876
|
+
typedCall: jest.fn(() => {
|
|
6877
|
+
calls.push('status');
|
|
6878
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6879
|
+
}),
|
|
6880
|
+
},
|
|
6371
6881
|
isProtocolV2: () => true,
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
getCurrentPassphraseProtection: () => true,
|
|
6377
|
-
getInternalState: () => 'hidden-session',
|
|
6378
|
-
clearInternalState: jest.fn(),
|
|
6379
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6380
|
-
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6882
|
+
isBootloader: () => false,
|
|
6883
|
+
isRomloader: () => false,
|
|
6884
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6885
|
+
unlockDevice: jest.fn(),
|
|
6381
6886
|
};
|
|
6382
6887
|
|
|
6383
|
-
await expect(
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
'validate-hidden-session',
|
|
6392
|
-
'run-2',
|
|
6393
|
-
]);
|
|
6394
|
-
});
|
|
6395
|
-
|
|
6396
|
-
test('does not replay a business method when the hidden-wallet session cannot be restored after unlock', async () => {
|
|
6397
|
-
const initialError = deviceLockedError();
|
|
6398
|
-
const restoreError = new Error('Invalid wallet session');
|
|
6399
|
-
const method = {
|
|
6400
|
-
name: 'evmSignMessage',
|
|
6401
|
-
payload: { passphraseState: 'hidden-state' },
|
|
6402
|
-
useDevicePassphraseState: true,
|
|
6403
|
-
unlockPolicy: 'retry-on-locked',
|
|
6404
|
-
run: jest.fn().mockRejectedValueOnce(initialError),
|
|
6405
|
-
};
|
|
6406
|
-
const typedCall = jest.fn((requestType: string) => {
|
|
6407
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6408
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6409
|
-
}
|
|
6410
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6411
|
-
return Promise.reject(restoreError);
|
|
6412
|
-
}
|
|
6413
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6414
|
-
});
|
|
6415
|
-
const device = {
|
|
6416
|
-
features: { unlocked: true, passphraseProtection: true },
|
|
6417
|
-
passphraseState: 'hidden-state',
|
|
6418
|
-
commands: { typedCall },
|
|
6419
|
-
isProtocolV2: () => true,
|
|
6420
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6421
|
-
getCurrentPassphraseProtection: () => true,
|
|
6422
|
-
getInternalState: () => 'hidden-session',
|
|
6423
|
-
clearInternalState: jest.fn(),
|
|
6424
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6425
|
-
updateInternalState: jest.fn(),
|
|
6426
|
-
};
|
|
6888
|
+
await expect(
|
|
6889
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6890
|
+
afterStatusBeforeUnlock: () => {
|
|
6891
|
+
calls.push('identity');
|
|
6892
|
+
throw identityError;
|
|
6893
|
+
},
|
|
6894
|
+
})
|
|
6895
|
+
).rejects.toBe(identityError);
|
|
6427
6896
|
|
|
6428
|
-
|
|
6429
|
-
expect(
|
|
6430
|
-
expect(
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6897
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6898
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6899
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6433
6900
|
});
|
|
6434
6901
|
|
|
6435
|
-
test('
|
|
6902
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6436
6903
|
const calls: string[] = [];
|
|
6904
|
+
const features = { unlocked: true };
|
|
6437
6905
|
const method = {
|
|
6438
6906
|
name: 'evmSignMessage',
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
unlockPolicy: 'retry-on-locked',
|
|
6907
|
+
unlockPolicy: 'unlock-before-run',
|
|
6908
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6442
6909
|
run: jest.fn(() => {
|
|
6443
6910
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6911
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6445
6912
|
}),
|
|
6446
6913
|
};
|
|
6447
|
-
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6448
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
6449
|
-
calls.push('negotiate-session');
|
|
6450
|
-
return Promise.resolve({ message: { version: 2 } });
|
|
6451
|
-
}
|
|
6452
|
-
if (requestType === 'DeviceSessionGet') {
|
|
6453
|
-
calls.push('resume-hidden-session');
|
|
6454
|
-
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6455
|
-
return Promise.resolve({
|
|
6456
|
-
message: {
|
|
6457
|
-
session_id: 'hidden-session',
|
|
6458
|
-
btc_test_address: 'hidden-state',
|
|
6459
|
-
},
|
|
6460
|
-
});
|
|
6461
|
-
}
|
|
6462
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
6463
|
-
});
|
|
6464
|
-
const features = {
|
|
6465
|
-
unlocked: false,
|
|
6466
|
-
unlockedAttachPin: true,
|
|
6467
|
-
passphraseProtection: true,
|
|
6468
|
-
};
|
|
6469
6914
|
const device = {
|
|
6470
6915
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6916
|
+
commands: {
|
|
6917
|
+
typedCall: jest.fn(() => {
|
|
6918
|
+
calls.push('status');
|
|
6919
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6920
|
+
}),
|
|
6921
|
+
},
|
|
6473
6922
|
isProtocolV2: () => true,
|
|
6923
|
+
isBootloader: () => false,
|
|
6924
|
+
isRomloader: () => false,
|
|
6925
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6926
|
+
features.unlocked = status.unlocked === true;
|
|
6927
|
+
return features;
|
|
6928
|
+
}),
|
|
6474
6929
|
unlockDevice: jest.fn(() => {
|
|
6475
|
-
calls.push('unlock
|
|
6930
|
+
calls.push('unlock');
|
|
6476
6931
|
features.unlocked = true;
|
|
6477
|
-
features
|
|
6478
|
-
|
|
6932
|
+
return Promise.resolve(features);
|
|
6933
|
+
}),
|
|
6934
|
+
};
|
|
6935
|
+
const uiCoordinator = {
|
|
6936
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6937
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
6938
|
+
calls.push('unlock-prompt');
|
|
6939
|
+
return undefined;
|
|
6479
6940
|
}),
|
|
6480
|
-
getCurrentPassphraseProtection: () => true,
|
|
6481
|
-
getInternalState: () => 'hidden-session',
|
|
6482
|
-
clearInternalState: jest.fn(),
|
|
6483
|
-
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6484
|
-
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6485
6941
|
};
|
|
6486
6942
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6943
|
+
await expect(
|
|
6944
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6945
|
+
uiCoordinator: uiCoordinator as any,
|
|
6946
|
+
prepare: () => {
|
|
6947
|
+
calls.push('wallet-session');
|
|
6948
|
+
return Promise.resolve();
|
|
6949
|
+
},
|
|
6950
|
+
})
|
|
6951
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
6952
|
+
|
|
6491
6953
|
expect(calls).toEqual([
|
|
6492
|
-
'
|
|
6493
|
-
'
|
|
6494
|
-
'
|
|
6495
|
-
'
|
|
6954
|
+
'status',
|
|
6955
|
+
'unlock-prompt',
|
|
6956
|
+
'unlock',
|
|
6957
|
+
'wallet-session',
|
|
6958
|
+
'method-prompt',
|
|
6496
6959
|
'run',
|
|
6497
6960
|
]);
|
|
6961
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6962
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6963
|
});
|
|
6499
6964
|
|
|
6500
|
-
test('
|
|
6965
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6501
6966
|
const method = {
|
|
6502
6967
|
name: 'evmGetAddress',
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
unlockPolicy: 'retry-on-locked',
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6968
|
+
unlockPolicy: 'unlock-before-run',
|
|
6969
|
+
run: jest.fn(),
|
|
6507
6970
|
};
|
|
6508
6971
|
const device = {
|
|
6509
|
-
features: { unlocked:
|
|
6510
|
-
|
|
6972
|
+
features: { unlocked: undefined },
|
|
6973
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6511
6974
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6975
|
+
isBootloader: () => false,
|
|
6976
|
+
isRomloader: () => false,
|
|
6977
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6978
|
+
unlockDevice: jest.fn(),
|
|
6513
6979
|
};
|
|
6514
6980
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6981
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6982
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6517
6983
|
});
|
|
6518
|
-
expect(device.unlockDevice).
|
|
6519
|
-
expect(method.run).
|
|
6984
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6985
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6986
|
});
|
|
6521
6987
|
|
|
6522
|
-
test('
|
|
6523
|
-
const
|
|
6988
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6989
|
+
const error = deviceLockedError();
|
|
6524
6990
|
const method = {
|
|
6525
|
-
name: '
|
|
6991
|
+
name: 'evmSignMessage',
|
|
6526
6992
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
|
|
6528
|
-
run: jest.fn(() => {
|
|
6529
|
-
calls.push('run');
|
|
6530
|
-
return Promise.resolve({ message: 'ok' });
|
|
6531
|
-
}),
|
|
6993
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6532
6994
|
};
|
|
6533
6995
|
const features = { unlocked: true };
|
|
6534
6996
|
const device = {
|
|
6535
6997
|
features,
|
|
6536
|
-
commands: {
|
|
6537
|
-
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('refresh-status');
|
|
6539
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
-
}),
|
|
6541
|
-
},
|
|
6998
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6542
6999
|
isProtocolV2: () => true,
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
unlockDevice: jest.fn(() => {
|
|
6548
|
-
calls.push('unlock');
|
|
6549
|
-
features.unlocked = true;
|
|
6550
|
-
return Promise.resolve();
|
|
6551
|
-
}),
|
|
6552
|
-
};
|
|
6553
|
-
const uiCoordinator = {
|
|
6554
|
-
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6555
|
-
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6556
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
7000
|
+
isBootloader: () => false,
|
|
7001
|
+
isRomloader: () => false,
|
|
7002
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
7003
|
+
unlockDevice: jest.fn(),
|
|
6557
7004
|
};
|
|
6558
7005
|
|
|
6559
|
-
await expect(
|
|
6560
|
-
|
|
6561
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6562
|
-
expect(calls).toEqual(['refresh-status', 'unlock-prompt', 'unlock', 'method-prompt', 'run']);
|
|
6563
|
-
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
7006
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
7007
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6564
7008
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
7009
|
});
|
|
6568
7010
|
|
|
6569
|
-
test('
|
|
6570
|
-
const features = { unlocked:
|
|
6571
|
-
const
|
|
6572
|
-
|
|
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',
|
|
6573
7016
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().mockResolvedValue({
|
|
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' }),
|
|
6575
7023
|
};
|
|
6576
7024
|
const device = {
|
|
6577
7025
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
7026
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
7027
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
7028
|
+
isBootloader: () => false,
|
|
7029
|
+
isRomloader: () => false,
|
|
7030
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
7031
|
unlockDevice: jest.fn(),
|
|
6587
7032
|
};
|
|
6588
7033
|
|
|
6589
|
-
await
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
expect(device.
|
|
6593
|
-
expect(
|
|
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);
|
|
6594
7040
|
});
|
|
6595
7041
|
|
|
6596
7042
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6610,7 +7056,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
7056
|
unlockDevice: jest.fn(),
|
|
6611
7057
|
};
|
|
6612
7058
|
|
|
6613
|
-
await expect(
|
|
7059
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
7060
|
message: 'updating',
|
|
6615
7061
|
});
|
|
6616
7062
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +7065,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
7065
|
}
|
|
6620
7066
|
);
|
|
6621
7067
|
|
|
6622
|
-
test('
|
|
6623
|
-
const method = {
|
|
6624
|
-
name: 'evmSignMessage',
|
|
6625
|
-
params: { message: 'not-exposed-in-event' },
|
|
6626
|
-
payload: {},
|
|
6627
|
-
unlockPolicy: 'retry-on-locked',
|
|
6628
|
-
run: jest.fn().mockResolvedValue({ signature: 'test' }),
|
|
6629
|
-
};
|
|
6630
|
-
const device = {
|
|
6631
|
-
isProtocolV2: () => true,
|
|
6632
|
-
unlockDevice: jest.fn(),
|
|
6633
|
-
};
|
|
6634
|
-
const uiCoordinator = {
|
|
6635
|
-
enterMethodInteraction: jest.fn(),
|
|
6636
|
-
enterUnlockInteraction: jest.fn(),
|
|
6637
|
-
resumeMethodInteraction: jest.fn(),
|
|
6638
|
-
};
|
|
6639
|
-
|
|
6640
|
-
await runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any);
|
|
6641
|
-
|
|
6642
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledWith({
|
|
6643
|
-
request: 'button',
|
|
6644
|
-
source: 'method-lifecycle',
|
|
6645
|
-
reason: 'signing-confirmation',
|
|
6646
|
-
completion: 'operation-completed',
|
|
6647
|
-
deviceOnly: true,
|
|
6648
|
-
operation: 'evmSignMessage',
|
|
6649
|
-
});
|
|
6650
|
-
});
|
|
6651
|
-
|
|
6652
|
-
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
7068
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
7069
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
7070
|
+
[false, 'unlock-before-run'],
|
|
6655
7071
|
[true, 'none'],
|
|
6656
7072
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
7073
|
const method = {
|
|
6659
7074
|
unlockPolicy,
|
|
6660
|
-
|
|
7075
|
+
useDevicePassphraseState: false,
|
|
7076
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
7077
|
};
|
|
6662
7078
|
const device = {
|
|
7079
|
+
commands: { typedCall: jest.fn() },
|
|
6663
7080
|
isProtocolV2: () => isProtocolV2,
|
|
6664
7081
|
unlockDevice: jest.fn(),
|
|
6665
7082
|
};
|
|
6666
7083
|
|
|
6667
|
-
await expect(
|
|
7084
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
7085
|
+
message: 'ok',
|
|
7086
|
+
});
|
|
7087
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
7088
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
7089
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
7090
|
}
|
|
6671
7091
|
});
|
|
6672
|
-
|
|
6673
|
-
test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
|
|
6674
|
-
const method = {
|
|
6675
|
-
name: 'uploadPortfolio',
|
|
6676
|
-
unlockPolicy: 'none',
|
|
6677
|
-
protocolV2UiMode: 'none',
|
|
6678
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6679
|
-
};
|
|
6680
|
-
const device = {
|
|
6681
|
-
features: { unlocked: false },
|
|
6682
|
-
isProtocolV2: () => true,
|
|
6683
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6684
|
-
};
|
|
6685
|
-
const uiCoordinator = {
|
|
6686
|
-
enterMethodInteraction: jest.fn(),
|
|
6687
|
-
enterUnlockInteraction: jest.fn(),
|
|
6688
|
-
resumeMethodInteraction: jest.fn(),
|
|
6689
|
-
};
|
|
6690
|
-
|
|
6691
|
-
await expect(
|
|
6692
|
-
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6693
|
-
).resolves.toEqual({ message: 'ok' });
|
|
6694
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6695
|
-
expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
|
|
6696
|
-
expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
|
|
6697
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6698
|
-
});
|
|
6699
|
-
|
|
6700
|
-
test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
|
|
6701
|
-
const error = deviceLockedError();
|
|
6702
|
-
const method = {
|
|
6703
|
-
name: 'uploadPortfolio',
|
|
6704
|
-
unlockPolicy: 'none',
|
|
6705
|
-
run: jest.fn().mockRejectedValue(error),
|
|
6706
|
-
};
|
|
6707
|
-
const device = {
|
|
6708
|
-
features: { unlocked: true },
|
|
6709
|
-
isProtocolV2: () => true,
|
|
6710
|
-
unlockDevice: jest.fn(),
|
|
6711
|
-
};
|
|
6712
|
-
|
|
6713
|
-
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6714
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6715
|
-
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6716
|
-
});
|
|
6717
|
-
|
|
6718
|
-
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
6719
|
-
const initialError = deviceLockedError();
|
|
6720
|
-
const unlockError = new Error('PIN cancelled');
|
|
6721
|
-
const unlockFailMethod = {
|
|
6722
|
-
unlockPolicy: 'retry-on-locked',
|
|
6723
|
-
run: jest.fn().mockRejectedValue(initialError),
|
|
6724
|
-
};
|
|
6725
|
-
const unlockFailDevice = {
|
|
6726
|
-
isProtocolV2: () => true,
|
|
6727
|
-
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
6728
|
-
};
|
|
6729
|
-
const unlockFailCoordinator = {
|
|
6730
|
-
enterMethodInteraction: jest.fn(),
|
|
6731
|
-
enterUnlockInteraction: jest.fn(),
|
|
6732
|
-
resumeMethodInteraction: jest.fn(),
|
|
6733
|
-
};
|
|
6734
|
-
|
|
6735
|
-
await expect(
|
|
6736
|
-
runMethodWithUnlockRetry(
|
|
6737
|
-
unlockFailMethod as any,
|
|
6738
|
-
unlockFailDevice as any,
|
|
6739
|
-
unlockFailCoordinator as any
|
|
6740
|
-
)
|
|
6741
|
-
).rejects.toBe(unlockError);
|
|
6742
|
-
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
6743
|
-
expect(unlockFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6744
|
-
expect(unlockFailCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6745
|
-
|
|
6746
|
-
const retryError = deviceLockedError();
|
|
6747
|
-
const retryFailMethod = {
|
|
6748
|
-
unlockPolicy: 'retry-on-locked',
|
|
6749
|
-
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
6750
|
-
};
|
|
6751
|
-
const retryFailDevice = {
|
|
6752
|
-
isProtocolV2: () => true,
|
|
6753
|
-
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6754
|
-
};
|
|
6755
|
-
const retryFailCoordinator = {
|
|
6756
|
-
enterMethodInteraction: jest.fn(),
|
|
6757
|
-
enterUnlockInteraction: jest.fn(),
|
|
6758
|
-
resumeMethodInteraction: jest.fn(),
|
|
6759
|
-
};
|
|
6760
|
-
|
|
6761
|
-
await expect(
|
|
6762
|
-
runMethodWithUnlockRetry(
|
|
6763
|
-
retryFailMethod as any,
|
|
6764
|
-
retryFailDevice as any,
|
|
6765
|
-
retryFailCoordinator as any
|
|
6766
|
-
)
|
|
6767
|
-
).rejects.toBe(retryError);
|
|
6768
|
-
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
6769
|
-
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6770
|
-
expect(retryFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6771
|
-
expect(retryFailCoordinator.resumeMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6772
|
-
});
|
|
6773
7092
|
});
|
|
6774
7093
|
|
|
6775
7094
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +7220,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
7220
|
|
|
6902
7221
|
await method.run();
|
|
6903
7222
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7223
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
7224
|
+
eventless_wallet_session: true,
|
|
7225
|
+
});
|
|
6905
7226
|
});
|
|
6906
7227
|
|
|
6907
7228
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
@@ -6977,6 +7298,29 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6977
7298
|
|
|
6978
7299
|
await expect(method.run()).resolves.toBe(features);
|
|
6979
7300
|
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
|
+
});
|
|
6980
7324
|
});
|
|
6981
7325
|
|
|
6982
7326
|
test.each([
|