@onekeyfe/hd-core 1.2.0-alpha.41 → 1.2.0-alpha.43
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__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +14 -1
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/deviceUploadNft.test.ts +272 -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__/open-wallet-session.test.ts +366 -31
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-resources.test.ts +204 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +196 -35
- package/__tests__/protocol-v2.test.ts +568 -957
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.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 +6 -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/allnetwork/AllNetworkGetAddressBase.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 -8
- 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 +1 -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/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/data-manager/DataManager.d.ts +3 -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 +24 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- 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-request.d.ts +28 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +115 -184
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2051 -3349
- 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 +4 -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 +1 -4
- package/dist/types/api/checkAllFirmwareRelease.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 +1 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +0 -69
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +3 -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/settings.d.ts +12 -13
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -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 +40 -53
- package/src/api/FirmwareUpdateV2.ts +120 -298
- package/src/api/FirmwareUpdateV3.ts +57 -263
- package/src/api/FirmwareUpdateV4.ts +307 -912
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +54 -17
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/device/DeviceUpdateBootloader.ts +6 -122
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -57
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +22 -140
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +1 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +136 -77
- package/src/data/messages/messages-protocol-v2.json +68 -0
- package/src/data-manager/DataManager.ts +63 -38
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +203 -21
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-request.ts +33 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +3 -22
- package/src/lowLevelInject.ts +1 -5
- package/src/protocols/protocol-v2/features.ts +9 -2
- package/src/protocols/protocol-v2/resources.ts +233 -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 +124 -0
- package/src/protocols/protocol-v2/walletSession.ts +154 -30
- package/src/topLevelInject.ts +1 -5
- package/src/types/api/checkAllFirmwareRelease.ts +1 -4
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +0 -18
- package/src/types/api/firmwareUpdate.ts +0 -76
- package/src/types/api/index.ts +2 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/settings.ts +26 -13
- package/src/utils/deviceFeaturesUtils.ts +4 -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__/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-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
|
@@ -61,6 +61,7 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
61
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
62
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
63
|
import { DevicePool } from '../src/device/DevicePool';
|
|
64
|
+
import { DEVICE } from '../src/events';
|
|
64
65
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
65
66
|
import {
|
|
66
67
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -78,7 +79,10 @@ import {
|
|
|
78
79
|
getProtocolV2WalletSession,
|
|
79
80
|
refreshProtocolV2DeviceStatus,
|
|
80
81
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
81
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
createProtocolV2UnlockContext,
|
|
84
|
+
runMethodWithUnlockPolicy,
|
|
85
|
+
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
82
86
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
83
87
|
import {
|
|
84
88
|
buildProtocolV1FeaturesPayload,
|
|
@@ -92,7 +96,6 @@ import {
|
|
|
92
96
|
getMethodVersionRange,
|
|
93
97
|
} from '../src/utils';
|
|
94
98
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
-
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
96
99
|
import {
|
|
97
100
|
getPassphraseState,
|
|
98
101
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -241,7 +244,7 @@ describe('UploadPortfolio', () => {
|
|
|
241
244
|
(method as any).device = device;
|
|
242
245
|
|
|
243
246
|
method.init();
|
|
244
|
-
await
|
|
247
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
245
248
|
|
|
246
249
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
247
250
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -417,14 +420,16 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
417
420
|
}
|
|
418
421
|
return d.features;
|
|
419
422
|
};
|
|
423
|
+
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
424
|
+
requestProtocolV2ProtocolInfo({
|
|
425
|
+
commands: d.getCommands?.() ?? d.commands,
|
|
426
|
+
timeoutMs,
|
|
427
|
+
});
|
|
420
428
|
d.probeProtocolV2RuntimeState ??= async (
|
|
421
429
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
422
430
|
timeoutMs?: number
|
|
423
431
|
) => {
|
|
424
|
-
const protocolInfo = await
|
|
425
|
-
commands: d.getCommands(),
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
432
|
+
const protocolInfo = await d.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
428
433
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
429
434
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
430
435
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -805,7 +810,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
810
|
).resolves.toEqual({
|
|
806
811
|
passphraseState: 'state-1',
|
|
807
812
|
newSession: 'session-1',
|
|
808
|
-
unlockedAttachPin:
|
|
813
|
+
unlockedAttachPin: false,
|
|
809
814
|
resumed: false,
|
|
810
815
|
});
|
|
811
816
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -814,7 +819,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
814
819
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
815
820
|
type: DeviceSessionPinType.Main,
|
|
816
821
|
});
|
|
817
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
822
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
823
|
+
seed_domains: [],
|
|
824
|
+
});
|
|
818
825
|
device.passphraseState = 'state-1';
|
|
819
826
|
expect(device.getInternalState()).toBe('session-1');
|
|
820
827
|
});
|
|
@@ -863,9 +870,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
863
870
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
864
871
|
|
|
865
872
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPin')).toHaveLength(1);
|
|
866
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
873
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
874
|
+
seed_domains: [],
|
|
875
|
+
});
|
|
867
876
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
868
877
|
session_id: 'standard-session',
|
|
878
|
+
btc_test_address: 'standard-state',
|
|
879
|
+
seed_domains: [],
|
|
869
880
|
});
|
|
870
881
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
871
882
|
passphraseState: 'standard-state',
|
|
@@ -1227,7 +1238,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1227
1238
|
passphrase: 'host hidden wallet',
|
|
1228
1239
|
on_device: false,
|
|
1229
1240
|
});
|
|
1230
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1241
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1242
|
+
seed_domains: [],
|
|
1243
|
+
});
|
|
1231
1244
|
});
|
|
1232
1245
|
|
|
1233
1246
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1335,6 +1348,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1335
1348
|
|
|
1336
1349
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1337
1350
|
session_id: 'session-a',
|
|
1351
|
+
btc_test_address: 'state-a',
|
|
1352
|
+
seed_domains: [],
|
|
1338
1353
|
});
|
|
1339
1354
|
expect(device.getInternalState()).toBe('session-b');
|
|
1340
1355
|
});
|
|
@@ -1402,7 +1417,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1402
1417
|
});
|
|
1403
1418
|
expect(typedCall.mock.calls).toEqual([
|
|
1404
1419
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1405
|
-
[
|
|
1420
|
+
[
|
|
1421
|
+
'DeviceSessionGet',
|
|
1422
|
+
'DeviceSession',
|
|
1423
|
+
{
|
|
1424
|
+
session_id: 'session-a',
|
|
1425
|
+
btc_test_address: 'state-a',
|
|
1426
|
+
seed_domains: [],
|
|
1427
|
+
},
|
|
1428
|
+
],
|
|
1406
1429
|
]);
|
|
1407
1430
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1408
1431
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1450,7 +1473,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1450
1473
|
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1451
1474
|
],
|
|
1452
1475
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1453
|
-
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1476
|
+
['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
|
|
1454
1477
|
]);
|
|
1455
1478
|
});
|
|
1456
1479
|
|
|
@@ -1553,6 +1576,73 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1576
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1577
|
});
|
|
1555
1578
|
|
|
1579
|
+
test.each([false, true])(
|
|
1580
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1581
|
+
async lockFails => {
|
|
1582
|
+
const deviceId = 'attach-mismatch-device';
|
|
1583
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1584
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1585
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1586
|
+
{
|
|
1587
|
+
status: {
|
|
1588
|
+
device_id: deviceId,
|
|
1589
|
+
unlocked: true,
|
|
1590
|
+
passphrase_enabled: true,
|
|
1591
|
+
attach_to_pin_enabled: true,
|
|
1592
|
+
unlocked_by_attach_to_pin: false,
|
|
1593
|
+
},
|
|
1594
|
+
}
|
|
1595
|
+
);
|
|
1596
|
+
device.passphraseState = 'expected-state';
|
|
1597
|
+
|
|
1598
|
+
const typedCall = jest.fn((request: string) => {
|
|
1599
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1600
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1601
|
+
if (request === 'DeviceStatusGet') {
|
|
1602
|
+
return {
|
|
1603
|
+
message: {
|
|
1604
|
+
device_id: deviceId,
|
|
1605
|
+
unlocked: true,
|
|
1606
|
+
passphrase_enabled: true,
|
|
1607
|
+
attach_to_pin_enabled: true,
|
|
1608
|
+
unlocked_by_attach_to_pin: true,
|
|
1609
|
+
},
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
if (request === 'DeviceSessionGet') {
|
|
1613
|
+
return {
|
|
1614
|
+
message: {
|
|
1615
|
+
btc_test_address: 'wrong-attach-state',
|
|
1616
|
+
session_id: 'wrong-attach-session',
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
if (request === 'LockDevice') {
|
|
1621
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1622
|
+
return { message: { message: 'Device locked' } };
|
|
1623
|
+
}
|
|
1624
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1625
|
+
});
|
|
1626
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1627
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1628
|
+
|
|
1629
|
+
await expect(
|
|
1630
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1631
|
+
).rejects.toMatchObject({
|
|
1632
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1636
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1637
|
+
btc_test_address: 'expected-state',
|
|
1638
|
+
seed_domains: [],
|
|
1639
|
+
});
|
|
1640
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1641
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1642
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1643
|
+
}
|
|
1644
|
+
);
|
|
1645
|
+
|
|
1556
1646
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1647
|
const device = Device.fromDescriptor({
|
|
1558
1648
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1699,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1699
|
}),
|
|
1610
1700
|
{ cancelDeviceOnReject: false }
|
|
1611
1701
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1702
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1703
|
+
DeviceSessionPinType.AttachToPin,
|
|
1704
|
+
expect.objectContaining({
|
|
1705
|
+
emitUiEvent: false,
|
|
1706
|
+
interaction: expect.objectContaining({
|
|
1707
|
+
phase: 'pin',
|
|
1708
|
+
transition: 'start',
|
|
1709
|
+
protocol: 'V2',
|
|
1710
|
+
}),
|
|
1711
|
+
})
|
|
1712
|
+
);
|
|
1613
1713
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1714
|
});
|
|
1615
1715
|
|
|
@@ -1893,12 +1993,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
1993
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
1994
|
});
|
|
1895
1995
|
|
|
1896
|
-
test('
|
|
1996
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
1997
|
const typedCall = jest
|
|
1898
1998
|
.fn()
|
|
1899
1999
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
2000
|
.mockResolvedValueOnce({
|
|
1903
2001
|
message: {
|
|
1904
2002
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2024,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2024
|
await expect(
|
|
1927
2025
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2026
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2027
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2028
|
+
btc_test_address: 'expected-state',
|
|
2029
|
+
seed_domains: [],
|
|
2030
|
+
});
|
|
2031
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2032
|
});
|
|
1940
2033
|
|
|
1941
2034
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,8 +2094,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2094
|
true
|
|
2002
2095
|
);
|
|
2003
2096
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
[
|
|
2005
|
-
|
|
2097
|
+
[
|
|
2098
|
+
'DeviceSessionGet',
|
|
2099
|
+
'DeviceSession',
|
|
2100
|
+
{
|
|
2101
|
+
session_id: 'session-pro2-app',
|
|
2102
|
+
btc_test_address: 'state-pro2-app',
|
|
2103
|
+
seed_domains: [],
|
|
2104
|
+
},
|
|
2105
|
+
],
|
|
2006
2106
|
]);
|
|
2007
2107
|
});
|
|
2008
2108
|
|
|
@@ -2191,7 +2291,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2191
2291
|
passphrase: 'host hidden wallet',
|
|
2192
2292
|
on_device: false,
|
|
2193
2293
|
});
|
|
2194
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2294
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2295
|
+
seed_domains: [],
|
|
2296
|
+
});
|
|
2195
2297
|
});
|
|
2196
2298
|
|
|
2197
2299
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2269,12 +2371,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2371
|
);
|
|
2270
2372
|
|
|
2271
2373
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2374
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2375
|
+
btc_test_address: 'expected-state',
|
|
2376
|
+
seed_domains: [],
|
|
2377
|
+
});
|
|
2378
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2379
|
passphrase: 'host hidden wallet',
|
|
2274
2380
|
on_device: false,
|
|
2275
2381
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2382
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2383
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2384
|
+
seed_domains: [],
|
|
2385
|
+
});
|
|
2278
2386
|
});
|
|
2279
2387
|
|
|
2280
2388
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2307,10 +2415,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2307
2415
|
await expect(
|
|
2308
2416
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
2309
2417
|
).resolves.toBe(true);
|
|
2310
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
2418
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2311
2419
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2420
|
eventless_wallet_session: true,
|
|
2313
2421
|
});
|
|
2422
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2423
|
+
seed_domains: [],
|
|
2424
|
+
});
|
|
2425
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', expect.anything());
|
|
2314
2426
|
});
|
|
2315
2427
|
|
|
2316
2428
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2681,7 +2793,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2793
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2794
|
}
|
|
2683
2795
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2796
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2797
|
+
eventless_wallet_session: true,
|
|
2798
|
+
});
|
|
2685
2799
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2800
|
});
|
|
2687
2801
|
|
|
@@ -2719,7 +2833,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2833
|
}),
|
|
2720
2834
|
expect.anything()
|
|
2721
2835
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2836
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2837
|
+
eventless_wallet_session: true,
|
|
2838
|
+
});
|
|
2723
2839
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2840
|
expect(device.features).toMatchObject({
|
|
2725
2841
|
deviceId: null,
|
|
@@ -2985,10 +3101,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3101
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3102
|
},
|
|
2987
3103
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3104
|
.mockResolvedValueOnce({
|
|
2993
3105
|
type: 'DeviceStatus',
|
|
2994
3106
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3122,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3122
|
firmwareVersion: '1.2.4',
|
|
3011
3123
|
passphraseProtection: null,
|
|
3012
3124
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3125
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3126
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3127
|
4,
|
|
3016
3128
|
'DeviceInfoGet',
|
|
@@ -3124,9 +3236,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3236
|
status: { init_states: true },
|
|
3125
3237
|
}
|
|
3126
3238
|
);
|
|
3239
|
+
const pinOnDevice = jest.fn();
|
|
3240
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3241
|
|
|
3128
3242
|
const features = await device.unlockDevice();
|
|
3129
3243
|
|
|
3244
|
+
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3245
|
+
device,
|
|
3246
|
+
DeviceSessionPinType.Main,
|
|
3247
|
+
expect.objectContaining({
|
|
3248
|
+
source: 'unlock-coordinator',
|
|
3249
|
+
reason: 'device-unlock',
|
|
3250
|
+
deviceOnly: true,
|
|
3251
|
+
})
|
|
3252
|
+
);
|
|
3253
|
+
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3254
|
+
typedCall.mock.invocationCallOrder[0]
|
|
3255
|
+
);
|
|
3130
3256
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3257
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3258
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3298,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3298
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3299
|
});
|
|
3174
3300
|
(device as any).commands = { typedCall };
|
|
3301
|
+
const pinOnDevice = jest.fn();
|
|
3302
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3303
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3304
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3305
|
|
|
3306
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3307
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3308
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3309
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3577,6 +3706,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3706
|
|
|
3578
3707
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3708
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3709
|
+
let forceReloadDataSpy: jest.SpyInstance<Promise<void>, []>;
|
|
3710
|
+
|
|
3711
|
+
beforeEach(() => {
|
|
3712
|
+
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
afterEach(() => {
|
|
3716
|
+
jest.restoreAllMocks();
|
|
3717
|
+
});
|
|
3580
3718
|
|
|
3581
3719
|
const buildOkppHeader = ({
|
|
3582
3720
|
type = 'RESC',
|
|
@@ -3717,7 +3855,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3855
|
});
|
|
3718
3856
|
});
|
|
3719
3857
|
|
|
3720
|
-
test('
|
|
3858
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3859
|
const method = new FirmwareUpdateV4({
|
|
3722
3860
|
id: 1,
|
|
3723
3861
|
payload: {
|
|
@@ -3752,7 +3890,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3890
|
|
|
3753
3891
|
await method.run();
|
|
3754
3892
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3893
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3894
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3895
|
fwBinaryMap: [
|
|
3758
3896
|
{
|
|
@@ -3866,6 +4004,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
4004
|
},
|
|
3867
4005
|
});
|
|
3868
4006
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4007
|
+
const prepareResourceBundles = jest
|
|
4008
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4009
|
+
.mockResolvedValue(undefined);
|
|
3869
4010
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
4011
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
4012
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +4019,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
4019
|
|
|
3879
4020
|
await method.run();
|
|
3880
4021
|
|
|
4022
|
+
expect(prepareResourceBundles).toHaveBeenCalledWith(true);
|
|
3881
4023
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
4024
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
4025
|
bootloaderBinary: null,
|
|
@@ -4135,9 +4277,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4277
|
const typedCall = jest
|
|
4136
4278
|
.fn()
|
|
4137
4279
|
.mockRejectedValue(
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4280
|
+
new DOMException(
|
|
4281
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4282
|
+
'NotFoundError'
|
|
4141
4283
|
)
|
|
4142
4284
|
);
|
|
4143
4285
|
|
|
@@ -4318,77 +4460,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4460
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4461
|
});
|
|
4320
4462
|
|
|
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
4463
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4393
4464
|
const method = new FirmwareUpdateV4({
|
|
4394
4465
|
id: 1,
|
|
@@ -4465,7 +4536,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4465
4536
|
).toBe(true);
|
|
4466
4537
|
|
|
4467
4538
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4468
|
-
expect(
|
|
4539
|
+
expect(
|
|
4469
4540
|
(method as any).assertProtocolV2TargetStatus(
|
|
4470
4541
|
[
|
|
4471
4542
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4473,8 +4544,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4473
4544
|
],
|
|
4474
4545
|
new Set([4, 10])
|
|
4475
4546
|
)
|
|
4476
|
-
).
|
|
4477
|
-
expect(method.postProgressMessage).
|
|
4547
|
+
).toBe(false);
|
|
4548
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4478
4549
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4479
4550
|
|
|
4480
4551
|
expect(
|
|
@@ -4578,14 +4649,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4578
4649
|
const writtenPaths: string[] = [];
|
|
4579
4650
|
method.postTipMessage = jest.fn();
|
|
4580
4651
|
method.postProgressMessage = jest.fn();
|
|
4581
|
-
(method as any).
|
|
4652
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4582
4653
|
writtenPaths.push(params.filePath);
|
|
4583
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4654
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4584
4655
|
});
|
|
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
4656
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4590
4657
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4591
4658
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4625,12 +4692,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
4692
|
'vol0:/coprocessor.bin',
|
|
4626
4693
|
1
|
|
4627
4694
|
);
|
|
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, {
|
|
4695
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
4696
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4633
4697
|
targets: [
|
|
4698
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4634
4699
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4635
4700
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4636
4701
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4640,7 +4705,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4640
4705
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4641
4706
|
});
|
|
4642
4707
|
|
|
4643
|
-
test('announces
|
|
4708
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4644
4709
|
const method = new FirmwareUpdateV4({
|
|
4645
4710
|
id: 1,
|
|
4646
4711
|
payload: {
|
|
@@ -4650,14 +4715,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4650
4715
|
|
|
4651
4716
|
method.postTipMessage = jest.fn();
|
|
4652
4717
|
method.postProgressMessage = jest.fn();
|
|
4653
|
-
(method as any).
|
|
4718
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4654
4719
|
.fn()
|
|
4655
4720
|
.mockImplementation(params =>
|
|
4656
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4721
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4657
4722
|
);
|
|
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
4723
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4662
4724
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4663
4725
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4677,22 +4739,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
4739
|
{
|
|
4678
4740
|
fileName: 'application_p1.bin',
|
|
4679
4741
|
binary: new Uint8Array([3]).buffer,
|
|
4680
|
-
targetId:
|
|
4742
|
+
targetId: 3,
|
|
4681
4743
|
},
|
|
4682
4744
|
],
|
|
4683
4745
|
});
|
|
4684
4746
|
|
|
4685
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4747
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4686
4748
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4687
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4688
|
-
expect(method.
|
|
4689
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
4749
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
4750
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4690
4751
|
1,
|
|
4691
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4752
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4692
4753
|
);
|
|
4693
|
-
expect((method as any).
|
|
4754
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4694
4755
|
2,
|
|
4695
|
-
expect.objectContaining({ processedSize:
|
|
4756
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4696
4757
|
);
|
|
4697
4758
|
});
|
|
4698
4759
|
|
|
@@ -4706,8 +4767,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4706
4767
|
|
|
4707
4768
|
method.postTipMessage = jest.fn();
|
|
4708
4769
|
method.postProgressMessage = jest.fn();
|
|
4709
|
-
(method as any).
|
|
4710
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4770
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4711
4771
|
(method as any).device = stubDevice({
|
|
4712
4772
|
getCommands: () => ({
|
|
4713
4773
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4753,14 +4813,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4753
4813
|
|
|
4754
4814
|
method.postTipMessage = jest.fn();
|
|
4755
4815
|
method.postProgressMessage = jest.fn();
|
|
4756
|
-
(method as any).
|
|
4816
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4757
4817
|
.fn()
|
|
4758
4818
|
.mockImplementation(params =>
|
|
4759
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4819
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4760
4820
|
);
|
|
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
4821
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4765
4822
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4766
4823
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4975,33 +5032,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4975
5032
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4976
5033
|
});
|
|
4977
5034
|
|
|
4978
|
-
test('
|
|
5035
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
4979
5036
|
const method = new FirmwareUpdateV4({
|
|
4980
5037
|
id: 1,
|
|
4981
5038
|
payload: {
|
|
4982
5039
|
method: 'firmwareUpdateV4',
|
|
4983
5040
|
platform: 'web',
|
|
4984
|
-
targetsToUpdate: ['resource'],
|
|
4985
5041
|
},
|
|
4986
5042
|
});
|
|
4987
5043
|
method.init();
|
|
4988
5044
|
|
|
5045
|
+
const getSysResourceBinarySpy = jest
|
|
5046
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5047
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
4989
5048
|
const getFirmwareLatestReleaseSpy = jest
|
|
4990
5049
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4991
5050
|
.mockReturnValue({
|
|
4992
5051
|
required: false,
|
|
4993
5052
|
version: [1, 0, 0],
|
|
4994
|
-
url: '',
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
headerHash: 'cd'.repeat(64),
|
|
5053
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5054
|
+
bootloaderVersion: [1, 0, 0],
|
|
5055
|
+
upgradeType: 'payload-package-set',
|
|
5056
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5057
|
+
components: {
|
|
5058
|
+
bootloader: {
|
|
5059
|
+
target: 'BOOTLOADER',
|
|
5060
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5003
5061
|
},
|
|
5004
|
-
|
|
5062
|
+
applicationP1: {
|
|
5063
|
+
target: 'APPLICATION_P1',
|
|
5064
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5065
|
+
},
|
|
5066
|
+
applicationP2: {
|
|
5067
|
+
target: 'APPLICATION_P2',
|
|
5068
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
5069
|
+
},
|
|
5070
|
+
},
|
|
5005
5071
|
fingerprint: '',
|
|
5006
5072
|
changelog: {
|
|
5007
5073
|
'zh-CN': '',
|
|
@@ -5009,399 +5075,94 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5009
5075
|
},
|
|
5010
5076
|
});
|
|
5011
5077
|
|
|
5012
|
-
const
|
|
5078
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5013
5079
|
deviceType: 'pro2',
|
|
5014
5080
|
firmwareVersion: '1.0.0',
|
|
5081
|
+
bootloaderVersion: '1.0.0',
|
|
5015
5082
|
capabilities: [],
|
|
5016
5083
|
});
|
|
5017
5084
|
|
|
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);
|
|
5085
|
+
expect(remoteBinaries).toEqual({
|
|
5086
|
+
bootloaderBinary: null,
|
|
5087
|
+
fwBinaryMap: [],
|
|
5088
|
+
installItems: [],
|
|
5089
|
+
});
|
|
5090
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5030
5091
|
|
|
5092
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5031
5093
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5032
5094
|
});
|
|
5033
5095
|
|
|
5034
|
-
test('
|
|
5096
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5035
5097
|
const method = new FirmwareUpdateV4({
|
|
5036
5098
|
id: 1,
|
|
5037
5099
|
payload: {
|
|
5038
5100
|
method: 'firmwareUpdateV4',
|
|
5039
5101
|
platform: 'web',
|
|
5040
|
-
targetsToUpdate: ['resource'],
|
|
5041
5102
|
},
|
|
5042
5103
|
});
|
|
5043
5104
|
method.init();
|
|
5044
5105
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5045
5106
|
|
|
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': '',
|
|
5107
|
+
const remoteBinaries = {
|
|
5108
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5109
|
+
fwBinaryMap: [
|
|
5110
|
+
{
|
|
5111
|
+
fileName: 'application_p1.bin',
|
|
5112
|
+
binary: new Uint8Array([2]).buffer,
|
|
5113
|
+
targetId: 3,
|
|
5064
5114
|
},
|
|
5065
|
-
|
|
5115
|
+
],
|
|
5116
|
+
};
|
|
5066
5117
|
|
|
5067
5118
|
(method as any).device = stubDevice({
|
|
5068
5119
|
originalDescriptor: { protocolType: 'V2' },
|
|
5069
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5120
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5070
5121
|
});
|
|
5071
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5072
|
-
|
|
5073
|
-
|
|
5122
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5123
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5124
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5125
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5126
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5127
|
+
bootloaderVersion: '1.0.0',
|
|
5128
|
+
bleVersion: '0.0.0',
|
|
5129
|
+
firmwareVersion: '1.0.0',
|
|
5074
5130
|
});
|
|
5075
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5076
5131
|
method.postTipMessage = jest.fn();
|
|
5077
5132
|
|
|
5078
|
-
await
|
|
5079
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5133
|
+
await method.run();
|
|
5081
5134
|
|
|
5082
|
-
|
|
5083
|
-
|
|
5135
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5136
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5137
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5138
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5139
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5140
|
+
});
|
|
5084
5141
|
});
|
|
5085
5142
|
|
|
5086
|
-
test('
|
|
5143
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5087
5144
|
const method = new FirmwareUpdateV4({
|
|
5088
5145
|
id: 1,
|
|
5089
5146
|
payload: {
|
|
5090
5147
|
method: 'firmwareUpdateV4',
|
|
5091
|
-
|
|
5092
|
-
});
|
|
5093
|
-
const openPreparedSource = jest.fn();
|
|
5094
|
-
(method as any).params = {
|
|
5095
|
-
targetsToUpdate: ['resource'],
|
|
5096
|
-
resourceBundleArtifacts: [
|
|
5097
|
-
{
|
|
5098
|
-
name: 'images',
|
|
5099
|
-
artifact: {
|
|
5100
|
-
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5101
|
-
size: 1,
|
|
5102
|
-
sha256: 'a'.repeat(64),
|
|
5103
|
-
},
|
|
5104
|
-
},
|
|
5105
|
-
],
|
|
5106
|
-
};
|
|
5107
|
-
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
-
.mockReturnValue({
|
|
5111
|
-
required: false,
|
|
5112
|
-
version: [1, 0, 0],
|
|
5113
|
-
url: '',
|
|
5114
|
-
resourceBundles: [
|
|
5115
|
-
{
|
|
5116
|
-
name: 'images',
|
|
5117
|
-
url: 'https://example.com/images.okpkg',
|
|
5118
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
-
},
|
|
5120
|
-
],
|
|
5121
|
-
fingerprint: '',
|
|
5122
|
-
changelog: {
|
|
5123
|
-
'zh-CN': '',
|
|
5124
|
-
'en-US': '',
|
|
5125
|
-
},
|
|
5126
|
-
});
|
|
5127
|
-
|
|
5128
|
-
await expect(
|
|
5129
|
-
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
-
deviceType: 'pro2',
|
|
5131
|
-
})
|
|
5132
|
-
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
-
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5134
|
-
|
|
5135
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5136
|
-
});
|
|
5137
|
-
|
|
5138
|
-
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5139
|
-
const method = new FirmwareUpdateV4({
|
|
5140
|
-
id: 1,
|
|
5141
|
-
payload: {
|
|
5142
|
-
method: 'firmwareUpdateV4',
|
|
5143
|
-
platform: 'web',
|
|
5144
|
-
targetsToUpdate: ['resource'],
|
|
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',
|
|
5148
|
+
platform: 'web',
|
|
5367
5149
|
},
|
|
5368
5150
|
});
|
|
5369
5151
|
method.init();
|
|
5370
5152
|
(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
5153
|
(method as any).device = stubDevice({
|
|
5384
5154
|
originalDescriptor: { protocolType: 'V2' },
|
|
5385
5155
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5386
5156
|
});
|
|
5387
|
-
(
|
|
5388
|
-
(method as any).
|
|
5389
|
-
(method as any).
|
|
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
|
-
});
|
|
5157
|
+
forceReloadDataSpy.mockRejectedValue(new Error('latest config unavailable'));
|
|
5158
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5159
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5396
5160
|
method.postTipMessage = jest.fn();
|
|
5397
5161
|
|
|
5398
|
-
await method.run();
|
|
5162
|
+
await expect(method.run()).rejects.toThrow('latest config unavailable');
|
|
5399
5163
|
|
|
5400
|
-
expect((method as any).prepareRemoteProtocolV2Binaries).
|
|
5401
|
-
expect((method as any).
|
|
5402
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5403
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5404
|
-
});
|
|
5164
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5165
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5405
5166
|
});
|
|
5406
5167
|
|
|
5407
5168
|
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
@@ -5529,10 +5290,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5529
5290
|
|
|
5530
5291
|
method.postTipMessage = jest.fn();
|
|
5531
5292
|
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);
|
|
5293
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5536
5294
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5537
5295
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5538
5296
|
|
|
@@ -5548,7 +5306,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5548
5306
|
fwBinaryMap: [],
|
|
5549
5307
|
});
|
|
5550
5308
|
|
|
5551
|
-
expect((method as any).
|
|
5309
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5552
5310
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5553
5311
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5554
5312
|
});
|
|
@@ -5579,19 +5337,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5579
5337
|
});
|
|
5580
5338
|
method.postProgressMessage = jest.fn();
|
|
5581
5339
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5340
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5341
|
+
payload: new Uint8Array(4097).buffer,
|
|
5342
|
+
filePath: 'vol1:firmware.bin',
|
|
5343
|
+
processedSize: 0,
|
|
5344
|
+
totalSize: 4097,
|
|
5584
5345
|
});
|
|
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
5346
|
|
|
5596
5347
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5597
5348
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5648,16 +5399,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5648
5399
|
method.postProgressMessage = jest.fn();
|
|
5649
5400
|
|
|
5650
5401
|
try {
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
});
|
|
5654
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
-
source,
|
|
5402
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5403
|
+
payload: new Uint8Array(8001).buffer,
|
|
5656
5404
|
filePath: 'vol0:/firmware.bin',
|
|
5657
5405
|
processedSize: 0,
|
|
5658
5406
|
totalSize: 8001,
|
|
5659
5407
|
});
|
|
5660
|
-
await source?.close();
|
|
5661
5408
|
} finally {
|
|
5662
5409
|
setTimeoutSpy.mockRestore();
|
|
5663
5410
|
}
|
|
@@ -5692,21 +5439,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5692
5439
|
});
|
|
5693
5440
|
method.postProgressMessage = jest.fn();
|
|
5694
5441
|
method.postTipMessage = jest.fn();
|
|
5695
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5696
5442
|
|
|
5697
|
-
const source = await openFirmwareByteSource({
|
|
5698
|
-
binary: new Uint8Array(4097).buffer,
|
|
5699
|
-
});
|
|
5700
5443
|
await expect(
|
|
5701
|
-
(method as any).
|
|
5702
|
-
|
|
5444
|
+
(method as any).protocolV2WriteWholeFile({
|
|
5445
|
+
payload: new Uint8Array(4097).buffer,
|
|
5703
5446
|
filePath: 'vol0:/firmware.bin',
|
|
5704
5447
|
processedSize: 0,
|
|
5705
5448
|
totalSize: 4097,
|
|
5706
5449
|
})
|
|
5707
5450
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5708
|
-
|
|
5709
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
5451
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5710
5452
|
});
|
|
5711
5453
|
|
|
5712
5454
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5739,19 +5481,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5739
5481
|
});
|
|
5740
5482
|
method.postProgressMessage = jest.fn();
|
|
5741
5483
|
|
|
5742
|
-
|
|
5743
|
-
|
|
5484
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5485
|
+
payload: new Uint8Array(1801).buffer,
|
|
5486
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
5487
|
+
processedSize: 0,
|
|
5488
|
+
totalSize: 1801,
|
|
5744
5489
|
});
|
|
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
5490
|
|
|
5756
5491
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5757
5492
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -5868,9 +5603,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
5603
|
const typedCall = jest
|
|
5869
5604
|
.fn()
|
|
5870
5605
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5606
|
+
new DOMException(
|
|
5607
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
5608
|
+
'NotFoundError'
|
|
5874
5609
|
)
|
|
5875
5610
|
);
|
|
5876
5611
|
|
|
@@ -6035,21 +5770,6 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
5770
|
});
|
|
6036
5771
|
|
|
6037
5772
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
-
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
6039
|
-
const method = new FirmwareUpdateV4({
|
|
6040
|
-
id: 1,
|
|
6041
|
-
payload: {
|
|
6042
|
-
method: 'firmwareUpdateV4',
|
|
6043
|
-
platform: 'native',
|
|
6044
|
-
expectedDeviceId: 'persisted-device-id',
|
|
6045
|
-
},
|
|
6046
|
-
});
|
|
6047
|
-
|
|
6048
|
-
method.init();
|
|
6049
|
-
|
|
6050
|
-
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
-
});
|
|
6052
|
-
|
|
6053
5773
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6054
5774
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
6055
5775
|
'identity mismatch'
|
|
@@ -6119,34 +5839,131 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6119
5839
|
});
|
|
6120
5840
|
});
|
|
6121
5841
|
|
|
6122
|
-
test('uses
|
|
5842
|
+
test('uses ResourceInventory instead of host-side FileRead for resource comparison', async () => {
|
|
6123
5843
|
const method = new FirmwareUpdateV4({
|
|
6124
5844
|
id: 1,
|
|
6125
5845
|
payload: {
|
|
6126
5846
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
5847
|
+
platform: 'web',
|
|
5848
|
+
targetsToUpdate: ['resource'],
|
|
6129
5849
|
},
|
|
6130
5850
|
});
|
|
6131
5851
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
5852
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5853
|
+
(type, index) => ({
|
|
5854
|
+
type,
|
|
5855
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5856
|
+
size: index + 1,
|
|
5857
|
+
fileHash: 'a'.repeat(64),
|
|
5858
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5859
|
+
})
|
|
5860
|
+
);
|
|
5861
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5862
|
+
message: {
|
|
5863
|
+
items: stable.map(item => ({
|
|
5864
|
+
type: item.type.toUpperCase(),
|
|
5865
|
+
size: item.size,
|
|
5866
|
+
header_hash: item.headerHash,
|
|
5867
|
+
})),
|
|
5868
|
+
},
|
|
6142
5869
|
});
|
|
6143
5870
|
(method as any).device = stubDevice({
|
|
6144
5871
|
getCommands: () => ({ typedCall }),
|
|
6145
5872
|
});
|
|
5873
|
+
const resourcesSpy = jest
|
|
5874
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5875
|
+
.mockReturnValue(stable as any);
|
|
5876
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
5877
|
+
|
|
5878
|
+
await expect((method as any).prepareProtocolV2ResourceBundles(false)).resolves.toEqual([]);
|
|
5879
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5880
|
+
'ResourceInventoryGet',
|
|
5881
|
+
'ResourceInventory',
|
|
5882
|
+
{},
|
|
5883
|
+
{ timeoutMs: 5000 }
|
|
5884
|
+
);
|
|
5885
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(false);
|
|
5886
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
5887
|
+
resourcesSpy.mockRestore();
|
|
5888
|
+
});
|
|
5889
|
+
|
|
5890
|
+
test('downloads only changed resources in Application mode', async () => {
|
|
5891
|
+
const method = new FirmwareUpdateV4({
|
|
5892
|
+
id: 1,
|
|
5893
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
5894
|
+
});
|
|
5895
|
+
method.init();
|
|
5896
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5897
|
+
(type, index) => ({
|
|
5898
|
+
type,
|
|
5899
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5900
|
+
size: index + 1,
|
|
5901
|
+
fileHash: 'a'.repeat(64),
|
|
5902
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5903
|
+
})
|
|
5904
|
+
);
|
|
5905
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5906
|
+
message: {
|
|
5907
|
+
items: stable.slice(1).map(item => ({
|
|
5908
|
+
type: item.type.toUpperCase(),
|
|
5909
|
+
size: item.size,
|
|
5910
|
+
header_hash: item.headerHash,
|
|
5911
|
+
})),
|
|
5912
|
+
},
|
|
5913
|
+
});
|
|
5914
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
5915
|
+
const resourcesSpy = jest
|
|
5916
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5917
|
+
.mockReturnValue(stable as any);
|
|
5918
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
5919
|
+
Promise.resolve({
|
|
5920
|
+
name: `${resource.type}.okpkg`,
|
|
5921
|
+
binary: new ArrayBuffer(resource.size),
|
|
5922
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
5923
|
+
})
|
|
5924
|
+
);
|
|
5925
|
+
|
|
5926
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles(false);
|
|
5927
|
+
|
|
5928
|
+
expect(bundles).toHaveLength(1);
|
|
5929
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
5930
|
+
resourcesSpy.mockRestore();
|
|
5931
|
+
});
|
|
5932
|
+
|
|
5933
|
+
test('downloads all six resources in Bootloader recovery without inventory RPC', async () => {
|
|
5934
|
+
const method = new FirmwareUpdateV4({
|
|
5935
|
+
id: 1,
|
|
5936
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
5937
|
+
});
|
|
5938
|
+
method.init();
|
|
5939
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5940
|
+
(type, index) => ({
|
|
5941
|
+
type,
|
|
5942
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5943
|
+
size: index + 1,
|
|
5944
|
+
fileHash: 'a'.repeat(64),
|
|
5945
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5946
|
+
})
|
|
5947
|
+
);
|
|
5948
|
+
const typedCall = jest.fn();
|
|
5949
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
5950
|
+
const resourcesSpy = jest
|
|
5951
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5952
|
+
.mockReturnValue(stable as any);
|
|
5953
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
5954
|
+
Promise.resolve({
|
|
5955
|
+
name: `${resource.type}.okpkg`,
|
|
5956
|
+
binary: new ArrayBuffer(resource.size),
|
|
5957
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
5958
|
+
})
|
|
5959
|
+
);
|
|
5960
|
+
|
|
5961
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles(true);
|
|
6146
5962
|
|
|
6147
|
-
|
|
6148
|
-
expect(
|
|
6149
|
-
expect(
|
|
5963
|
+
expect(bundles).toHaveLength(6);
|
|
5964
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
5965
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledTimes(6);
|
|
5966
|
+
resourcesSpy.mockRestore();
|
|
6150
5967
|
});
|
|
6151
5968
|
});
|
|
6152
5969
|
|
|
@@ -6286,311 +6103,230 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6286
6103
|
expect(method.unlockPolicy).toBe('none');
|
|
6287
6104
|
});
|
|
6288
6105
|
|
|
6289
|
-
test('
|
|
6106
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6290
6107
|
const calls: string[] = [];
|
|
6108
|
+
const features = { unlocked: true };
|
|
6291
6109
|
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
|
-
}),
|
|
6110
|
+
name: 'confluxSignMessageCIP23',
|
|
6111
|
+
unlockPolicy: 'none',
|
|
6112
|
+
useDevicePassphraseState: true,
|
|
6113
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6114
|
+
run: jest.fn(() => {
|
|
6115
|
+
calls.push('run');
|
|
6116
|
+
return Promise.resolve({ address: '0x1' });
|
|
6117
|
+
}),
|
|
6305
6118
|
};
|
|
6306
6119
|
const device = {
|
|
6120
|
+
features,
|
|
6121
|
+
commands: {
|
|
6122
|
+
typedCall: jest.fn(() => {
|
|
6123
|
+
calls.push('status');
|
|
6124
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6125
|
+
}),
|
|
6126
|
+
},
|
|
6307
6127
|
isProtocolV2: () => true,
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6128
|
+
isBootloader: () => false,
|
|
6129
|
+
isRomloader: () => false,
|
|
6130
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6131
|
+
features.unlocked = status.unlocked === true;
|
|
6132
|
+
return features;
|
|
6311
6133
|
}),
|
|
6134
|
+
unlockDevice: jest.fn(),
|
|
6312
6135
|
};
|
|
6313
6136
|
const uiCoordinator = {
|
|
6314
6137
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6138
|
+
enterUnlockInteraction: jest.fn(),
|
|
6317
6139
|
};
|
|
6318
6140
|
|
|
6319
6141
|
await expect(
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
]);
|
|
6142
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6143
|
+
uiCoordinator: uiCoordinator as any,
|
|
6144
|
+
})
|
|
6145
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6146
|
+
|
|
6147
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6148
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6149
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6150
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6330
6151
|
});
|
|
6331
6152
|
|
|
6332
|
-
test('
|
|
6153
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6333
6154
|
const calls: string[] = [];
|
|
6155
|
+
const identityError = new Error('Unexpected device');
|
|
6334
6156
|
const method = {
|
|
6335
6157
|
name: 'evmSignMessage',
|
|
6336
|
-
|
|
6158
|
+
unlockPolicy: 'none',
|
|
6337
6159
|
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
|
-
}),
|
|
6160
|
+
run: jest.fn(),
|
|
6349
6161
|
};
|
|
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
|
-
});
|
|
6162
|
+
const features = { unlocked: false };
|
|
6367
6163
|
const device = {
|
|
6368
|
-
features
|
|
6369
|
-
|
|
6370
|
-
|
|
6164
|
+
features,
|
|
6165
|
+
commands: {
|
|
6166
|
+
typedCall: jest.fn(() => {
|
|
6167
|
+
calls.push('status');
|
|
6168
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6169
|
+
}),
|
|
6170
|
+
},
|
|
6371
6171
|
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')),
|
|
6172
|
+
isBootloader: () => false,
|
|
6173
|
+
isRomloader: () => false,
|
|
6174
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6175
|
+
unlockDevice: jest.fn(),
|
|
6381
6176
|
};
|
|
6382
6177
|
|
|
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
|
-
};
|
|
6178
|
+
await expect(
|
|
6179
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6180
|
+
afterStatusBeforeUnlock: () => {
|
|
6181
|
+
calls.push('identity');
|
|
6182
|
+
throw identityError;
|
|
6183
|
+
},
|
|
6184
|
+
})
|
|
6185
|
+
).rejects.toBe(identityError);
|
|
6427
6186
|
|
|
6428
|
-
|
|
6429
|
-
expect(
|
|
6430
|
-
expect(
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6187
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6188
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6189
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6433
6190
|
});
|
|
6434
6191
|
|
|
6435
|
-
test('
|
|
6192
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6436
6193
|
const calls: string[] = [];
|
|
6194
|
+
const features = { unlocked: true };
|
|
6437
6195
|
const method = {
|
|
6438
6196
|
name: 'evmSignMessage',
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
unlockPolicy: 'retry-on-locked',
|
|
6197
|
+
unlockPolicy: 'unlock-before-run',
|
|
6198
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6442
6199
|
run: jest.fn(() => {
|
|
6443
6200
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6201
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6445
6202
|
}),
|
|
6446
6203
|
};
|
|
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
6204
|
const device = {
|
|
6470
6205
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6206
|
+
commands: {
|
|
6207
|
+
typedCall: jest.fn(() => {
|
|
6208
|
+
calls.push('status');
|
|
6209
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6210
|
+
}),
|
|
6211
|
+
},
|
|
6473
6212
|
isProtocolV2: () => true,
|
|
6213
|
+
isBootloader: () => false,
|
|
6214
|
+
isRomloader: () => false,
|
|
6215
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6216
|
+
features.unlocked = status.unlocked === true;
|
|
6217
|
+
return features;
|
|
6218
|
+
}),
|
|
6474
6219
|
unlockDevice: jest.fn(() => {
|
|
6475
|
-
calls.push('unlock
|
|
6220
|
+
calls.push('unlock');
|
|
6476
6221
|
features.unlocked = true;
|
|
6477
|
-
features
|
|
6478
|
-
|
|
6222
|
+
return Promise.resolve(features);
|
|
6223
|
+
}),
|
|
6224
|
+
};
|
|
6225
|
+
const uiCoordinator = {
|
|
6226
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6227
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
6228
|
+
calls.push('unlock-prompt');
|
|
6229
|
+
return undefined;
|
|
6479
6230
|
}),
|
|
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
6231
|
};
|
|
6486
6232
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6233
|
+
await expect(
|
|
6234
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6235
|
+
uiCoordinator: uiCoordinator as any,
|
|
6236
|
+
prepare: () => {
|
|
6237
|
+
calls.push('wallet-session');
|
|
6238
|
+
return Promise.resolve();
|
|
6239
|
+
},
|
|
6240
|
+
})
|
|
6241
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
6242
|
+
|
|
6491
6243
|
expect(calls).toEqual([
|
|
6492
|
-
'
|
|
6493
|
-
'
|
|
6494
|
-
'
|
|
6495
|
-
'
|
|
6244
|
+
'status',
|
|
6245
|
+
'unlock-prompt',
|
|
6246
|
+
'unlock',
|
|
6247
|
+
'wallet-session',
|
|
6248
|
+
'method-prompt',
|
|
6496
6249
|
'run',
|
|
6497
6250
|
]);
|
|
6251
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6252
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6253
|
});
|
|
6499
6254
|
|
|
6500
|
-
test('
|
|
6255
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6501
6256
|
const method = {
|
|
6502
6257
|
name: 'evmGetAddress',
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
unlockPolicy: 'retry-on-locked',
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6258
|
+
unlockPolicy: 'unlock-before-run',
|
|
6259
|
+
run: jest.fn(),
|
|
6507
6260
|
};
|
|
6508
6261
|
const device = {
|
|
6509
|
-
features: { unlocked:
|
|
6510
|
-
|
|
6262
|
+
features: { unlocked: undefined },
|
|
6263
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6511
6264
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6265
|
+
isBootloader: () => false,
|
|
6266
|
+
isRomloader: () => false,
|
|
6267
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6268
|
+
unlockDevice: jest.fn(),
|
|
6513
6269
|
};
|
|
6514
6270
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6271
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6272
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6517
6273
|
});
|
|
6518
|
-
expect(device.unlockDevice).
|
|
6519
|
-
expect(method.run).
|
|
6274
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6275
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6276
|
});
|
|
6521
6277
|
|
|
6522
|
-
test('
|
|
6523
|
-
const
|
|
6278
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6279
|
+
const error = deviceLockedError();
|
|
6524
6280
|
const method = {
|
|
6525
|
-
name: '
|
|
6281
|
+
name: 'evmSignMessage',
|
|
6526
6282
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
|
|
6528
|
-
run: jest.fn(() => {
|
|
6529
|
-
calls.push('run');
|
|
6530
|
-
return Promise.resolve({ message: 'ok' });
|
|
6531
|
-
}),
|
|
6283
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6532
6284
|
};
|
|
6533
6285
|
const features = { unlocked: true };
|
|
6534
6286
|
const device = {
|
|
6535
6287
|
features,
|
|
6536
|
-
commands: {
|
|
6537
|
-
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('refresh-status');
|
|
6539
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
-
}),
|
|
6541
|
-
},
|
|
6288
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6542
6289
|
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')),
|
|
6290
|
+
isBootloader: () => false,
|
|
6291
|
+
isRomloader: () => false,
|
|
6292
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6293
|
+
unlockDevice: jest.fn(),
|
|
6557
6294
|
};
|
|
6558
6295
|
|
|
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', {});
|
|
6296
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
6297
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6564
6298
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
6299
|
});
|
|
6568
6300
|
|
|
6569
|
-
test('
|
|
6570
|
-
const features = { unlocked:
|
|
6571
|
-
const
|
|
6572
|
-
|
|
6301
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
6302
|
+
const features = { unlocked: true };
|
|
6303
|
+
const context = createProtocolV2UnlockContext();
|
|
6304
|
+
const firstMethod = {
|
|
6305
|
+
name: 'allNetworkGetAddress',
|
|
6573
6306
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().mockResolvedValue({
|
|
6307
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
6308
|
+
};
|
|
6309
|
+
const nestedMethod = {
|
|
6310
|
+
name: 'evmGetAddress',
|
|
6311
|
+
unlockPolicy: 'unlock-before-run',
|
|
6312
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
6575
6313
|
};
|
|
6576
6314
|
const device = {
|
|
6577
6315
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
6316
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
6317
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
6318
|
+
isBootloader: () => false,
|
|
6319
|
+
isRomloader: () => false,
|
|
6320
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
6321
|
unlockDevice: jest.fn(),
|
|
6587
6322
|
};
|
|
6588
6323
|
|
|
6589
|
-
await
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
expect(device.
|
|
6593
|
-
expect(
|
|
6324
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
6325
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
6326
|
+
|
|
6327
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
6328
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
6329
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
6594
6330
|
});
|
|
6595
6331
|
|
|
6596
6332
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6610,7 +6346,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
6346
|
unlockDevice: jest.fn(),
|
|
6611
6347
|
};
|
|
6612
6348
|
|
|
6613
|
-
await expect(
|
|
6349
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
6350
|
message: 'updating',
|
|
6615
6351
|
});
|
|
6616
6352
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +6355,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
6355
|
}
|
|
6620
6356
|
);
|
|
6621
6357
|
|
|
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 () => {
|
|
6358
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
6359
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
6360
|
+
[false, 'unlock-before-run'],
|
|
6655
6361
|
[true, 'none'],
|
|
6656
6362
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
6363
|
const method = {
|
|
6659
6364
|
unlockPolicy,
|
|
6660
|
-
|
|
6365
|
+
useDevicePassphraseState: false,
|
|
6366
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
6367
|
};
|
|
6662
6368
|
const device = {
|
|
6369
|
+
commands: { typedCall: jest.fn() },
|
|
6663
6370
|
isProtocolV2: () => isProtocolV2,
|
|
6664
6371
|
unlockDevice: jest.fn(),
|
|
6665
6372
|
};
|
|
6666
6373
|
|
|
6667
|
-
await expect(
|
|
6374
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6375
|
+
message: 'ok',
|
|
6376
|
+
});
|
|
6377
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
6378
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
6379
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
6380
|
}
|
|
6671
6381
|
});
|
|
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
6382
|
});
|
|
6774
6383
|
|
|
6775
6384
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +6510,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
6510
|
|
|
6902
6511
|
await method.run();
|
|
6903
6512
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
6513
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
6514
|
+
eventless_wallet_session: true,
|
|
6515
|
+
});
|
|
6905
6516
|
});
|
|
6906
6517
|
|
|
6907
6518
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|