@onekeyfe/hd-core 1.2.0-alpha.41 → 1.2.0-alpha.42
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 +187 -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 +259 -18
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-resources.test.ts +204 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
- package/__tests__/protocol-v2.test.ts +526 -955
- 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 +30 -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 +22 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +27 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +110 -182
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1919 -3336
- 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 +3 -3
- 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 +29 -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 +166 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +118 -75
- package/src/data/messages/messages-protocol-v2.json +53 -0
- package/src/data-manager/DataManager.ts +63 -38
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +193 -21
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-request.ts +32 -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 +31 -5
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
- package/src/protocols/protocol-v2/walletSession.ts +93 -18
- 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 +91 -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', {
|
|
@@ -1553,6 +1558,70 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1553
1558
|
expect(device.getInternalState()).toBeUndefined();
|
|
1554
1559
|
});
|
|
1555
1560
|
|
|
1561
|
+
test.each([false, true])(
|
|
1562
|
+
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1563
|
+
async lockFails => {
|
|
1564
|
+
const deviceId = 'attach-mismatch-device';
|
|
1565
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1566
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1567
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1568
|
+
{
|
|
1569
|
+
status: {
|
|
1570
|
+
device_id: deviceId,
|
|
1571
|
+
unlocked: true,
|
|
1572
|
+
passphrase_enabled: true,
|
|
1573
|
+
attach_to_pin_enabled: true,
|
|
1574
|
+
unlocked_by_attach_to_pin: false,
|
|
1575
|
+
},
|
|
1576
|
+
}
|
|
1577
|
+
);
|
|
1578
|
+
device.passphraseState = 'expected-state';
|
|
1579
|
+
|
|
1580
|
+
const typedCall = jest.fn((request: string) => {
|
|
1581
|
+
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1582
|
+
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1583
|
+
if (request === 'DeviceStatusGet') {
|
|
1584
|
+
return {
|
|
1585
|
+
message: {
|
|
1586
|
+
device_id: deviceId,
|
|
1587
|
+
unlocked: true,
|
|
1588
|
+
passphrase_enabled: true,
|
|
1589
|
+
attach_to_pin_enabled: true,
|
|
1590
|
+
unlocked_by_attach_to_pin: true,
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
if (request === 'DeviceSessionGet') {
|
|
1595
|
+
return {
|
|
1596
|
+
message: {
|
|
1597
|
+
btc_test_address: 'wrong-attach-state',
|
|
1598
|
+
session_id: 'wrong-attach-session',
|
|
1599
|
+
},
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
if (request === 'LockDevice') {
|
|
1603
|
+
if (lockFails) throw new Error('Lock unsupported');
|
|
1604
|
+
return { message: { message: 'Device locked' } };
|
|
1605
|
+
}
|
|
1606
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1607
|
+
});
|
|
1608
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1609
|
+
(device as any).commands = { typedCall, promptPassphrase };
|
|
1610
|
+
|
|
1611
|
+
await expect(
|
|
1612
|
+
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1613
|
+
).rejects.toMatchObject({
|
|
1614
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1618
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1619
|
+
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1620
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1621
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
1622
|
+
}
|
|
1623
|
+
);
|
|
1624
|
+
|
|
1556
1625
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1557
1626
|
const device = Device.fromDescriptor({
|
|
1558
1627
|
id: 'cache-device-attach-recovery',
|
|
@@ -1609,7 +1678,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1609
1678
|
}),
|
|
1610
1679
|
{ cancelDeviceOnReject: false }
|
|
1611
1680
|
);
|
|
1612
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1681
|
+
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1682
|
+
DeviceSessionPinType.AttachToPin,
|
|
1683
|
+
expect.objectContaining({
|
|
1684
|
+
emitUiEvent: false,
|
|
1685
|
+
interaction: expect.objectContaining({
|
|
1686
|
+
phase: 'pin',
|
|
1687
|
+
transition: 'start',
|
|
1688
|
+
protocol: 'V2',
|
|
1689
|
+
}),
|
|
1690
|
+
})
|
|
1691
|
+
);
|
|
1613
1692
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1614
1693
|
});
|
|
1615
1694
|
|
|
@@ -1893,12 +1972,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1893
1972
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1894
1973
|
});
|
|
1895
1974
|
|
|
1896
|
-
test('
|
|
1975
|
+
test('uses the cached wallet address when the local session is missing', async () => {
|
|
1897
1976
|
const typedCall = jest
|
|
1898
1977
|
.fn()
|
|
1899
1978
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
-
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
-
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1902
1979
|
.mockResolvedValueOnce({
|
|
1903
1980
|
message: {
|
|
1904
1981
|
btc_test_address: 'expected-state',
|
|
@@ -1926,16 +2003,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1926
2003
|
await expect(
|
|
1927
2004
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1928
2005
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
1929
|
-
expect(
|
|
1930
|
-
|
|
1931
|
-
existsAttachPinUser: false,
|
|
1932
|
-
deviceOnly: false,
|
|
1933
|
-
source: 'wallet-session-coordinator',
|
|
1934
|
-
reason: 'session-recovery',
|
|
1935
|
-
expectedPassphraseState: 'expected-state',
|
|
1936
|
-
},
|
|
1937
|
-
{ cancelDeviceOnReject: false }
|
|
1938
|
-
);
|
|
2006
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2007
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1939
2008
|
});
|
|
1940
2009
|
|
|
1941
2010
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2001,7 +2070,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2001
2070
|
true
|
|
2002
2071
|
);
|
|
2003
2072
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
-
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
2005
2073
|
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
2006
2074
|
]);
|
|
2007
2075
|
});
|
|
@@ -2269,12 +2337,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2269
2337
|
);
|
|
2270
2338
|
|
|
2271
2339
|
expect(device.getInternalState()).toBeUndefined();
|
|
2272
|
-
expect(typedCall).
|
|
2340
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2341
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2273
2342
|
passphrase: 'host hidden wallet',
|
|
2274
2343
|
on_device: false,
|
|
2275
2344
|
});
|
|
2276
|
-
expect(typedCall).
|
|
2277
|
-
expect(typedCall).
|
|
2345
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2346
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2278
2347
|
});
|
|
2279
2348
|
|
|
2280
2349
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2311,6 +2380,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2311
2380
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2312
2381
|
eventless_wallet_session: true,
|
|
2313
2382
|
});
|
|
2383
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
2384
|
+
type: DeviceSessionPinType.Main,
|
|
2385
|
+
});
|
|
2314
2386
|
});
|
|
2315
2387
|
|
|
2316
2388
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2681,7 +2753,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2681
2753
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2682
2754
|
}
|
|
2683
2755
|
);
|
|
2684
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2756
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2757
|
+
eventless_wallet_session: true,
|
|
2758
|
+
});
|
|
2685
2759
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2686
2760
|
});
|
|
2687
2761
|
|
|
@@ -2719,7 +2793,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2719
2793
|
}),
|
|
2720
2794
|
expect.anything()
|
|
2721
2795
|
);
|
|
2722
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2796
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2797
|
+
eventless_wallet_session: true,
|
|
2798
|
+
});
|
|
2723
2799
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2724
2800
|
expect(device.features).toMatchObject({
|
|
2725
2801
|
deviceId: null,
|
|
@@ -2985,10 +3061,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2985
3061
|
fw: { application: { version: '1.2.4' } },
|
|
2986
3062
|
},
|
|
2987
3063
|
})
|
|
2988
|
-
.mockResolvedValueOnce({
|
|
2989
|
-
type: 'ProtocolInfo',
|
|
2990
|
-
message: protocolV2ApplicationInfo,
|
|
2991
|
-
})
|
|
2992
3064
|
.mockResolvedValueOnce({
|
|
2993
3065
|
type: 'DeviceStatus',
|
|
2994
3066
|
message: { init_states: true, unlocked: true },
|
|
@@ -3010,7 +3082,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3010
3082
|
firmwareVersion: '1.2.4',
|
|
3011
3083
|
passphraseProtection: null,
|
|
3012
3084
|
});
|
|
3013
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3085
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
3014
3086
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3015
3087
|
4,
|
|
3016
3088
|
'DeviceInfoGet',
|
|
@@ -3124,9 +3196,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3124
3196
|
status: { init_states: true },
|
|
3125
3197
|
}
|
|
3126
3198
|
);
|
|
3199
|
+
const pinOnDevice = jest.fn();
|
|
3200
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3127
3201
|
|
|
3128
3202
|
const features = await device.unlockDevice();
|
|
3129
3203
|
|
|
3204
|
+
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3205
|
+
device,
|
|
3206
|
+
DeviceSessionPinType.Main,
|
|
3207
|
+
expect.objectContaining({
|
|
3208
|
+
source: 'unlock-coordinator',
|
|
3209
|
+
reason: 'device-unlock',
|
|
3210
|
+
deviceOnly: true,
|
|
3211
|
+
})
|
|
3212
|
+
);
|
|
3213
|
+
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3214
|
+
typedCall.mock.invocationCallOrder[0]
|
|
3215
|
+
);
|
|
3130
3216
|
expect(typedCall.mock.calls).toEqual([
|
|
3131
3217
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3132
3218
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3172,9 +3258,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3172
3258
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3173
3259
|
});
|
|
3174
3260
|
(device as any).commands = { typedCall };
|
|
3261
|
+
const pinOnDevice = jest.fn();
|
|
3262
|
+
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3175
3263
|
|
|
3176
|
-
await device.unlockDevice();
|
|
3264
|
+
await device.unlockDevice(DeviceSessionPinType.Main, { emitUiEvent: false });
|
|
3177
3265
|
|
|
3266
|
+
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3178
3267
|
expect(typedCall.mock.calls).toEqual([
|
|
3179
3268
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3180
3269
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3577,6 +3666,15 @@ describe('API compatibility handling', () => {
|
|
|
3577
3666
|
|
|
3578
3667
|
describe('Protocol V2 firmware update targets', () => {
|
|
3579
3668
|
const OKPP_HEADER_SIZE = 0x52a0;
|
|
3669
|
+
let forceReloadDataSpy: jest.SpyInstance<Promise<void>, []>;
|
|
3670
|
+
|
|
3671
|
+
beforeEach(() => {
|
|
3672
|
+
forceReloadDataSpy = jest.spyOn(DataManager, 'forceReloadData').mockResolvedValue(undefined);
|
|
3673
|
+
});
|
|
3674
|
+
|
|
3675
|
+
afterEach(() => {
|
|
3676
|
+
jest.restoreAllMocks();
|
|
3677
|
+
});
|
|
3580
3678
|
|
|
3581
3679
|
const buildOkppHeader = ({
|
|
3582
3680
|
type = 'RESC',
|
|
@@ -3717,7 +3815,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3717
3815
|
});
|
|
3718
3816
|
});
|
|
3719
3817
|
|
|
3720
|
-
test('
|
|
3818
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
3721
3819
|
const method = new FirmwareUpdateV4({
|
|
3722
3820
|
id: 1,
|
|
3723
3821
|
payload: {
|
|
@@ -3752,7 +3850,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3752
3850
|
|
|
3753
3851
|
await method.run();
|
|
3754
3852
|
|
|
3755
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3853
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
3756
3854
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3757
3855
|
fwBinaryMap: [
|
|
3758
3856
|
{
|
|
@@ -3866,6 +3964,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3866
3964
|
},
|
|
3867
3965
|
});
|
|
3868
3966
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3967
|
+
const prepareResourceBundles = jest
|
|
3968
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
3969
|
+
.mockResolvedValue(undefined);
|
|
3869
3970
|
(method as any).protocolV2Reboot = jest.fn();
|
|
3870
3971
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3871
3972
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3878,6 +3979,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3878
3979
|
|
|
3879
3980
|
await method.run();
|
|
3880
3981
|
|
|
3982
|
+
expect(prepareResourceBundles).toHaveBeenCalledWith(true);
|
|
3881
3983
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
3882
3984
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3883
3985
|
bootloaderBinary: null,
|
|
@@ -4135,9 +4237,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4237
|
const typedCall = jest
|
|
4136
4238
|
.fn()
|
|
4137
4239
|
.mockRejectedValue(
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4240
|
+
new DOMException(
|
|
4241
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4242
|
+
'NotFoundError'
|
|
4141
4243
|
)
|
|
4142
4244
|
);
|
|
4143
4245
|
|
|
@@ -4318,77 +4420,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4318
4420
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4319
4421
|
});
|
|
4320
4422
|
|
|
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
4423
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4393
4424
|
const method = new FirmwareUpdateV4({
|
|
4394
4425
|
id: 1,
|
|
@@ -4465,7 +4496,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4465
4496
|
).toBe(true);
|
|
4466
4497
|
|
|
4467
4498
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4468
|
-
expect(
|
|
4499
|
+
expect(
|
|
4469
4500
|
(method as any).assertProtocolV2TargetStatus(
|
|
4470
4501
|
[
|
|
4471
4502
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4473,8 +4504,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4473
4504
|
],
|
|
4474
4505
|
new Set([4, 10])
|
|
4475
4506
|
)
|
|
4476
|
-
).
|
|
4477
|
-
expect(method.postProgressMessage).
|
|
4507
|
+
).toBe(false);
|
|
4508
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
4478
4509
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4479
4510
|
|
|
4480
4511
|
expect(
|
|
@@ -4578,14 +4609,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4578
4609
|
const writtenPaths: string[] = [];
|
|
4579
4610
|
method.postTipMessage = jest.fn();
|
|
4580
4611
|
method.postProgressMessage = jest.fn();
|
|
4581
|
-
(method as any).
|
|
4612
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4582
4613
|
writtenPaths.push(params.filePath);
|
|
4583
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4614
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
4584
4615
|
});
|
|
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
4616
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4590
4617
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4591
4618
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4625,12 +4652,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4625
4652
|
'vol0:/coprocessor.bin',
|
|
4626
4653
|
1
|
|
4627
4654
|
);
|
|
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, {
|
|
4655
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
4656
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
4633
4657
|
targets: [
|
|
4658
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4634
4659
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4635
4660
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4636
4661
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4640,7 +4665,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4640
4665
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4641
4666
|
});
|
|
4642
4667
|
|
|
4643
|
-
test('announces
|
|
4668
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
4644
4669
|
const method = new FirmwareUpdateV4({
|
|
4645
4670
|
id: 1,
|
|
4646
4671
|
payload: {
|
|
@@ -4650,14 +4675,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4650
4675
|
|
|
4651
4676
|
method.postTipMessage = jest.fn();
|
|
4652
4677
|
method.postProgressMessage = jest.fn();
|
|
4653
|
-
(method as any).
|
|
4678
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4654
4679
|
.fn()
|
|
4655
4680
|
.mockImplementation(params =>
|
|
4656
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4681
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4657
4682
|
);
|
|
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
4683
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4662
4684
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4663
4685
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4677,22 +4699,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4677
4699
|
{
|
|
4678
4700
|
fileName: 'application_p1.bin',
|
|
4679
4701
|
binary: new Uint8Array([3]).buffer,
|
|
4680
|
-
targetId:
|
|
4702
|
+
targetId: 3,
|
|
4681
4703
|
},
|
|
4682
4704
|
],
|
|
4683
4705
|
});
|
|
4684
4706
|
|
|
4685
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4707
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
4686
4708
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4687
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4688
|
-
expect(method.
|
|
4689
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
4709
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
4710
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4690
4711
|
1,
|
|
4691
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4712
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
4692
4713
|
);
|
|
4693
|
-
expect((method as any).
|
|
4714
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
4694
4715
|
2,
|
|
4695
|
-
expect.objectContaining({ processedSize:
|
|
4716
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
4696
4717
|
);
|
|
4697
4718
|
});
|
|
4698
4719
|
|
|
@@ -4706,8 +4727,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4706
4727
|
|
|
4707
4728
|
method.postTipMessage = jest.fn();
|
|
4708
4729
|
method.postProgressMessage = jest.fn();
|
|
4709
|
-
(method as any).
|
|
4710
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4730
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4711
4731
|
(method as any).device = stubDevice({
|
|
4712
4732
|
getCommands: () => ({
|
|
4713
4733
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4753,14 +4773,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4753
4773
|
|
|
4754
4774
|
method.postTipMessage = jest.fn();
|
|
4755
4775
|
method.postProgressMessage = jest.fn();
|
|
4756
|
-
(method as any).
|
|
4776
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
4757
4777
|
.fn()
|
|
4758
4778
|
.mockImplementation(params =>
|
|
4759
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4779
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
4760
4780
|
);
|
|
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
4781
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4765
4782
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4766
4783
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4975,33 +4992,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4975
4992
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4976
4993
|
});
|
|
4977
4994
|
|
|
4978
|
-
test('
|
|
4995
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
4979
4996
|
const method = new FirmwareUpdateV4({
|
|
4980
4997
|
id: 1,
|
|
4981
4998
|
payload: {
|
|
4982
4999
|
method: 'firmwareUpdateV4',
|
|
4983
5000
|
platform: 'web',
|
|
4984
|
-
targetsToUpdate: ['resource'],
|
|
4985
5001
|
},
|
|
4986
5002
|
});
|
|
4987
5003
|
method.init();
|
|
4988
5004
|
|
|
5005
|
+
const getSysResourceBinarySpy = jest
|
|
5006
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5007
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
4989
5008
|
const getFirmwareLatestReleaseSpy = jest
|
|
4990
5009
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4991
5010
|
.mockReturnValue({
|
|
4992
5011
|
required: false,
|
|
4993
5012
|
version: [1, 0, 0],
|
|
4994
|
-
url: '',
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
headerHash: 'cd'.repeat(64),
|
|
5013
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5014
|
+
bootloaderVersion: [1, 0, 0],
|
|
5015
|
+
upgradeType: 'payload-package-set',
|
|
5016
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5017
|
+
components: {
|
|
5018
|
+
bootloader: {
|
|
5019
|
+
target: 'BOOTLOADER',
|
|
5020
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
5003
5021
|
},
|
|
5004
|
-
|
|
5022
|
+
applicationP1: {
|
|
5023
|
+
target: 'APPLICATION_P1',
|
|
5024
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
5025
|
+
},
|
|
5026
|
+
applicationP2: {
|
|
5027
|
+
target: 'APPLICATION_P2',
|
|
5028
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
5029
|
+
},
|
|
5030
|
+
},
|
|
5005
5031
|
fingerprint: '',
|
|
5006
5032
|
changelog: {
|
|
5007
5033
|
'zh-CN': '',
|
|
@@ -5009,399 +5035,94 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5009
5035
|
},
|
|
5010
5036
|
});
|
|
5011
5037
|
|
|
5012
|
-
const
|
|
5038
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5013
5039
|
deviceType: 'pro2',
|
|
5014
5040
|
firmwareVersion: '1.0.0',
|
|
5041
|
+
bootloaderVersion: '1.0.0',
|
|
5015
5042
|
capabilities: [],
|
|
5016
5043
|
});
|
|
5017
5044
|
|
|
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);
|
|
5045
|
+
expect(remoteBinaries).toEqual({
|
|
5046
|
+
bootloaderBinary: null,
|
|
5047
|
+
fwBinaryMap: [],
|
|
5048
|
+
installItems: [],
|
|
5049
|
+
});
|
|
5050
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5030
5051
|
|
|
5052
|
+
getSysResourceBinarySpy.mockRestore();
|
|
5031
5053
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5032
5054
|
});
|
|
5033
5055
|
|
|
5034
|
-
test('
|
|
5056
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5035
5057
|
const method = new FirmwareUpdateV4({
|
|
5036
5058
|
id: 1,
|
|
5037
5059
|
payload: {
|
|
5038
5060
|
method: 'firmwareUpdateV4',
|
|
5039
5061
|
platform: 'web',
|
|
5040
|
-
targetsToUpdate: ['resource'],
|
|
5041
5062
|
},
|
|
5042
5063
|
});
|
|
5043
5064
|
method.init();
|
|
5044
5065
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5045
5066
|
|
|
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': '',
|
|
5067
|
+
const remoteBinaries = {
|
|
5068
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5069
|
+
fwBinaryMap: [
|
|
5070
|
+
{
|
|
5071
|
+
fileName: 'application_p1.bin',
|
|
5072
|
+
binary: new Uint8Array([2]).buffer,
|
|
5073
|
+
targetId: 3,
|
|
5064
5074
|
},
|
|
5065
|
-
|
|
5075
|
+
],
|
|
5076
|
+
};
|
|
5066
5077
|
|
|
5067
5078
|
(method as any).device = stubDevice({
|
|
5068
5079
|
originalDescriptor: { protocolType: 'V2' },
|
|
5069
|
-
features: { deviceType: 'pro2', firmwareVersion: '
|
|
5080
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5070
5081
|
});
|
|
5071
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(
|
|
5072
|
-
|
|
5073
|
-
|
|
5082
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5083
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5084
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5085
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5086
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5087
|
+
bootloaderVersion: '1.0.0',
|
|
5088
|
+
bleVersion: '0.0.0',
|
|
5089
|
+
firmwareVersion: '1.0.0',
|
|
5074
5090
|
});
|
|
5075
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5076
5091
|
method.postTipMessage = jest.fn();
|
|
5077
5092
|
|
|
5078
|
-
await
|
|
5079
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5080
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5093
|
+
await method.run();
|
|
5081
5094
|
|
|
5082
|
-
|
|
5083
|
-
|
|
5095
|
+
expect(forceReloadDataSpy).toHaveBeenCalledTimes(1);
|
|
5096
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5097
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5098
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5099
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5100
|
+
});
|
|
5084
5101
|
});
|
|
5085
5102
|
|
|
5086
|
-
test('
|
|
5103
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
5087
5104
|
const method = new FirmwareUpdateV4({
|
|
5088
5105
|
id: 1,
|
|
5089
5106
|
payload: {
|
|
5090
5107
|
method: 'firmwareUpdateV4',
|
|
5108
|
+
platform: 'web',
|
|
5091
5109
|
},
|
|
5092
5110
|
});
|
|
5093
|
-
|
|
5094
|
-
(method as any).
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
},
|
|
5104
|
-
},
|
|
5105
|
-
],
|
|
5106
|
-
};
|
|
5107
|
-
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
-
.mockReturnValue({
|
|
5111
|
-
required: false,
|
|
5112
|
-
version: [1, 0, 0],
|
|
5113
|
-
url: '',
|
|
5114
|
-
resourceBundles: [
|
|
5115
|
-
{
|
|
5116
|
-
name: 'images',
|
|
5117
|
-
url: 'https://example.com/images.okpkg',
|
|
5118
|
-
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
-
},
|
|
5120
|
-
],
|
|
5121
|
-
fingerprint: '',
|
|
5122
|
-
changelog: {
|
|
5123
|
-
'zh-CN': '',
|
|
5124
|
-
'en-US': '',
|
|
5125
|
-
},
|
|
5126
|
-
});
|
|
5111
|
+
method.init();
|
|
5112
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5113
|
+
(method as any).device = stubDevice({
|
|
5114
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
5115
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5116
|
+
});
|
|
5117
|
+
forceReloadDataSpy.mockRejectedValue(new Error('latest config unavailable'));
|
|
5118
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
5119
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
5120
|
+
method.postTipMessage = jest.fn();
|
|
5127
5121
|
|
|
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();
|
|
5122
|
+
await expect(method.run()).rejects.toThrow('latest config unavailable');
|
|
5292
5123
|
|
|
5293
|
-
|
|
5124
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
5294
5125
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
5295
|
-
|
|
5296
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5297
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5298
|
-
});
|
|
5299
|
-
|
|
5300
|
-
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
5301
|
-
const method = new FirmwareUpdateV4({
|
|
5302
|
-
id: 1,
|
|
5303
|
-
payload: {
|
|
5304
|
-
method: 'firmwareUpdateV4',
|
|
5305
|
-
platform: 'web',
|
|
5306
|
-
},
|
|
5307
|
-
});
|
|
5308
|
-
method.init();
|
|
5309
|
-
|
|
5310
|
-
const getSysResourceBinarySpy = jest
|
|
5311
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5312
|
-
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
5313
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5314
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5315
|
-
.mockReturnValue({
|
|
5316
|
-
required: false,
|
|
5317
|
-
version: [1, 0, 0],
|
|
5318
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5319
|
-
bootloaderVersion: [1, 0, 0],
|
|
5320
|
-
upgradeType: 'payload-package-set',
|
|
5321
|
-
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
5322
|
-
components: {
|
|
5323
|
-
bootloader: {
|
|
5324
|
-
target: 'BOOTLOADER',
|
|
5325
|
-
url: 'https://example.com/bootloader.pp.bin',
|
|
5326
|
-
},
|
|
5327
|
-
applicationP1: {
|
|
5328
|
-
target: 'APPLICATION_P1',
|
|
5329
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5330
|
-
},
|
|
5331
|
-
applicationP2: {
|
|
5332
|
-
target: 'APPLICATION_P2',
|
|
5333
|
-
url: 'https://example.com/applicationP2.pp.bin',
|
|
5334
|
-
},
|
|
5335
|
-
},
|
|
5336
|
-
fingerprint: '',
|
|
5337
|
-
changelog: {
|
|
5338
|
-
'zh-CN': '',
|
|
5339
|
-
'en-US': '',
|
|
5340
|
-
},
|
|
5341
|
-
});
|
|
5342
|
-
|
|
5343
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
5344
|
-
deviceType: 'pro2',
|
|
5345
|
-
firmwareVersion: '1.0.0',
|
|
5346
|
-
bootloaderVersion: '1.0.0',
|
|
5347
|
-
capabilities: [],
|
|
5348
|
-
});
|
|
5349
|
-
|
|
5350
|
-
expect(remoteBinaries).toEqual({
|
|
5351
|
-
bootloaderBinary: null,
|
|
5352
|
-
fwBinaryMap: [],
|
|
5353
|
-
installItems: [],
|
|
5354
|
-
});
|
|
5355
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
5356
|
-
|
|
5357
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5358
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5359
|
-
});
|
|
5360
|
-
|
|
5361
|
-
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
5362
|
-
const method = new FirmwareUpdateV4({
|
|
5363
|
-
id: 1,
|
|
5364
|
-
payload: {
|
|
5365
|
-
method: 'firmwareUpdateV4',
|
|
5366
|
-
platform: 'web',
|
|
5367
|
-
},
|
|
5368
|
-
});
|
|
5369
|
-
method.init();
|
|
5370
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
5371
|
-
|
|
5372
|
-
const remoteBinaries = {
|
|
5373
|
-
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
5374
|
-
fwBinaryMap: [
|
|
5375
|
-
{
|
|
5376
|
-
fileName: 'application_p1.bin',
|
|
5377
|
-
binary: new Uint8Array([2]).buffer,
|
|
5378
|
-
targetId: 3,
|
|
5379
|
-
},
|
|
5380
|
-
],
|
|
5381
|
-
};
|
|
5382
|
-
|
|
5383
|
-
(method as any).device = stubDevice({
|
|
5384
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
5385
|
-
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
5386
|
-
});
|
|
5387
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
5388
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
5389
|
-
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
5390
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5391
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5392
|
-
bootloaderVersion: '1.0.0',
|
|
5393
|
-
bleVersion: '0.0.0',
|
|
5394
|
-
firmwareVersion: '1.0.0',
|
|
5395
|
-
});
|
|
5396
|
-
method.postTipMessage = jest.fn();
|
|
5397
|
-
|
|
5398
|
-
await method.run();
|
|
5399
|
-
|
|
5400
|
-
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
5401
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
5402
|
-
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
5403
|
-
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
5404
|
-
});
|
|
5405
5126
|
});
|
|
5406
5127
|
|
|
5407
5128
|
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
@@ -5529,10 +5250,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5529
5250
|
|
|
5530
5251
|
method.postTipMessage = jest.fn();
|
|
5531
5252
|
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);
|
|
5253
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5536
5254
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5537
5255
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5538
5256
|
|
|
@@ -5548,7 +5266,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5548
5266
|
fwBinaryMap: [],
|
|
5549
5267
|
});
|
|
5550
5268
|
|
|
5551
|
-
expect((method as any).
|
|
5269
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5552
5270
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5553
5271
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5554
5272
|
});
|
|
@@ -5579,19 +5297,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5579
5297
|
});
|
|
5580
5298
|
method.postProgressMessage = jest.fn();
|
|
5581
5299
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5300
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5301
|
+
payload: new Uint8Array(4097).buffer,
|
|
5302
|
+
filePath: 'vol1:firmware.bin',
|
|
5303
|
+
processedSize: 0,
|
|
5304
|
+
totalSize: 4097,
|
|
5584
5305
|
});
|
|
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
5306
|
|
|
5596
5307
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5597
5308
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5648,16 +5359,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5648
5359
|
method.postProgressMessage = jest.fn();
|
|
5649
5360
|
|
|
5650
5361
|
try {
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
});
|
|
5654
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
-
source,
|
|
5362
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5363
|
+
payload: new Uint8Array(8001).buffer,
|
|
5656
5364
|
filePath: 'vol0:/firmware.bin',
|
|
5657
5365
|
processedSize: 0,
|
|
5658
5366
|
totalSize: 8001,
|
|
5659
5367
|
});
|
|
5660
|
-
await source?.close();
|
|
5661
5368
|
} finally {
|
|
5662
5369
|
setTimeoutSpy.mockRestore();
|
|
5663
5370
|
}
|
|
@@ -5692,21 +5399,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5692
5399
|
});
|
|
5693
5400
|
method.postProgressMessage = jest.fn();
|
|
5694
5401
|
method.postTipMessage = jest.fn();
|
|
5695
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5696
5402
|
|
|
5697
|
-
const source = await openFirmwareByteSource({
|
|
5698
|
-
binary: new Uint8Array(4097).buffer,
|
|
5699
|
-
});
|
|
5700
5403
|
await expect(
|
|
5701
|
-
(method as any).
|
|
5702
|
-
|
|
5404
|
+
(method as any).protocolV2WriteWholeFile({
|
|
5405
|
+
payload: new Uint8Array(4097).buffer,
|
|
5703
5406
|
filePath: 'vol0:/firmware.bin',
|
|
5704
5407
|
processedSize: 0,
|
|
5705
5408
|
totalSize: 4097,
|
|
5706
5409
|
})
|
|
5707
5410
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5708
|
-
|
|
5709
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
5411
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
5710
5412
|
});
|
|
5711
5413
|
|
|
5712
5414
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5739,19 +5441,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5739
5441
|
});
|
|
5740
5442
|
method.postProgressMessage = jest.fn();
|
|
5741
5443
|
|
|
5742
|
-
|
|
5743
|
-
|
|
5444
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
5445
|
+
payload: new Uint8Array(1801).buffer,
|
|
5446
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
5447
|
+
processedSize: 0,
|
|
5448
|
+
totalSize: 1801,
|
|
5744
5449
|
});
|
|
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
5450
|
|
|
5756
5451
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5757
5452
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -5868,9 +5563,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5868
5563
|
const typedCall = jest
|
|
5869
5564
|
.fn()
|
|
5870
5565
|
.mockRejectedValue(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5566
|
+
new DOMException(
|
|
5567
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
5568
|
+
'NotFoundError'
|
|
5874
5569
|
)
|
|
5875
5570
|
);
|
|
5876
5571
|
|
|
@@ -6035,21 +5730,6 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
6035
5730
|
});
|
|
6036
5731
|
|
|
6037
5732
|
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
5733
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
6054
5734
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
6055
5735
|
'identity mismatch'
|
|
@@ -6119,34 +5799,131 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
6119
5799
|
});
|
|
6120
5800
|
});
|
|
6121
5801
|
|
|
6122
|
-
test('uses
|
|
5802
|
+
test('uses ResourceInventory instead of host-side FileRead for resource comparison', async () => {
|
|
6123
5803
|
const method = new FirmwareUpdateV4({
|
|
6124
5804
|
id: 1,
|
|
6125
5805
|
payload: {
|
|
6126
5806
|
method: 'firmwareUpdateV4',
|
|
6127
|
-
platform: '
|
|
6128
|
-
|
|
5807
|
+
platform: 'web',
|
|
5808
|
+
targetsToUpdate: ['resource'],
|
|
6129
5809
|
},
|
|
6130
5810
|
});
|
|
6131
5811
|
method.init();
|
|
6132
|
-
const
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
5812
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5813
|
+
(type, index) => ({
|
|
5814
|
+
type,
|
|
5815
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5816
|
+
size: index + 1,
|
|
5817
|
+
fileHash: 'a'.repeat(64),
|
|
5818
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5819
|
+
})
|
|
5820
|
+
);
|
|
5821
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5822
|
+
message: {
|
|
5823
|
+
items: stable.map(item => ({
|
|
5824
|
+
type: item.type.toUpperCase(),
|
|
5825
|
+
size: item.size,
|
|
5826
|
+
header_hash: item.headerHash,
|
|
5827
|
+
})),
|
|
5828
|
+
},
|
|
6142
5829
|
});
|
|
6143
5830
|
(method as any).device = stubDevice({
|
|
6144
5831
|
getCommands: () => ({ typedCall }),
|
|
6145
5832
|
});
|
|
5833
|
+
const resourcesSpy = jest
|
|
5834
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5835
|
+
.mockReturnValue(stable as any);
|
|
5836
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
5837
|
+
|
|
5838
|
+
await expect((method as any).prepareProtocolV2ResourceBundles(false)).resolves.toEqual([]);
|
|
5839
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5840
|
+
'ResourceInventoryGet',
|
|
5841
|
+
'ResourceInventory',
|
|
5842
|
+
{},
|
|
5843
|
+
{ timeoutMs: 5000 }
|
|
5844
|
+
);
|
|
5845
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(false);
|
|
5846
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
5847
|
+
resourcesSpy.mockRestore();
|
|
5848
|
+
});
|
|
5849
|
+
|
|
5850
|
+
test('downloads only changed resources in Application mode', async () => {
|
|
5851
|
+
const method = new FirmwareUpdateV4({
|
|
5852
|
+
id: 1,
|
|
5853
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
5854
|
+
});
|
|
5855
|
+
method.init();
|
|
5856
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5857
|
+
(type, index) => ({
|
|
5858
|
+
type,
|
|
5859
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5860
|
+
size: index + 1,
|
|
5861
|
+
fileHash: 'a'.repeat(64),
|
|
5862
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5863
|
+
})
|
|
5864
|
+
);
|
|
5865
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5866
|
+
message: {
|
|
5867
|
+
items: stable.slice(1).map(item => ({
|
|
5868
|
+
type: item.type.toUpperCase(),
|
|
5869
|
+
size: item.size,
|
|
5870
|
+
header_hash: item.headerHash,
|
|
5871
|
+
})),
|
|
5872
|
+
},
|
|
5873
|
+
});
|
|
5874
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
5875
|
+
const resourcesSpy = jest
|
|
5876
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5877
|
+
.mockReturnValue(stable as any);
|
|
5878
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
5879
|
+
Promise.resolve({
|
|
5880
|
+
name: `${resource.type}.okpkg`,
|
|
5881
|
+
binary: new ArrayBuffer(resource.size),
|
|
5882
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
5883
|
+
})
|
|
5884
|
+
);
|
|
5885
|
+
|
|
5886
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles(false);
|
|
5887
|
+
|
|
5888
|
+
expect(bundles).toHaveLength(1);
|
|
5889
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
5890
|
+
resourcesSpy.mockRestore();
|
|
5891
|
+
});
|
|
5892
|
+
|
|
5893
|
+
test('downloads all six resources in Bootloader recovery without inventory RPC', async () => {
|
|
5894
|
+
const method = new FirmwareUpdateV4({
|
|
5895
|
+
id: 1,
|
|
5896
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
5897
|
+
});
|
|
5898
|
+
method.init();
|
|
5899
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
5900
|
+
(type, index) => ({
|
|
5901
|
+
type,
|
|
5902
|
+
url: `https://example.com/${type}.okpkg`,
|
|
5903
|
+
size: index + 1,
|
|
5904
|
+
fileHash: 'a'.repeat(64),
|
|
5905
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
5906
|
+
})
|
|
5907
|
+
);
|
|
5908
|
+
const typedCall = jest.fn();
|
|
5909
|
+
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
5910
|
+
const resourcesSpy = jest
|
|
5911
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
5912
|
+
.mockReturnValue(stable as any);
|
|
5913
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
5914
|
+
Promise.resolve({
|
|
5915
|
+
name: `${resource.type}.okpkg`,
|
|
5916
|
+
binary: new ArrayBuffer(resource.size),
|
|
5917
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
5918
|
+
})
|
|
5919
|
+
);
|
|
5920
|
+
|
|
5921
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles(true);
|
|
6146
5922
|
|
|
6147
|
-
|
|
6148
|
-
expect(
|
|
6149
|
-
expect(
|
|
5923
|
+
expect(bundles).toHaveLength(6);
|
|
5924
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
5925
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledTimes(6);
|
|
5926
|
+
resourcesSpy.mockRestore();
|
|
6150
5927
|
});
|
|
6151
5928
|
});
|
|
6152
5929
|
|
|
@@ -6286,311 +6063,230 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6286
6063
|
expect(method.unlockPolicy).toBe('none');
|
|
6287
6064
|
});
|
|
6288
6065
|
|
|
6289
|
-
test('
|
|
6066
|
+
test('checks fresh status for a wallet business method without a name allowlist', async () => {
|
|
6290
6067
|
const calls: string[] = [];
|
|
6068
|
+
const features = { unlocked: true };
|
|
6291
6069
|
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
|
-
}),
|
|
6070
|
+
name: 'confluxSignMessageCIP23',
|
|
6071
|
+
unlockPolicy: 'none',
|
|
6072
|
+
useDevicePassphraseState: true,
|
|
6073
|
+
protocolV2UiInteraction: { reason: 'address-confirmation' },
|
|
6074
|
+
run: jest.fn(() => {
|
|
6075
|
+
calls.push('run');
|
|
6076
|
+
return Promise.resolve({ address: '0x1' });
|
|
6077
|
+
}),
|
|
6305
6078
|
};
|
|
6306
6079
|
const device = {
|
|
6080
|
+
features,
|
|
6081
|
+
commands: {
|
|
6082
|
+
typedCall: jest.fn(() => {
|
|
6083
|
+
calls.push('status');
|
|
6084
|
+
return Promise.resolve({ message: { unlocked: true } });
|
|
6085
|
+
}),
|
|
6086
|
+
},
|
|
6307
6087
|
isProtocolV2: () => true,
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6088
|
+
isBootloader: () => false,
|
|
6089
|
+
isRomloader: () => false,
|
|
6090
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6091
|
+
features.unlocked = status.unlocked === true;
|
|
6092
|
+
return features;
|
|
6311
6093
|
}),
|
|
6094
|
+
unlockDevice: jest.fn(),
|
|
6312
6095
|
};
|
|
6313
6096
|
const uiCoordinator = {
|
|
6314
6097
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6315
|
-
enterUnlockInteraction: jest.fn(
|
|
6316
|
-
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6098
|
+
enterUnlockInteraction: jest.fn(),
|
|
6317
6099
|
};
|
|
6318
6100
|
|
|
6319
6101
|
await expect(
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
]);
|
|
6102
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6103
|
+
uiCoordinator: uiCoordinator as any,
|
|
6104
|
+
})
|
|
6105
|
+
).resolves.toEqual({ address: '0x1' });
|
|
6106
|
+
|
|
6107
|
+
expect(calls).toEqual(['status', 'method-prompt', 'run']);
|
|
6108
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6109
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6110
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6330
6111
|
});
|
|
6331
6112
|
|
|
6332
|
-
test('
|
|
6113
|
+
test('validates the fresh device identity before starting unlock', async () => {
|
|
6333
6114
|
const calls: string[] = [];
|
|
6115
|
+
const identityError = new Error('Unexpected device');
|
|
6334
6116
|
const method = {
|
|
6335
6117
|
name: 'evmSignMessage',
|
|
6336
|
-
|
|
6118
|
+
unlockPolicy: 'none',
|
|
6337
6119
|
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
|
-
}),
|
|
6120
|
+
run: jest.fn(),
|
|
6349
6121
|
};
|
|
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
|
-
});
|
|
6122
|
+
const features = { unlocked: false };
|
|
6367
6123
|
const device = {
|
|
6368
|
-
features
|
|
6369
|
-
|
|
6370
|
-
|
|
6124
|
+
features,
|
|
6125
|
+
commands: {
|
|
6126
|
+
typedCall: jest.fn(() => {
|
|
6127
|
+
calls.push('status');
|
|
6128
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6129
|
+
}),
|
|
6130
|
+
},
|
|
6371
6131
|
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')),
|
|
6132
|
+
isBootloader: () => false,
|
|
6133
|
+
isRomloader: () => false,
|
|
6134
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6135
|
+
unlockDevice: jest.fn(),
|
|
6381
6136
|
};
|
|
6382
6137
|
|
|
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
|
-
};
|
|
6138
|
+
await expect(
|
|
6139
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6140
|
+
afterStatusBeforeUnlock: () => {
|
|
6141
|
+
calls.push('identity');
|
|
6142
|
+
throw identityError;
|
|
6143
|
+
},
|
|
6144
|
+
})
|
|
6145
|
+
).rejects.toBe(identityError);
|
|
6427
6146
|
|
|
6428
|
-
|
|
6429
|
-
expect(
|
|
6430
|
-
expect(
|
|
6431
|
-
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
-
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6147
|
+
expect(calls).toEqual(['status', 'identity']);
|
|
6148
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6149
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6433
6150
|
});
|
|
6434
6151
|
|
|
6435
|
-
test('
|
|
6152
|
+
test('unlocks before business and never replays the callback', async () => {
|
|
6436
6153
|
const calls: string[] = [];
|
|
6154
|
+
const features = { unlocked: true };
|
|
6437
6155
|
const method = {
|
|
6438
6156
|
name: 'evmSignMessage',
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
unlockPolicy: 'retry-on-locked',
|
|
6157
|
+
unlockPolicy: 'unlock-before-run',
|
|
6158
|
+
protocolV2UiInteraction: { reason: 'signing-confirmation' },
|
|
6442
6159
|
run: jest.fn(() => {
|
|
6443
6160
|
calls.push('run');
|
|
6444
|
-
return Promise.resolve({
|
|
6161
|
+
return Promise.resolve({ signature: 'signed' });
|
|
6445
6162
|
}),
|
|
6446
6163
|
};
|
|
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
6164
|
const device = {
|
|
6470
6165
|
features,
|
|
6471
|
-
|
|
6472
|
-
|
|
6166
|
+
commands: {
|
|
6167
|
+
typedCall: jest.fn(() => {
|
|
6168
|
+
calls.push('status');
|
|
6169
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6170
|
+
}),
|
|
6171
|
+
},
|
|
6473
6172
|
isProtocolV2: () => true,
|
|
6173
|
+
isBootloader: () => false,
|
|
6174
|
+
isRomloader: () => false,
|
|
6175
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6176
|
+
features.unlocked = status.unlocked === true;
|
|
6177
|
+
return features;
|
|
6178
|
+
}),
|
|
6474
6179
|
unlockDevice: jest.fn(() => {
|
|
6475
|
-
calls.push('unlock
|
|
6180
|
+
calls.push('unlock');
|
|
6476
6181
|
features.unlocked = true;
|
|
6477
|
-
features
|
|
6478
|
-
|
|
6182
|
+
return Promise.resolve(features);
|
|
6183
|
+
}),
|
|
6184
|
+
};
|
|
6185
|
+
const uiCoordinator = {
|
|
6186
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6187
|
+
enterUnlockInteraction: jest.fn(() => {
|
|
6188
|
+
calls.push('unlock-prompt');
|
|
6189
|
+
return undefined;
|
|
6479
6190
|
}),
|
|
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
6191
|
};
|
|
6486
6192
|
|
|
6487
|
-
await expect(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6193
|
+
await expect(
|
|
6194
|
+
runMethodWithUnlockPolicy(method as any, device as any, {
|
|
6195
|
+
uiCoordinator: uiCoordinator as any,
|
|
6196
|
+
prepare: () => {
|
|
6197
|
+
calls.push('wallet-session');
|
|
6198
|
+
return Promise.resolve();
|
|
6199
|
+
},
|
|
6200
|
+
})
|
|
6201
|
+
).resolves.toEqual({ signature: 'signed' });
|
|
6202
|
+
|
|
6491
6203
|
expect(calls).toEqual([
|
|
6492
|
-
'
|
|
6493
|
-
'
|
|
6494
|
-
'
|
|
6495
|
-
'
|
|
6204
|
+
'status',
|
|
6205
|
+
'unlock-prompt',
|
|
6206
|
+
'unlock',
|
|
6207
|
+
'wallet-session',
|
|
6208
|
+
'method-prompt',
|
|
6496
6209
|
'run',
|
|
6497
6210
|
]);
|
|
6211
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6212
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6498
6213
|
});
|
|
6499
6214
|
|
|
6500
|
-
test('
|
|
6215
|
+
test('fails closed when fresh status does not explicitly report unlocked state', async () => {
|
|
6501
6216
|
const method = {
|
|
6502
6217
|
name: 'evmGetAddress',
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
unlockPolicy: 'retry-on-locked',
|
|
6506
|
-
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6218
|
+
unlockPolicy: 'unlock-before-run',
|
|
6219
|
+
run: jest.fn(),
|
|
6507
6220
|
};
|
|
6508
6221
|
const device = {
|
|
6509
|
-
features: { unlocked:
|
|
6510
|
-
|
|
6222
|
+
features: { unlocked: undefined },
|
|
6223
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: {} }) },
|
|
6511
6224
|
isProtocolV2: () => true,
|
|
6512
|
-
|
|
6225
|
+
isBootloader: () => false,
|
|
6226
|
+
isRomloader: () => false,
|
|
6227
|
+
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6228
|
+
unlockDevice: jest.fn(),
|
|
6513
6229
|
};
|
|
6514
6230
|
|
|
6515
|
-
await expect(
|
|
6516
|
-
|
|
6231
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toMatchObject({
|
|
6232
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6517
6233
|
});
|
|
6518
|
-
expect(device.unlockDevice).
|
|
6519
|
-
expect(method.run).
|
|
6234
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6235
|
+
expect(method.run).not.toHaveBeenCalled();
|
|
6520
6236
|
});
|
|
6521
6237
|
|
|
6522
|
-
test('
|
|
6523
|
-
const
|
|
6238
|
+
test('returns a business DeviceLocked error without unlocking or replaying', async () => {
|
|
6239
|
+
const error = deviceLockedError();
|
|
6524
6240
|
const method = {
|
|
6525
|
-
name: '
|
|
6241
|
+
name: 'evmSignMessage',
|
|
6526
6242
|
unlockPolicy: 'unlock-before-run',
|
|
6527
|
-
|
|
6528
|
-
run: jest.fn(() => {
|
|
6529
|
-
calls.push('run');
|
|
6530
|
-
return Promise.resolve({ message: 'ok' });
|
|
6531
|
-
}),
|
|
6243
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6532
6244
|
};
|
|
6533
6245
|
const features = { unlocked: true };
|
|
6534
6246
|
const device = {
|
|
6535
6247
|
features,
|
|
6536
|
-
commands: {
|
|
6537
|
-
typedCall: jest.fn(() => {
|
|
6538
|
-
calls.push('refresh-status');
|
|
6539
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
-
}),
|
|
6541
|
-
},
|
|
6248
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6542
6249
|
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')),
|
|
6250
|
+
isBootloader: () => false,
|
|
6251
|
+
isRomloader: () => false,
|
|
6252
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6253
|
+
unlockDevice: jest.fn(),
|
|
6557
6254
|
};
|
|
6558
6255
|
|
|
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', {});
|
|
6256
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).rejects.toBe(error);
|
|
6257
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6564
6258
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
-
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
-
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6567
6259
|
});
|
|
6568
6260
|
|
|
6569
|
-
test('
|
|
6570
|
-
const features = { unlocked:
|
|
6571
|
-
const
|
|
6572
|
-
|
|
6261
|
+
test('reuses one lightweight preflight context across nested protected methods', async () => {
|
|
6262
|
+
const features = { unlocked: true };
|
|
6263
|
+
const context = createProtocolV2UnlockContext();
|
|
6264
|
+
const firstMethod = {
|
|
6265
|
+
name: 'allNetworkGetAddress',
|
|
6266
|
+
unlockPolicy: 'unlock-before-run',
|
|
6267
|
+
run: jest.fn().mockResolvedValue({ root: true }),
|
|
6268
|
+
};
|
|
6269
|
+
const nestedMethod = {
|
|
6270
|
+
name: 'evmGetAddress',
|
|
6573
6271
|
unlockPolicy: 'unlock-before-run',
|
|
6574
|
-
run: jest.fn().mockResolvedValue({
|
|
6272
|
+
run: jest.fn().mockResolvedValue({ address: '0x1' }),
|
|
6575
6273
|
};
|
|
6576
6274
|
const device = {
|
|
6577
6275
|
features,
|
|
6578
|
-
commands: {
|
|
6579
|
-
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
-
},
|
|
6276
|
+
commands: { typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }) },
|
|
6581
6277
|
isProtocolV2: () => true,
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
}),
|
|
6278
|
+
isBootloader: () => false,
|
|
6279
|
+
isRomloader: () => false,
|
|
6280
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
6586
6281
|
unlockDevice: jest.fn(),
|
|
6587
6282
|
};
|
|
6588
6283
|
|
|
6589
|
-
await
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
expect(device.
|
|
6593
|
-
expect(
|
|
6284
|
+
await runMethodWithUnlockPolicy(firstMethod as any, device as any, { context });
|
|
6285
|
+
await runMethodWithUnlockPolicy(nestedMethod as any, device as any, { context });
|
|
6286
|
+
|
|
6287
|
+
expect(device.commands.typedCall).toHaveBeenCalledTimes(1);
|
|
6288
|
+
expect(firstMethod.run).toHaveBeenCalledTimes(1);
|
|
6289
|
+
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
6594
6290
|
});
|
|
6595
6291
|
|
|
6596
6292
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6610,7 +6306,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6610
6306
|
unlockDevice: jest.fn(),
|
|
6611
6307
|
};
|
|
6612
6308
|
|
|
6613
|
-
await expect(
|
|
6309
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6614
6310
|
message: 'updating',
|
|
6615
6311
|
});
|
|
6616
6312
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6619,157 +6315,30 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6619
6315
|
}
|
|
6620
6316
|
);
|
|
6621
6317
|
|
|
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 () => {
|
|
6318
|
+
test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
|
|
6653
6319
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6654
|
-
[false, '
|
|
6320
|
+
[false, 'unlock-before-run'],
|
|
6655
6321
|
[true, 'none'],
|
|
6656
6322
|
] as const) {
|
|
6657
|
-
const error = deviceLockedError();
|
|
6658
6323
|
const method = {
|
|
6659
6324
|
unlockPolicy,
|
|
6660
|
-
|
|
6325
|
+
useDevicePassphraseState: false,
|
|
6326
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6661
6327
|
};
|
|
6662
6328
|
const device = {
|
|
6329
|
+
commands: { typedCall: jest.fn() },
|
|
6663
6330
|
isProtocolV2: () => isProtocolV2,
|
|
6664
6331
|
unlockDevice: jest.fn(),
|
|
6665
6332
|
};
|
|
6666
6333
|
|
|
6667
|
-
await expect(
|
|
6334
|
+
await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
|
|
6335
|
+
message: 'ok',
|
|
6336
|
+
});
|
|
6337
|
+
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6668
6338
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6669
6339
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6670
6340
|
}
|
|
6671
6341
|
});
|
|
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
6342
|
});
|
|
6774
6343
|
|
|
6775
6344
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6901,7 +6470,9 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6901
6470
|
|
|
6902
6471
|
await method.run();
|
|
6903
6472
|
|
|
6904
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
6473
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
6474
|
+
eventless_wallet_session: true,
|
|
6475
|
+
});
|
|
6905
6476
|
});
|
|
6906
6477
|
|
|
6907
6478
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|