@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.23
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__/DeviceCommands.test.ts +85 -0
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-identity.test.ts +24 -2
- package/__tests__/device-settings.test.ts +2 -2
- package/__tests__/device-wallet-session-store.test.ts +152 -7
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +86 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/logBlockEvent.test.ts +17 -0
- package/__tests__/open-wallet-session.test.ts +311 -12
- package/__tests__/pro2Wallpaper.test.ts +7 -18
- package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
- package/__tests__/protocol-v2.test.ts +685 -177
- package/__tests__/public-device-state-api.test.ts +69 -62
- package/__tests__/public-pro2-api-boundary.test.ts +8 -34
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +29 -10
- 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/conflux/ConfluxGetAddress.d.ts +1 -0
- package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +1 -1
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +23 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -17
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
- package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- 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 +3 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/events/device.d.ts +1 -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 +1 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +273 -230
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3060 -1676
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -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 +3 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +2 -2
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +69 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +10 -19
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -76
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +4 -1
- package/dist/types/api/sessionCache.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -3
- package/src/api/CheckAllFirmwareRelease.ts +23 -3
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/FirmwareUpdate.ts +8 -1
- package/src/api/FirmwareUpdateV2.ts +292 -120
- package/src/api/FirmwareUpdateV3.ts +256 -55
- package/src/api/FirmwareUpdateV4.ts +821 -331
- package/src/api/GetPassphraseState.ts +11 -5
- package/src/api/OpenWalletSession.ts +74 -25
- package/src/api/UploadPortfolio.ts +1 -1
- package/src/api/conflux/ConfluxGetAddress.ts +2 -0
- package/src/api/conflux/ConfluxSignMessage.ts +2 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
- package/src/api/device/DeviceChangePin.ts +1 -1
- package/src/api/device/DeviceUnlock.ts +1 -2
- package/src/api/device/DeviceUpdateBootloader.ts +122 -6
- package/src/api/device/DeviceWipe.ts +3 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +6 -0
- package/src/api/index.ts +0 -18
- package/src/api/nexa/NexaSignTransaction.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/core/index.ts +1 -0
- package/src/data/messages/messages-protocol-v2.json +35 -1
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +80 -67
- package/src/device/DeviceCommands.ts +76 -159
- package/src/device/DeviceStateMapper.ts +18 -1
- package/src/device/DeviceStateProjector.ts +7 -1
- package/src/events/device.ts +1 -0
- package/src/events/logBlockEvent.ts +1 -14
- package/src/events/ui-request.ts +1 -0
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -25
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -3
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
- package/src/protocols/protocol-v2/walletSession.ts +86 -9
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +3 -0
- package/src/types/api/deviceUnlock.ts +2 -5
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +36 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/getPassphraseState.ts +4 -3
- package/src/types/api/index.ts +14 -34
- package/src/types/api/openWalletSession.ts +2 -0
- package/src/types/api/protocolV2.ts +3 -156
- package/src/types/api/sessionCache.ts +9 -4
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +5 -1
- package/src/utils/deviceInfoUtils.ts +3 -16
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { DeviceRebootType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { DeviceRebootType, DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
5
5
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -83,6 +83,7 @@ import {
|
|
|
83
83
|
isMethodVersionRangeUnsupported,
|
|
84
84
|
} from '../src/utils';
|
|
85
85
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
86
|
+
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
86
87
|
import {
|
|
87
88
|
getPassphraseState,
|
|
88
89
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -122,7 +123,7 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
122
123
|
const result = await method.run();
|
|
123
124
|
|
|
124
125
|
expect(method.requireProtocolV2).toBe(true);
|
|
125
|
-
expect(method.unlockPolicy).toBe('
|
|
126
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
126
127
|
expect(typedCall).toHaveBeenNthCalledWith(1, 'FilesystemDirMake', 'Success', {
|
|
127
128
|
path: 'vol1:/wallpapers',
|
|
128
129
|
});
|
|
@@ -211,7 +212,7 @@ describe('UploadPortfolio', () => {
|
|
|
211
212
|
method.init();
|
|
212
213
|
const result = await method.run();
|
|
213
214
|
|
|
214
|
-
expect(method.unlockPolicy).toBe('
|
|
215
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
215
216
|
expect(method.protocolV2UiMode).toBe('none');
|
|
216
217
|
expect(method.protocolV2UiInteraction).toBeUndefined();
|
|
217
218
|
expect(method.payload.emitProgress).toBe(false);
|
|
@@ -711,7 +712,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
711
712
|
unlockedAttachPin: false,
|
|
712
713
|
resumed: false,
|
|
713
714
|
});
|
|
714
|
-
expect(typedCall).
|
|
715
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
716
|
+
eventless_wallet_session: true,
|
|
717
|
+
});
|
|
715
718
|
expect(device.getInternalState()).toBeUndefined();
|
|
716
719
|
});
|
|
717
720
|
|
|
@@ -721,7 +724,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
721
724
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
722
725
|
{ status: { passphrase_enabled: false, unlocked: true } }
|
|
723
726
|
);
|
|
724
|
-
const typedCall = jest.fn()
|
|
727
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
728
|
+
type: 'DeviceSession',
|
|
729
|
+
message: {
|
|
730
|
+
btc_test_address: 'main-wallet-state',
|
|
731
|
+
session_id: 'main-wallet-session',
|
|
732
|
+
},
|
|
733
|
+
});
|
|
725
734
|
const promptPassphrase = jest
|
|
726
735
|
.fn()
|
|
727
736
|
.mockResolvedValue({ passphrase: 'hidden-wallet-with-disabled-flag' });
|
|
@@ -735,7 +744,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
735
744
|
});
|
|
736
745
|
|
|
737
746
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
738
|
-
expect(typedCall).
|
|
747
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
748
|
+
eventless_wallet_session: true,
|
|
749
|
+
});
|
|
739
750
|
});
|
|
740
751
|
|
|
741
752
|
test('preserves cached settings only while Protocol V2 physical identity stays the same', () => {
|
|
@@ -790,13 +801,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
790
801
|
}
|
|
791
802
|
);
|
|
792
803
|
const typedCall = jest.fn().mockImplementation((request: string) => {
|
|
793
|
-
if (request === '
|
|
794
|
-
return Promise.resolve({
|
|
795
|
-
message: {
|
|
796
|
-
btc_test_address: 'state-after-unlock',
|
|
797
|
-
session_id: 'session-after-unlock',
|
|
798
|
-
},
|
|
799
|
-
});
|
|
804
|
+
if (request === 'ProtocolInfoRequest') {
|
|
805
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
800
806
|
}
|
|
801
807
|
if (request === 'DeviceStatusGet') {
|
|
802
808
|
return Promise.resolve({
|
|
@@ -825,7 +831,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
825
831
|
|
|
826
832
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
827
833
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
828
|
-
expect(typedCall).
|
|
834
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
835
|
+
eventless_wallet_session: true,
|
|
836
|
+
});
|
|
829
837
|
});
|
|
830
838
|
|
|
831
839
|
test('onlyMainPin takes precedence over a cached hidden-wallet state', async () => {
|
|
@@ -836,14 +844,28 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
836
844
|
);
|
|
837
845
|
device.passphraseState = 'hidden-state';
|
|
838
846
|
preloadSessionCache('main-wallet-device', 'hidden-state', 'hidden-session');
|
|
839
|
-
const typedCall = jest.fn()
|
|
847
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
848
|
+
type: 'DeviceSession',
|
|
849
|
+
message: {
|
|
850
|
+
btc_test_address: 'main-wallet-state',
|
|
851
|
+
session_id: 'main-wallet-session',
|
|
852
|
+
},
|
|
853
|
+
});
|
|
840
854
|
(device as any).commands = { typedCall };
|
|
841
855
|
|
|
842
|
-
await
|
|
843
|
-
|
|
856
|
+
await expect(
|
|
857
|
+
getPassphraseState(device, {
|
|
858
|
+
onlyMainPin: true,
|
|
859
|
+
})
|
|
860
|
+
).resolves.toMatchObject({
|
|
861
|
+
passphraseState: undefined,
|
|
862
|
+
newSession: undefined,
|
|
844
863
|
});
|
|
845
|
-
expect(typedCall).
|
|
846
|
-
|
|
864
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
865
|
+
eventless_wallet_session: true,
|
|
866
|
+
});
|
|
867
|
+
expect(device.passphraseState).toBeUndefined();
|
|
868
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
847
869
|
});
|
|
848
870
|
|
|
849
871
|
test('gets the wallet session selected by the Protocol V2 device', async () => {
|
|
@@ -867,7 +889,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
867
889
|
type: 'DeviceSession',
|
|
868
890
|
message: { session_id: 'hidden-session', btc_test_address: 'hidden-state' },
|
|
869
891
|
});
|
|
870
|
-
const promptPassphrase = jest.fn();
|
|
892
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
|
|
871
893
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
872
894
|
|
|
873
895
|
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
@@ -875,7 +897,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
875
897
|
passphraseState: 'hidden-state',
|
|
876
898
|
});
|
|
877
899
|
|
|
878
|
-
expect(promptPassphrase).
|
|
900
|
+
expect(promptPassphrase).toHaveBeenCalled();
|
|
901
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
902
|
+
'DeviceSessionAskPassphrase',
|
|
903
|
+
'Success',
|
|
904
|
+
{},
|
|
905
|
+
{
|
|
906
|
+
timeoutMs: 120_000,
|
|
907
|
+
}
|
|
908
|
+
);
|
|
879
909
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
880
910
|
});
|
|
881
911
|
|
|
@@ -999,13 +1029,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
999
1029
|
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
1000
1030
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1001
1031
|
type: 'DeviceSession',
|
|
1002
|
-
message: {
|
|
1032
|
+
message: {
|
|
1033
|
+
session_id: 'main-session',
|
|
1034
|
+
btc_test_address: 'main-wallet-state',
|
|
1035
|
+
},
|
|
1003
1036
|
});
|
|
1004
1037
|
(device as any).commands = { typedCall };
|
|
1005
1038
|
|
|
1006
1039
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
1007
1040
|
|
|
1008
|
-
expect(typedCall).
|
|
1041
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1042
|
+
eventless_wallet_session: true,
|
|
1043
|
+
});
|
|
1009
1044
|
expect(device.getInternalState()).toBeUndefined();
|
|
1010
1045
|
});
|
|
1011
1046
|
|
|
@@ -1027,11 +1062,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1027
1062
|
);
|
|
1028
1063
|
device.passphraseState = 'state-a';
|
|
1029
1064
|
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
1030
|
-
const typedCall = jest
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1065
|
+
const typedCall = jest
|
|
1066
|
+
.fn()
|
|
1067
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1068
|
+
.mockRejectedValue(
|
|
1069
|
+
Object.assign(new Error('SE request failed'), {
|
|
1070
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1071
|
+
})
|
|
1072
|
+
);
|
|
1035
1073
|
const promptPassphrase = jest.fn();
|
|
1036
1074
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
1037
1075
|
|
|
@@ -1039,6 +1077,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1039
1077
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
1040
1078
|
});
|
|
1041
1079
|
expect(typedCall.mock.calls).toEqual([
|
|
1080
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1042
1081
|
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
1043
1082
|
]);
|
|
1044
1083
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
@@ -1061,11 +1100,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1061
1100
|
},
|
|
1062
1101
|
}
|
|
1063
1102
|
);
|
|
1064
|
-
const typedCall = jest
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1103
|
+
const typedCall = jest
|
|
1104
|
+
.fn()
|
|
1105
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1106
|
+
.mockRejectedValue(
|
|
1107
|
+
Object.assign(new Error('Invalid session'), {
|
|
1108
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1109
|
+
})
|
|
1110
|
+
);
|
|
1069
1111
|
(device as any).commands = {
|
|
1070
1112
|
typedCall,
|
|
1071
1113
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'new-secret' }),
|
|
@@ -1074,7 +1116,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1074
1116
|
await expect(getProtocolV2WalletSession(device)).rejects.toMatchObject({
|
|
1075
1117
|
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1076
1118
|
});
|
|
1077
|
-
expect(typedCall.mock.calls).toEqual([
|
|
1119
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1120
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1121
|
+
['DeviceSessionAskPassphrase', 'Success', {}, { timeoutMs: 120_000 }],
|
|
1122
|
+
]);
|
|
1078
1123
|
});
|
|
1079
1124
|
|
|
1080
1125
|
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
@@ -1112,7 +1157,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1112
1157
|
expect(device.getInternalState()).toBeUndefined();
|
|
1113
1158
|
});
|
|
1114
1159
|
|
|
1115
|
-
test('unlocks and retries
|
|
1160
|
+
test('unlocks and retries DeviceSessionAskPassphrase when wallet selection is locked', async () => {
|
|
1116
1161
|
const device = Device.fromDescriptor({
|
|
1117
1162
|
id: 'cache-device-4',
|
|
1118
1163
|
path: 'cache-path-4',
|
|
@@ -1127,7 +1172,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1127
1172
|
});
|
|
1128
1173
|
const typedCall = jest
|
|
1129
1174
|
.fn()
|
|
1175
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1130
1176
|
.mockRejectedValueOnce(lockedError)
|
|
1177
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1131
1178
|
.mockResolvedValueOnce({
|
|
1132
1179
|
message: {
|
|
1133
1180
|
session_id: 'session-after-unlock',
|
|
@@ -1145,7 +1192,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1145
1192
|
passphraseState: 'state-after-unlock',
|
|
1146
1193
|
});
|
|
1147
1194
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
1148
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
1195
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
1149
1196
|
});
|
|
1150
1197
|
|
|
1151
1198
|
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
@@ -1262,6 +1309,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1262
1309
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'state-pro2-secret' }),
|
|
1263
1310
|
},
|
|
1264
1311
|
updateInternalState,
|
|
1312
|
+
getInternalState: () => 'feature-session',
|
|
1265
1313
|
getCurrentDeviceId: () => 'pro-device-id',
|
|
1266
1314
|
getCurrentPassphraseProtection: () => true,
|
|
1267
1315
|
}) as any;
|
|
@@ -1276,24 +1324,29 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1276
1324
|
);
|
|
1277
1325
|
});
|
|
1278
1326
|
|
|
1279
|
-
test('
|
|
1327
|
+
test('maps the legacy getPassphraseState App flow to the Pro2 wallet session protocol', async () => {
|
|
1280
1328
|
const features = {
|
|
1281
|
-
deviceId:
|
|
1329
|
+
deviceId: 'pro2-device-id',
|
|
1282
1330
|
deviceType: 'pro2',
|
|
1283
1331
|
firmwareVersion: '4.15.0',
|
|
1284
1332
|
passphraseProtection: true,
|
|
1285
1333
|
sessionId: 'feature-session',
|
|
1286
1334
|
unlockedAttachPin: true,
|
|
1287
1335
|
};
|
|
1288
|
-
const typedCall = jest
|
|
1289
|
-
|
|
1290
|
-
message: {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1336
|
+
const typedCall = jest
|
|
1337
|
+
.fn()
|
|
1338
|
+
.mockResolvedValueOnce({ message: { eventless_wallet_session: true } })
|
|
1339
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1340
|
+
.mockResolvedValueOnce({
|
|
1341
|
+
type: 'DeviceSession',
|
|
1342
|
+
message: {
|
|
1343
|
+
btc_test_address: 'state-pro2',
|
|
1344
|
+
session_id: 'session-pro2',
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1295
1347
|
const updateInternalState = jest.fn();
|
|
1296
1348
|
const getFeatures = jest.fn().mockResolvedValue(features);
|
|
1349
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'legacy app secret' });
|
|
1297
1350
|
const method = new GetPassphraseState({
|
|
1298
1351
|
payload: {
|
|
1299
1352
|
method: 'getPassphraseState',
|
|
@@ -1305,21 +1358,91 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1305
1358
|
features,
|
|
1306
1359
|
commands: {
|
|
1307
1360
|
typedCall,
|
|
1308
|
-
promptPassphrase
|
|
1361
|
+
promptPassphrase,
|
|
1309
1362
|
},
|
|
1363
|
+
emit: jest.fn(),
|
|
1310
1364
|
updateInternalState,
|
|
1311
1365
|
getFeatures,
|
|
1312
1366
|
getCurrentDeviceType: () => 'pro2',
|
|
1313
|
-
getCurrentDeviceId: () =>
|
|
1367
|
+
getCurrentDeviceId: () => 'pro2-device-id',
|
|
1314
1368
|
getCurrentPassphraseProtection: () => true,
|
|
1315
1369
|
}) as any;
|
|
1316
1370
|
|
|
1317
|
-
await expect(method.run()).
|
|
1318
|
-
|
|
1319
|
-
|
|
1371
|
+
await expect(method.run()).resolves.toBe('state-pro2');
|
|
1372
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
1373
|
+
expect.objectContaining({ deviceOnly: true }),
|
|
1374
|
+
expect.anything()
|
|
1375
|
+
);
|
|
1376
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
1377
|
+
true,
|
|
1378
|
+
'state-pro2',
|
|
1379
|
+
'pro2-device-id',
|
|
1380
|
+
'session-pro2',
|
|
1381
|
+
null
|
|
1382
|
+
);
|
|
1383
|
+
expect(JSON.stringify(typedCall.mock.calls)).not.toContain('legacy app secret');
|
|
1320
1384
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1321
1385
|
});
|
|
1322
1386
|
|
|
1387
|
+
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
1388
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1389
|
+
const typedCall = jest.fn().mockImplementation((request: string) => {
|
|
1390
|
+
if (request === 'ProtocolInfoRequest') {
|
|
1391
|
+
return Promise.resolve({ message: { eventless_wallet_session: true } });
|
|
1392
|
+
}
|
|
1393
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
1394
|
+
return Promise.resolve({ message: { message: 'passphrase prepared' } });
|
|
1395
|
+
}
|
|
1396
|
+
if (request === 'DeviceSessionGet') {
|
|
1397
|
+
return Promise.resolve({
|
|
1398
|
+
type: 'DeviceSession',
|
|
1399
|
+
message: {
|
|
1400
|
+
btc_test_address: 'state-pro2-app',
|
|
1401
|
+
session_id: 'session-pro2-app',
|
|
1402
|
+
},
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1409
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1410
|
+
{
|
|
1411
|
+
status: {
|
|
1412
|
+
device_id: 'pro2-app-device-id',
|
|
1413
|
+
unlocked: true,
|
|
1414
|
+
passphrase_enabled: true,
|
|
1415
|
+
},
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
(device as any).commands = {
|
|
1419
|
+
typedCall,
|
|
1420
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
const method = new GetPassphraseState({
|
|
1424
|
+
payload: {
|
|
1425
|
+
method: 'getPassphraseState',
|
|
1426
|
+
connectId: 'connect-id',
|
|
1427
|
+
initSession: true,
|
|
1428
|
+
},
|
|
1429
|
+
});
|
|
1430
|
+
method.device = device;
|
|
1431
|
+
|
|
1432
|
+
await expect(method.run()).resolves.toBe('state-pro2-app');
|
|
1433
|
+
|
|
1434
|
+
await device.initialize({ passphraseState: 'state-pro2-app' });
|
|
1435
|
+
typedCall.mockClear();
|
|
1436
|
+
|
|
1437
|
+
await expect(device.checkPassphraseStateSafety('state-pro2-app', false, false)).resolves.toBe(
|
|
1438
|
+
true
|
|
1439
|
+
);
|
|
1440
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1441
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1442
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
1443
|
+
]);
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1323
1446
|
test('uses the Pro2 standard wallet without passphraseState when passphrase is disabled', async () => {
|
|
1324
1447
|
const features = {
|
|
1325
1448
|
deviceId: 'pro2-device-id',
|
|
@@ -1334,6 +1457,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1334
1457
|
payload: {
|
|
1335
1458
|
method: 'getPassphraseState',
|
|
1336
1459
|
connectId: 'connect-id',
|
|
1460
|
+
useEmptyPassphrase: true,
|
|
1337
1461
|
},
|
|
1338
1462
|
});
|
|
1339
1463
|
method.device = stubDevice({
|
|
@@ -1349,10 +1473,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1349
1473
|
getCurrentPassphraseProtection: () => false,
|
|
1350
1474
|
}) as any;
|
|
1351
1475
|
|
|
1352
|
-
await expect(method.run()).
|
|
1353
|
-
|
|
1476
|
+
await expect(method.run()).resolves.toBeUndefined();
|
|
1477
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1478
|
+
eventless_wallet_session: true,
|
|
1354
1479
|
});
|
|
1355
|
-
expect(typedCall).not.toHaveBeenCalled();
|
|
1356
1480
|
expect(updateInternalState).not.toHaveBeenCalled();
|
|
1357
1481
|
});
|
|
1358
1482
|
|
|
@@ -1365,12 +1489,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1365
1489
|
sessionId: 'old-feature-session',
|
|
1366
1490
|
unlockedAttachPin: false,
|
|
1367
1491
|
};
|
|
1368
|
-
const typedCall = jest
|
|
1369
|
-
|
|
1370
|
-
message: {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1492
|
+
const typedCall = jest
|
|
1493
|
+
.fn()
|
|
1494
|
+
.mockResolvedValueOnce({ message: { eventless_wallet_session: true } })
|
|
1495
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1496
|
+
.mockResolvedValueOnce({
|
|
1497
|
+
type: 'DeviceSession',
|
|
1498
|
+
message: {
|
|
1499
|
+
btc_test_address: 'state-pro2-new',
|
|
1500
|
+
session_id: 'session-pro2-new',
|
|
1501
|
+
},
|
|
1502
|
+
});
|
|
1374
1503
|
const clearInternalState = jest.fn();
|
|
1375
1504
|
const updateInternalState = jest.fn();
|
|
1376
1505
|
const method = new GetPassphraseState({
|
|
@@ -1383,21 +1512,28 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1383
1512
|
method.device = stubDevice({
|
|
1384
1513
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
1385
1514
|
features,
|
|
1515
|
+
passphraseState: 'state-pro2-old',
|
|
1386
1516
|
commands: {
|
|
1387
1517
|
typedCall,
|
|
1388
1518
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'state-pro2-new-secret' }),
|
|
1389
1519
|
},
|
|
1520
|
+
emit: jest.fn(),
|
|
1390
1521
|
clearInternalState,
|
|
1391
1522
|
updateInternalState,
|
|
1392
1523
|
getCurrentDeviceId: () => 'pro2-device-id',
|
|
1393
1524
|
getCurrentPassphraseProtection: () => true,
|
|
1394
1525
|
}) as any;
|
|
1395
1526
|
|
|
1396
|
-
await expect(method.run()).
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
expect(
|
|
1400
|
-
|
|
1527
|
+
await expect(method.run()).resolves.toBe('state-pro2-new');
|
|
1528
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
1529
|
+
expect((method.device as any).passphraseState).toBeUndefined();
|
|
1530
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
1531
|
+
true,
|
|
1532
|
+
'state-pro2-new',
|
|
1533
|
+
'pro2-device-id',
|
|
1534
|
+
'session-pro2-new',
|
|
1535
|
+
null
|
|
1536
|
+
);
|
|
1401
1537
|
});
|
|
1402
1538
|
|
|
1403
1539
|
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
@@ -1481,6 +1617,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1481
1617
|
type: 'DeviceSession',
|
|
1482
1618
|
message: {
|
|
1483
1619
|
session_id: 'main-pin-session',
|
|
1620
|
+
btc_test_address: 'main-wallet-state',
|
|
1484
1621
|
},
|
|
1485
1622
|
});
|
|
1486
1623
|
|
|
@@ -1510,7 +1647,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1510
1647
|
expect(device.features?.passphraseProtection).toBe(false);
|
|
1511
1648
|
expect(device.features?.sessionId).toBeNull();
|
|
1512
1649
|
expect(device.getInternalState()).toBeUndefined();
|
|
1513
|
-
expect(typedCall).
|
|
1650
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1651
|
+
eventless_wallet_session: true,
|
|
1652
|
+
});
|
|
1514
1653
|
});
|
|
1515
1654
|
|
|
1516
1655
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
@@ -1542,12 +1681,26 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1542
1681
|
);
|
|
1543
1682
|
|
|
1544
1683
|
expect(device.getInternalState()).toBeUndefined();
|
|
1545
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1684
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1685
|
+
2,
|
|
1686
|
+
'DeviceSessionAskPassphrase',
|
|
1687
|
+
'Success',
|
|
1688
|
+
{},
|
|
1689
|
+
{
|
|
1690
|
+
timeoutMs: 120_000,
|
|
1691
|
+
}
|
|
1692
|
+
);
|
|
1546
1693
|
});
|
|
1547
1694
|
|
|
1548
1695
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
1549
1696
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1550
|
-
const typedCall = jest.fn()
|
|
1697
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
1698
|
+
type: 'DeviceSession',
|
|
1699
|
+
message: {
|
|
1700
|
+
session_id: 'main-wallet-session',
|
|
1701
|
+
btc_test_address: 'main-wallet-state',
|
|
1702
|
+
},
|
|
1703
|
+
});
|
|
1551
1704
|
|
|
1552
1705
|
(device as any).features = normalizeProtocolV2Features(
|
|
1553
1706
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1567,7 +1720,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1567
1720
|
await expect(
|
|
1568
1721
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
1569
1722
|
).resolves.toBe(true);
|
|
1570
|
-
expect(typedCall).
|
|
1723
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1724
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1725
|
+
eventless_wallet_session: true,
|
|
1726
|
+
});
|
|
1571
1727
|
});
|
|
1572
1728
|
|
|
1573
1729
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -1635,7 +1791,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1635
1791
|
uuid: 'CACHED-SERIAL',
|
|
1636
1792
|
deviceId: null,
|
|
1637
1793
|
bleName: 'Cached BLE',
|
|
1638
|
-
name: 'Cached
|
|
1794
|
+
name: 'Cached Label',
|
|
1639
1795
|
label: 'Cached Label',
|
|
1640
1796
|
deviceType: 'pro2',
|
|
1641
1797
|
});
|
|
@@ -1644,6 +1800,38 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1644
1800
|
expect(device.hasUsePassphrase()).toBe(true);
|
|
1645
1801
|
});
|
|
1646
1802
|
|
|
1803
|
+
test('keeps an empty Protocol V2 label distinct from the BLE name', () => {
|
|
1804
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1805
|
+
(device as any).features = {
|
|
1806
|
+
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
1807
|
+
serialNo: 'PR9999999999',
|
|
1808
|
+
label: null,
|
|
1809
|
+
bleName: 'Pro2 6136',
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
expect(device.toMessageObject()).toMatchObject({
|
|
1813
|
+
serialNo: 'PR9999999999',
|
|
1814
|
+
bleName: 'Pro2 6136',
|
|
1815
|
+
name: 'Pro2 6136',
|
|
1816
|
+
label: '',
|
|
1817
|
+
});
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
test('prefers the real Protocol V2 label over the BLE name for display', () => {
|
|
1821
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1822
|
+
(device as any).features = {
|
|
1823
|
+
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
1824
|
+
label: 'My Pro 2',
|
|
1825
|
+
bleName: 'Pro2 6136',
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
expect(device.toMessageObject()).toMatchObject({
|
|
1829
|
+
bleName: 'Pro2 6136',
|
|
1830
|
+
name: 'My Pro 2',
|
|
1831
|
+
label: 'My Pro 2',
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1647
1835
|
test('syncs Protocol V2 cached features without cached profile', () => {
|
|
1648
1836
|
const cached = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1649
1837
|
(cached as any).features = {
|
|
@@ -2105,7 +2293,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2105
2293
|
await expect(method.run()).resolves.toEqual({ address: '0x0', signature: '0x1' });
|
|
2106
2294
|
});
|
|
2107
2295
|
|
|
2108
|
-
test('unlocks Protocol V2 devices
|
|
2296
|
+
test('unlocks Protocol V2 devices with the main PIN regardless of Pro-series version gates', async () => {
|
|
2109
2297
|
const device = Device.fromDescriptor({
|
|
2110
2298
|
path: 'usb-path',
|
|
2111
2299
|
protocolType: 'V2',
|
|
@@ -2145,7 +2333,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2145
2333
|
const features = await device.unlockDevice();
|
|
2146
2334
|
|
|
2147
2335
|
expect(typedCall.mock.calls).toEqual([
|
|
2148
|
-
[
|
|
2336
|
+
[
|
|
2337
|
+
'DeviceSessionAskPin',
|
|
2338
|
+
'Success',
|
|
2339
|
+
{ type: DeviceSessionPinType.Main },
|
|
2340
|
+
{ timeoutMs: 120_000 },
|
|
2341
|
+
],
|
|
2149
2342
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2150
2343
|
]);
|
|
2151
2344
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
@@ -2193,7 +2386,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2193
2386
|
await device.unlockDevice();
|
|
2194
2387
|
|
|
2195
2388
|
expect(typedCall.mock.calls).toEqual([
|
|
2196
|
-
[
|
|
2389
|
+
[
|
|
2390
|
+
'DeviceSessionAskPin',
|
|
2391
|
+
'Success',
|
|
2392
|
+
{ type: DeviceSessionPinType.Main },
|
|
2393
|
+
{ timeoutMs: 120_000 },
|
|
2394
|
+
],
|
|
2197
2395
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2198
2396
|
]);
|
|
2199
2397
|
expect((device as any).profile).toBeUndefined();
|
|
@@ -2223,7 +2421,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2223
2421
|
);
|
|
2224
2422
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
2225
2423
|
expect(typedCall.mock.calls).toEqual([
|
|
2226
|
-
[
|
|
2424
|
+
[
|
|
2425
|
+
'DeviceSessionAskPin',
|
|
2426
|
+
'Success',
|
|
2427
|
+
{ type: DeviceSessionPinType.Main },
|
|
2428
|
+
{ timeoutMs: 120_000 },
|
|
2429
|
+
],
|
|
2227
2430
|
]);
|
|
2228
2431
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2229
2432
|
'DeviceSessionGet',
|
|
@@ -2669,6 +2872,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2669
2872
|
return Promise.resolve({
|
|
2670
2873
|
type: 'DeviceInfo',
|
|
2671
2874
|
message: {
|
|
2875
|
+
hw: { serial_no: 'BLE-PRO2-SERIAL' },
|
|
2672
2876
|
fw: {
|
|
2673
2877
|
bootloader: { version: '0.0.0' },
|
|
2674
2878
|
application: { version: '0.0.0' },
|
|
@@ -2689,6 +2893,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2689
2893
|
});
|
|
2690
2894
|
const commands = { typedCall };
|
|
2691
2895
|
|
|
2896
|
+
(method as any).protocolV2ExpectedSerialNumber = 'BLE-PRO2-SERIAL';
|
|
2692
2897
|
(method as any).isBleReconnect = jest.fn(() => true);
|
|
2693
2898
|
(method as any).device = stubDevice({
|
|
2694
2899
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
@@ -2737,7 +2942,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2737
2942
|
});
|
|
2738
2943
|
});
|
|
2739
2944
|
|
|
2740
|
-
test('
|
|
2945
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
2741
2946
|
const method = new FirmwareUpdateV4({
|
|
2742
2947
|
id: 1,
|
|
2743
2948
|
payload: {
|
|
@@ -2750,6 +2955,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2750
2955
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
2751
2956
|
features: { capabilities: [] },
|
|
2752
2957
|
});
|
|
2958
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
2753
2959
|
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
2754
2960
|
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
2755
2961
|
{
|
|
@@ -2771,7 +2977,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2771
2977
|
|
|
2772
2978
|
await method.run();
|
|
2773
2979
|
|
|
2774
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
2980
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
2775
2981
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
2776
2982
|
fwBinaryMap: [
|
|
2777
2983
|
{
|
|
@@ -2815,6 +3021,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2815
3021
|
});
|
|
2816
3022
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2817
3023
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
3024
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
2818
3025
|
method.postTipMessage = jest.fn();
|
|
2819
3026
|
|
|
2820
3027
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
@@ -2880,7 +3087,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2880
3087
|
});
|
|
2881
3088
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2882
3089
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
2883
|
-
(method as any).
|
|
3090
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
2884
3091
|
|
|
2885
3092
|
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
2886
3093
|
|
|
@@ -2999,7 +3206,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2999
3206
|
fw: { application: { version: '1.0.0' } },
|
|
3000
3207
|
},
|
|
3001
3208
|
});
|
|
3002
|
-
(method as any).
|
|
3209
|
+
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
3003
3210
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3004
3211
|
(method as any).device = stubDevice({
|
|
3005
3212
|
getCommands: () => ({ typedCall }),
|
|
@@ -3025,6 +3232,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3025
3232
|
});
|
|
3026
3233
|
method.postTipMessage = jest.fn();
|
|
3027
3234
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3235
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3028
3236
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
3029
3237
|
message: 'Device rebooted successfully',
|
|
3030
3238
|
});
|
|
@@ -3088,9 +3296,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3088
3296
|
const typedCall = jest
|
|
3089
3297
|
.fn()
|
|
3090
3298
|
.mockRejectedValue(
|
|
3091
|
-
|
|
3092
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
3093
|
-
'NotFoundError'
|
|
3299
|
+
Object.assign(
|
|
3300
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
3301
|
+
{ name: 'NotFoundError' }
|
|
3094
3302
|
)
|
|
3095
3303
|
);
|
|
3096
3304
|
|
|
@@ -3161,6 +3369,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3161
3369
|
getCommands: () => ({ typedCall }),
|
|
3162
3370
|
});
|
|
3163
3371
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3372
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3164
3373
|
method.postProgressMessage = jest.fn();
|
|
3165
3374
|
|
|
3166
3375
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
@@ -3177,7 +3386,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3177
3386
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
3178
3387
|
});
|
|
3179
3388
|
|
|
3180
|
-
test('
|
|
3389
|
+
test('does not treat a missing firmware status handler as install completion', async () => {
|
|
3181
3390
|
const method = new FirmwareUpdateV4({
|
|
3182
3391
|
id: 1,
|
|
3183
3392
|
payload: {
|
|
@@ -3193,14 +3402,65 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3193
3402
|
getCommands: () => ({ typedCall }),
|
|
3194
3403
|
});
|
|
3195
3404
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3405
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3196
3406
|
method.postProgressMessage = jest.fn();
|
|
3197
3407
|
|
|
3198
|
-
await (
|
|
3199
|
-
|
|
3200
|
-
|
|
3408
|
+
await expect(
|
|
3409
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3410
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3411
|
+
])
|
|
3412
|
+
).rejects.toThrow('Protocol V2 firmware install status is unavailable');
|
|
3201
3413
|
|
|
3202
3414
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3203
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3415
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3416
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3417
|
+
'DeviceInfoGet',
|
|
3418
|
+
]);
|
|
3419
|
+
});
|
|
3420
|
+
|
|
3421
|
+
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
3422
|
+
const method = new FirmwareUpdateV4({
|
|
3423
|
+
id: 1,
|
|
3424
|
+
payload: {
|
|
3425
|
+
method: 'firmwareUpdateV4',
|
|
3426
|
+
platform: 'desktop',
|
|
3427
|
+
expectedTargetVersions: {
|
|
3428
|
+
app_v1: '2.0.0',
|
|
3429
|
+
},
|
|
3430
|
+
},
|
|
3431
|
+
});
|
|
3432
|
+
method.init();
|
|
3433
|
+
method.postProgressMessage = jest.fn();
|
|
3434
|
+
|
|
3435
|
+
expect(() =>
|
|
3436
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
3437
|
+
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
3438
|
+
new Set([4])
|
|
3439
|
+
)
|
|
3440
|
+
).toThrow('expected 131072');
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
test('does not fail a completed multi-app install when final app versions are unobservable', () => {
|
|
3444
|
+
const method = new FirmwareUpdateV4({
|
|
3445
|
+
id: 1,
|
|
3446
|
+
payload: {
|
|
3447
|
+
method: 'firmwareUpdateV4',
|
|
3448
|
+
platform: 'desktop',
|
|
3449
|
+
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
3450
|
+
expectedTargetVersions: {
|
|
3451
|
+
app_v1: '1.0.0',
|
|
3452
|
+
app_v2: '2.0.0',
|
|
3453
|
+
},
|
|
3454
|
+
},
|
|
3455
|
+
});
|
|
3456
|
+
method.init();
|
|
3457
|
+
(method as any).protocolV2LatestFinalFeatures = {
|
|
3458
|
+
major_version: 3,
|
|
3459
|
+
minor_version: 0,
|
|
3460
|
+
patch_version: 0,
|
|
3461
|
+
};
|
|
3462
|
+
|
|
3463
|
+
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
3204
3464
|
});
|
|
3205
3465
|
|
|
3206
3466
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
@@ -3225,6 +3485,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3225
3485
|
getCommands: () => ({ typedCall }),
|
|
3226
3486
|
});
|
|
3227
3487
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3488
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3228
3489
|
method.postProgressMessage = jest.fn();
|
|
3229
3490
|
|
|
3230
3491
|
await expect(
|
|
@@ -3342,6 +3603,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3342
3603
|
getCommands: () => ({ typedCall }),
|
|
3343
3604
|
});
|
|
3344
3605
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3606
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3345
3607
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
3346
3608
|
|
|
3347
3609
|
await expect(
|
|
@@ -3377,10 +3639,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3377
3639
|
const writtenPaths: string[] = [];
|
|
3378
3640
|
method.postTipMessage = jest.fn();
|
|
3379
3641
|
method.postProgressMessage = jest.fn();
|
|
3380
|
-
(method as any).
|
|
3642
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
3381
3643
|
writtenPaths.push(params.filePath);
|
|
3382
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
3644
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
3383
3645
|
});
|
|
3646
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3647
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3648
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
3649
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3384
3650
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3385
3651
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3386
3652
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3420,10 +3686,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3420
3686
|
'vol0:/coprocessor.bin',
|
|
3421
3687
|
1
|
|
3422
3688
|
);
|
|
3423
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
3424
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
3689
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(2);
|
|
3690
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
3691
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
3692
|
+
});
|
|
3693
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
3425
3694
|
targets: [
|
|
3426
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
3427
3695
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
3428
3696
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
3429
3697
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -3433,7 +3701,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3433
3701
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
3434
3702
|
});
|
|
3435
3703
|
|
|
3436
|
-
test('announces
|
|
3704
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
3437
3705
|
const method = new FirmwareUpdateV4({
|
|
3438
3706
|
id: 1,
|
|
3439
3707
|
payload: {
|
|
@@ -3443,11 +3711,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3443
3711
|
|
|
3444
3712
|
method.postTipMessage = jest.fn();
|
|
3445
3713
|
method.postProgressMessage = jest.fn();
|
|
3446
|
-
(method as any).
|
|
3714
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
3447
3715
|
.fn()
|
|
3448
3716
|
.mockImplementation(params =>
|
|
3449
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3717
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
3450
3718
|
);
|
|
3719
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3720
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3721
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3451
3722
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3452
3723
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3453
3724
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3467,21 +3738,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3467
3738
|
{
|
|
3468
3739
|
fileName: 'application_p1.bin',
|
|
3469
3740
|
binary: new Uint8Array([3]).buffer,
|
|
3470
|
-
targetId:
|
|
3741
|
+
targetId: 4,
|
|
3471
3742
|
},
|
|
3472
3743
|
],
|
|
3473
3744
|
});
|
|
3474
3745
|
|
|
3475
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
3746
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
3476
3747
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
3477
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
3478
|
-
expect(
|
|
3748
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
3749
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
3750
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
3479
3751
|
1,
|
|
3480
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
3752
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
3481
3753
|
);
|
|
3482
|
-
expect((method as any).
|
|
3754
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
3483
3755
|
2,
|
|
3484
|
-
expect.objectContaining({ processedSize:
|
|
3756
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
3485
3757
|
);
|
|
3486
3758
|
});
|
|
3487
3759
|
|
|
@@ -3495,7 +3767,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3495
3767
|
|
|
3496
3768
|
method.postTipMessage = jest.fn();
|
|
3497
3769
|
method.postProgressMessage = jest.fn();
|
|
3498
|
-
(method as any).
|
|
3770
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3771
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3499
3772
|
(method as any).device = stubDevice({
|
|
3500
3773
|
getCommands: () => ({
|
|
3501
3774
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -3541,11 +3814,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3541
3814
|
|
|
3542
3815
|
method.postTipMessage = jest.fn();
|
|
3543
3816
|
method.postProgressMessage = jest.fn();
|
|
3544
|
-
(method as any).
|
|
3817
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
3545
3818
|
.fn()
|
|
3546
3819
|
.mockImplementation(params =>
|
|
3547
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3820
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
3548
3821
|
);
|
|
3822
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3823
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3824
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3549
3825
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3550
3826
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3551
3827
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3725,7 +4001,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3725
4001
|
{
|
|
3726
4002
|
name: 'images',
|
|
3727
4003
|
url: 'https://example.com/images.okpkg',
|
|
3728
|
-
devicePath: '
|
|
4004
|
+
devicePath: 'VOL0:/resource/images/images.okpkg',
|
|
3729
4005
|
version: [2, 0, 0],
|
|
3730
4006
|
payloadHash: 'ab'.repeat(64),
|
|
3731
4007
|
headerHash: 'cd'.repeat(64),
|
|
@@ -3760,6 +4036,110 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3760
4036
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3761
4037
|
});
|
|
3762
4038
|
|
|
4039
|
+
test('rejects remote RESC bundle paths before download or bootloader entry', async () => {
|
|
4040
|
+
const method = new FirmwareUpdateV4({
|
|
4041
|
+
id: 1,
|
|
4042
|
+
payload: {
|
|
4043
|
+
method: 'firmwareUpdateV4',
|
|
4044
|
+
platform: 'web',
|
|
4045
|
+
targetsToUpdate: ['resource'],
|
|
4046
|
+
},
|
|
4047
|
+
});
|
|
4048
|
+
method.init();
|
|
4049
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4050
|
+
|
|
4051
|
+
const getSysResourceBinarySpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
4052
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
4053
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4054
|
+
.mockReturnValue({
|
|
4055
|
+
required: false,
|
|
4056
|
+
version: [1, 0, 0],
|
|
4057
|
+
url: '',
|
|
4058
|
+
resourceBundles: [
|
|
4059
|
+
{
|
|
4060
|
+
name: 'images',
|
|
4061
|
+
url: 'https://example.com/images.okpkg',
|
|
4062
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
4063
|
+
},
|
|
4064
|
+
],
|
|
4065
|
+
fingerprint: '',
|
|
4066
|
+
changelog: {
|
|
4067
|
+
'zh-CN': '',
|
|
4068
|
+
'en-US': '',
|
|
4069
|
+
},
|
|
4070
|
+
});
|
|
4071
|
+
|
|
4072
|
+
(method as any).device = stubDevice({
|
|
4073
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
4074
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
4075
|
+
});
|
|
4076
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4077
|
+
bootloaderBinary: null,
|
|
4078
|
+
fwBinaryMap: [],
|
|
4079
|
+
});
|
|
4080
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
4081
|
+
method.postTipMessage = jest.fn();
|
|
4082
|
+
|
|
4083
|
+
await expect(method.run()).rejects.toThrow('resourceBundles[0].devicePath');
|
|
4084
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
4085
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
4086
|
+
|
|
4087
|
+
getSysResourceBinarySpy.mockRestore();
|
|
4088
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4089
|
+
});
|
|
4090
|
+
|
|
4091
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
4092
|
+
const method = new FirmwareUpdateV4({
|
|
4093
|
+
id: 1,
|
|
4094
|
+
payload: {
|
|
4095
|
+
method: 'firmwareUpdateV4',
|
|
4096
|
+
},
|
|
4097
|
+
});
|
|
4098
|
+
const openPreparedSource = jest.fn();
|
|
4099
|
+
(method as any).params = {
|
|
4100
|
+
targetsToUpdate: ['resource'],
|
|
4101
|
+
resourceBundleArtifacts: [
|
|
4102
|
+
{
|
|
4103
|
+
name: 'images',
|
|
4104
|
+
artifact: {
|
|
4105
|
+
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
4106
|
+
size: 1,
|
|
4107
|
+
sha256: 'a'.repeat(64),
|
|
4108
|
+
},
|
|
4109
|
+
},
|
|
4110
|
+
],
|
|
4111
|
+
};
|
|
4112
|
+
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
4113
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
4114
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
4115
|
+
.mockReturnValue({
|
|
4116
|
+
required: false,
|
|
4117
|
+
version: [1, 0, 0],
|
|
4118
|
+
url: '',
|
|
4119
|
+
resourceBundles: [
|
|
4120
|
+
{
|
|
4121
|
+
name: 'images',
|
|
4122
|
+
url: 'https://example.com/images.okpkg',
|
|
4123
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
4124
|
+
},
|
|
4125
|
+
],
|
|
4126
|
+
fingerprint: '',
|
|
4127
|
+
changelog: {
|
|
4128
|
+
'zh-CN': '',
|
|
4129
|
+
'en-US': '',
|
|
4130
|
+
},
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
await expect(
|
|
4134
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
4135
|
+
deviceType: 'pro2',
|
|
4136
|
+
})
|
|
4137
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
4138
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
4139
|
+
|
|
4140
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4141
|
+
});
|
|
4142
|
+
|
|
3763
4143
|
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
3764
4144
|
const method = new FirmwareUpdateV4({
|
|
3765
4145
|
id: 1,
|
|
@@ -3770,6 +4150,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3770
4150
|
},
|
|
3771
4151
|
});
|
|
3772
4152
|
method.init();
|
|
4153
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3773
4154
|
|
|
3774
4155
|
const order: string[] = [];
|
|
3775
4156
|
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
@@ -3789,7 +4170,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3789
4170
|
{
|
|
3790
4171
|
name: 'images',
|
|
3791
4172
|
url: 'https://example.com/images.okpkg',
|
|
3792
|
-
devicePath: '
|
|
4173
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
3793
4174
|
},
|
|
3794
4175
|
],
|
|
3795
4176
|
fingerprint: '',
|
|
@@ -3811,7 +4192,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3811
4192
|
order.push('bootloader');
|
|
3812
4193
|
return Promise.resolve(true);
|
|
3813
4194
|
});
|
|
3814
|
-
(method as any
|
|
4195
|
+
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
4196
|
+
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
4197
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
3815
4198
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3816
4199
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3817
4200
|
bootloaderVersion: '1.0.0',
|
|
@@ -3823,7 +4206,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3823
4206
|
await method.run();
|
|
3824
4207
|
|
|
3825
4208
|
expect(order).toEqual(['download', 'bootloader']);
|
|
3826
|
-
expect(
|
|
4209
|
+
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
3827
4210
|
bootloaderBinary: null,
|
|
3828
4211
|
fwBinaryMap: [],
|
|
3829
4212
|
resourceBundles: [
|
|
@@ -3836,6 +4219,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3836
4219
|
],
|
|
3837
4220
|
});
|
|
3838
4221
|
|
|
4222
|
+
executeProtocolV2UpdateSpy.mockRestore();
|
|
3839
4223
|
getSysResourceBinarySpy.mockRestore();
|
|
3840
4224
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3841
4225
|
});
|
|
@@ -3850,6 +4234,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3850
4234
|
},
|
|
3851
4235
|
});
|
|
3852
4236
|
method.init();
|
|
4237
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3853
4238
|
|
|
3854
4239
|
const getSysResourceBinarySpy = jest
|
|
3855
4240
|
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
@@ -3961,6 +4346,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3961
4346
|
},
|
|
3962
4347
|
});
|
|
3963
4348
|
method.init();
|
|
4349
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3964
4350
|
|
|
3965
4351
|
const remoteBinaries = {
|
|
3966
4352
|
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
@@ -4007,6 +4393,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4007
4393
|
},
|
|
4008
4394
|
});
|
|
4009
4395
|
method.init();
|
|
4396
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4010
4397
|
|
|
4011
4398
|
(method as any).device = stubDevice({
|
|
4012
4399
|
originalDescriptor: { protocolType: 'V2' },
|
|
@@ -4042,12 +4429,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4042
4429
|
resourceBundleFiles: [
|
|
4043
4430
|
{
|
|
4044
4431
|
binary: resourceBundle,
|
|
4045
|
-
devicePath: '
|
|
4432
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
4046
4433
|
},
|
|
4047
4434
|
],
|
|
4048
4435
|
},
|
|
4049
4436
|
});
|
|
4050
4437
|
method.init();
|
|
4438
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4051
4439
|
|
|
4052
4440
|
(method as any).device = stubDevice({
|
|
4053
4441
|
originalDescriptor: { protocolType: 'V2' },
|
|
@@ -4080,6 +4468,36 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4080
4468
|
);
|
|
4081
4469
|
});
|
|
4082
4470
|
|
|
4471
|
+
test('rejects manual RESC bundle paths before bootloader entry', async () => {
|
|
4472
|
+
const method = new FirmwareUpdateV4({
|
|
4473
|
+
id: 1,
|
|
4474
|
+
payload: {
|
|
4475
|
+
method: 'firmwareUpdateV4',
|
|
4476
|
+
platform: 'web',
|
|
4477
|
+
resourceBundleFiles: [
|
|
4478
|
+
{
|
|
4479
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
4480
|
+
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
4481
|
+
},
|
|
4482
|
+
],
|
|
4483
|
+
},
|
|
4484
|
+
});
|
|
4485
|
+
method.init();
|
|
4486
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4487
|
+
|
|
4488
|
+
(method as any).device = stubDevice({
|
|
4489
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
4490
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
4491
|
+
});
|
|
4492
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
4493
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
4494
|
+
method.postTipMessage = jest.fn();
|
|
4495
|
+
|
|
4496
|
+
await expect(method.run()).rejects.toThrow('resourceBundleFiles[0].devicePath');
|
|
4497
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
4498
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
4499
|
+
});
|
|
4500
|
+
|
|
4083
4501
|
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
4084
4502
|
const method = new FirmwareUpdateV4({
|
|
4085
4503
|
id: 1,
|
|
@@ -4090,7 +4508,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4090
4508
|
|
|
4091
4509
|
method.postTipMessage = jest.fn();
|
|
4092
4510
|
method.postProgressMessage = jest.fn();
|
|
4093
|
-
(method as any).
|
|
4511
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4512
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4513
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4514
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4094
4515
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
4095
4516
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
4096
4517
|
|
|
@@ -4106,7 +4527,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4106
4527
|
fwBinaryMap: [],
|
|
4107
4528
|
});
|
|
4108
4529
|
|
|
4109
|
-
expect((method as any).
|
|
4530
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
4110
4531
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
4111
4532
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
4112
4533
|
});
|
|
@@ -4137,12 +4558,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4137
4558
|
});
|
|
4138
4559
|
method.postProgressMessage = jest.fn();
|
|
4139
4560
|
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
filePath: 'vol1:firmware.bin',
|
|
4143
|
-
processedSize: 0,
|
|
4144
|
-
totalSize: 4097,
|
|
4561
|
+
const source = await openFirmwareByteSource({
|
|
4562
|
+
binary: new Uint8Array(4097).buffer,
|
|
4145
4563
|
});
|
|
4564
|
+
try {
|
|
4565
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4566
|
+
source,
|
|
4567
|
+
filePath: 'vol1:firmware.bin',
|
|
4568
|
+
processedSize: 0,
|
|
4569
|
+
totalSize: 4097,
|
|
4570
|
+
});
|
|
4571
|
+
} finally {
|
|
4572
|
+
await source?.close();
|
|
4573
|
+
}
|
|
4146
4574
|
|
|
4147
4575
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4148
4576
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -4193,12 +4621,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4193
4621
|
method.postProgressMessage = jest.fn();
|
|
4194
4622
|
|
|
4195
4623
|
try {
|
|
4196
|
-
|
|
4197
|
-
|
|
4624
|
+
const source = await openFirmwareByteSource({
|
|
4625
|
+
binary: new Uint8Array(8001).buffer,
|
|
4626
|
+
});
|
|
4627
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4628
|
+
source,
|
|
4198
4629
|
filePath: 'vol0:/firmware.bin',
|
|
4199
4630
|
processedSize: 0,
|
|
4200
4631
|
totalSize: 8001,
|
|
4201
4632
|
});
|
|
4633
|
+
await source?.close();
|
|
4202
4634
|
} finally {
|
|
4203
4635
|
setTimeoutSpy.mockRestore();
|
|
4204
4636
|
}
|
|
@@ -4230,16 +4662,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4230
4662
|
});
|
|
4231
4663
|
method.postProgressMessage = jest.fn();
|
|
4232
4664
|
method.postTipMessage = jest.fn();
|
|
4665
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
4233
4666
|
|
|
4667
|
+
const source = await openFirmwareByteSource({
|
|
4668
|
+
binary: new Uint8Array(4097).buffer,
|
|
4669
|
+
});
|
|
4234
4670
|
await expect(
|
|
4235
|
-
(method as any).
|
|
4236
|
-
|
|
4671
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
4672
|
+
source,
|
|
4237
4673
|
filePath: 'vol0:/firmware.bin',
|
|
4238
4674
|
processedSize: 0,
|
|
4239
4675
|
totalSize: 4097,
|
|
4240
4676
|
})
|
|
4241
4677
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
4242
|
-
|
|
4678
|
+
await source?.close();
|
|
4679
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
4243
4680
|
});
|
|
4244
4681
|
|
|
4245
4682
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -4272,12 +4709,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4272
4709
|
});
|
|
4273
4710
|
method.postProgressMessage = jest.fn();
|
|
4274
4711
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
4278
|
-
processedSize: 0,
|
|
4279
|
-
totalSize: 1801,
|
|
4712
|
+
const source = await openFirmwareByteSource({
|
|
4713
|
+
binary: new Uint8Array(1801).buffer,
|
|
4280
4714
|
});
|
|
4715
|
+
try {
|
|
4716
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4717
|
+
source,
|
|
4718
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
4719
|
+
processedSize: 0,
|
|
4720
|
+
totalSize: 1801,
|
|
4721
|
+
});
|
|
4722
|
+
} finally {
|
|
4723
|
+
await source?.close();
|
|
4724
|
+
}
|
|
4281
4725
|
|
|
4282
4726
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4283
4727
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -4394,9 +4838,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4394
4838
|
const typedCall = jest
|
|
4395
4839
|
.fn()
|
|
4396
4840
|
.mockRejectedValue(
|
|
4397
|
-
|
|
4398
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4399
|
-
'NotFoundError'
|
|
4841
|
+
Object.assign(
|
|
4842
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4843
|
+
{ name: 'NotFoundError' }
|
|
4400
4844
|
)
|
|
4401
4845
|
);
|
|
4402
4846
|
|
|
@@ -4561,29 +5005,64 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4561
5005
|
});
|
|
4562
5006
|
|
|
4563
5007
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
4564
|
-
test('
|
|
4565
|
-
|
|
5008
|
+
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
5009
|
+
const method = new FirmwareUpdateV4({
|
|
5010
|
+
id: 1,
|
|
5011
|
+
payload: {
|
|
5012
|
+
method: 'firmwareUpdateV4',
|
|
5013
|
+
platform: 'native',
|
|
5014
|
+
expectedDeviceId: 'persisted-device-id',
|
|
5015
|
+
},
|
|
5016
|
+
});
|
|
5017
|
+
|
|
5018
|
+
method.init();
|
|
5019
|
+
|
|
5020
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
5021
|
+
});
|
|
5022
|
+
|
|
5023
|
+
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
5024
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
4566
5025
|
'identity mismatch'
|
|
4567
5026
|
);
|
|
4568
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-device', undefined)).toThrow(
|
|
4569
|
-
'identity unavailable'
|
|
4570
|
-
);
|
|
4571
5027
|
expect(() =>
|
|
4572
|
-
assertProtocolV2ReconnectIdentity('expected-
|
|
5028
|
+
assertProtocolV2ReconnectIdentity('expected-serial', 'expected-serial')
|
|
4573
5029
|
).not.toThrow();
|
|
4574
5030
|
});
|
|
4575
5031
|
|
|
4576
|
-
test('allows
|
|
4577
|
-
expect(() =>
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
5032
|
+
test('allows firmware update when the optional physical serial is unavailable', () => {
|
|
5033
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).not.toThrow();
|
|
5034
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).not.toThrow();
|
|
5035
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).not.toThrow();
|
|
5036
|
+
});
|
|
5037
|
+
|
|
5038
|
+
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
5039
|
+
const method = new FirmwareUpdateV4({
|
|
5040
|
+
id: 1,
|
|
5041
|
+
payload: {
|
|
5042
|
+
method: 'firmwareUpdateV4',
|
|
5043
|
+
},
|
|
5044
|
+
});
|
|
5045
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
5046
|
+
type: 'DeviceInfo',
|
|
5047
|
+
message: {
|
|
5048
|
+
protocol_version: 1,
|
|
5049
|
+
hw: { serial_no: 'PRO2-PHYSICAL-1' },
|
|
5050
|
+
},
|
|
5051
|
+
});
|
|
5052
|
+
(method as any).device = stubDevice({
|
|
5053
|
+
features: { deviceId: 'wallet-derived-id' },
|
|
5054
|
+
getCommands: () => ({ typedCall }),
|
|
5055
|
+
});
|
|
5056
|
+
|
|
5057
|
+
await (method as any).captureProtocolV2PhysicalIdentity();
|
|
5058
|
+
|
|
5059
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBe('PRO2-PHYSICAL-1');
|
|
5060
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5061
|
+
'DeviceInfoGet',
|
|
5062
|
+
'DeviceInfo',
|
|
5063
|
+
expect.objectContaining({ targets: expect.objectContaining({ hw: true }) }),
|
|
5064
|
+
{ timeoutMs: 5000 }
|
|
5065
|
+
);
|
|
4587
5066
|
});
|
|
4588
5067
|
});
|
|
4589
5068
|
|
|
@@ -4661,7 +5140,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4661
5140
|
errorCode: HardwareErrorCode.DeviceLocked,
|
|
4662
5141
|
});
|
|
4663
5142
|
|
|
4664
|
-
test('
|
|
5143
|
+
test('does not replay SDK methods unless they are explicitly classified as retry-safe', () => {
|
|
4665
5144
|
class TestBusinessMethod extends BaseMethod {
|
|
4666
5145
|
init() {}
|
|
4667
5146
|
|
|
@@ -4675,7 +5154,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4675
5154
|
payload: { method: 'testBusinessMethod' },
|
|
4676
5155
|
} as any);
|
|
4677
5156
|
|
|
4678
|
-
expect(method.unlockPolicy).toBe('
|
|
5157
|
+
expect(method.unlockPolicy).toBe('none');
|
|
4679
5158
|
});
|
|
4680
5159
|
|
|
4681
5160
|
test.each([
|
|
@@ -4770,7 +5249,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4770
5249
|
const calls: string[] = [];
|
|
4771
5250
|
const method = {
|
|
4772
5251
|
name: 'deviceSettingsPageShow',
|
|
4773
|
-
unlockPolicy: '
|
|
5252
|
+
unlockPolicy: 'unlock-before-run',
|
|
4774
5253
|
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
4775
5254
|
run: jest.fn(() => {
|
|
4776
5255
|
calls.push('run');
|
|
@@ -4854,14 +5333,12 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4854
5333
|
test('keeps auto unlock but suppresses all synthesized UI for eventless methods', async () => {
|
|
4855
5334
|
const method = {
|
|
4856
5335
|
name: 'uploadPortfolio',
|
|
4857
|
-
unlockPolicy: '
|
|
5336
|
+
unlockPolicy: 'unlock-before-run',
|
|
4858
5337
|
protocolV2UiMode: 'none',
|
|
4859
|
-
run: jest
|
|
4860
|
-
.fn()
|
|
4861
|
-
.mockRejectedValueOnce(deviceLockedError())
|
|
4862
|
-
.mockResolvedValueOnce({ message: 'ok' }),
|
|
5338
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
4863
5339
|
};
|
|
4864
5340
|
const device = {
|
|
5341
|
+
features: { unlocked: false },
|
|
4865
5342
|
isProtocolV2: () => true,
|
|
4866
5343
|
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
4867
5344
|
};
|
|
@@ -4880,6 +5357,24 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4880
5357
|
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
4881
5358
|
});
|
|
4882
5359
|
|
|
5360
|
+
test('does not replay a state-changing method after a locked response', async () => {
|
|
5361
|
+
const error = deviceLockedError();
|
|
5362
|
+
const method = {
|
|
5363
|
+
name: 'uploadPortfolio',
|
|
5364
|
+
unlockPolicy: 'unlock-before-run',
|
|
5365
|
+
run: jest.fn().mockRejectedValue(error),
|
|
5366
|
+
};
|
|
5367
|
+
const device = {
|
|
5368
|
+
features: { unlocked: true },
|
|
5369
|
+
isProtocolV2: () => true,
|
|
5370
|
+
unlockDevice: jest.fn(),
|
|
5371
|
+
};
|
|
5372
|
+
|
|
5373
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
5374
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
5375
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
5376
|
+
});
|
|
5377
|
+
|
|
4883
5378
|
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
4884
5379
|
const initialError = deviceLockedError();
|
|
4885
5380
|
const unlockError = new Error('PIN cancelled');
|
|
@@ -4940,29 +5435,35 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4940
5435
|
describe('Protocol V2 current low-level methods', () => {
|
|
4941
5436
|
test('routes device wipe to the Pro2 reset page until the operation completes', async () => {
|
|
4942
5437
|
const v1TypedCall = jest.fn().mockResolvedValue({ message: { message: 'wiped' } });
|
|
5438
|
+
const v1InvalidateAfterWipe = jest.fn();
|
|
4943
5439
|
const v1Method = new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } });
|
|
4944
5440
|
v1Method.init();
|
|
4945
5441
|
(v1Method as any).device = stubDevice({
|
|
4946
5442
|
isProtocolV2: () => false,
|
|
4947
5443
|
commands: { typedCall: v1TypedCall },
|
|
5444
|
+
invalidateAfterWipe: v1InvalidateAfterWipe,
|
|
4948
5445
|
});
|
|
4949
5446
|
|
|
4950
5447
|
await v1Method.run();
|
|
4951
5448
|
expect(v1TypedCall).toHaveBeenCalledWith('WipeDevice', 'Success');
|
|
5449
|
+
expect(v1InvalidateAfterWipe).toHaveBeenCalledTimes(1);
|
|
4952
5450
|
|
|
4953
5451
|
const v2TypedCall = jest.fn().mockResolvedValue({ message: { message: 'accepted' } });
|
|
5452
|
+
const v2InvalidateAfterWipe = jest.fn();
|
|
4954
5453
|
const v2Method = new DeviceWipe({ id: 2, payload: { method: 'deviceWipe' } });
|
|
4955
5454
|
v2Method.init();
|
|
4956
5455
|
(v2Method as any).device = stubDevice({
|
|
4957
5456
|
isProtocolV2: () => true,
|
|
4958
5457
|
commands: { typedCall: v2TypedCall },
|
|
5458
|
+
invalidateAfterWipe: v2InvalidateAfterWipe,
|
|
4959
5459
|
});
|
|
4960
5460
|
|
|
4961
5461
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
4962
|
-
expect(v2Method.unlockPolicy).toBe('
|
|
5462
|
+
expect(v2Method.unlockPolicy).toBe('unlock-before-run');
|
|
4963
5463
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4964
5464
|
page: DeviceSettingsPage.DeviceReset,
|
|
4965
5465
|
});
|
|
5466
|
+
expect(v2InvalidateAfterWipe).toHaveBeenCalledTimes(1);
|
|
4966
5467
|
expect(v2Method.protocolV2UiInteraction).toEqual({
|
|
4967
5468
|
request: 'button',
|
|
4968
5469
|
source: 'method-lifecycle',
|
|
@@ -4974,6 +5475,21 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4974
5475
|
});
|
|
4975
5476
|
});
|
|
4976
5477
|
|
|
5478
|
+
test('keeps the existing identity cache when device wipe fails', async () => {
|
|
5479
|
+
const wipeError = new Error('wipe failed');
|
|
5480
|
+
const invalidateAfterWipe = jest.fn();
|
|
5481
|
+
const method = new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } });
|
|
5482
|
+
method.init();
|
|
5483
|
+
(method as any).device = stubDevice({
|
|
5484
|
+
isProtocolV2: () => true,
|
|
5485
|
+
commands: { typedCall: jest.fn().mockRejectedValue(wipeError) },
|
|
5486
|
+
invalidateAfterWipe,
|
|
5487
|
+
});
|
|
5488
|
+
|
|
5489
|
+
await expect(method.run()).rejects.toBe(wipeError);
|
|
5490
|
+
expect(invalidateAfterWipe).not.toHaveBeenCalled();
|
|
5491
|
+
});
|
|
5492
|
+
|
|
4977
5493
|
test('routes Change PIN by protocol and waits for the Pro2 operation result', async () => {
|
|
4978
5494
|
const v1TypedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
4979
5495
|
const v1Method = new DeviceChangePin({
|
|
@@ -5002,7 +5518,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5002
5518
|
});
|
|
5003
5519
|
|
|
5004
5520
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
5005
|
-
expect(v2Method.unlockPolicy).toBe('
|
|
5521
|
+
expect(v2Method.unlockPolicy).toBe('unlock-before-run');
|
|
5006
5522
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
5007
5523
|
page: DeviceSettingsPage.DevicePinChange,
|
|
5008
5524
|
});
|
|
@@ -5103,43 +5619,33 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5103
5619
|
});
|
|
5104
5620
|
});
|
|
5105
5621
|
|
|
5106
|
-
test('
|
|
5622
|
+
test('preserves the legacy Features response after explicit device unlock', async () => {
|
|
5107
5623
|
const method = new DeviceUnlock({
|
|
5108
5624
|
id: 1,
|
|
5109
5625
|
payload: { method: 'deviceUnlock' },
|
|
5110
5626
|
});
|
|
5111
5627
|
method.init();
|
|
5112
5628
|
|
|
5113
|
-
const
|
|
5114
|
-
schemaVersion: 1,
|
|
5115
|
-
revision: 2,
|
|
5116
|
-
updatedAt: 1,
|
|
5629
|
+
const features = {
|
|
5117
5630
|
protocol: 'V2',
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
settings: {},
|
|
5122
|
-
versions: {},
|
|
5123
|
-
capabilities: [],
|
|
5631
|
+
unlocked: true,
|
|
5632
|
+
sessionId: null,
|
|
5633
|
+
session_id: null,
|
|
5124
5634
|
};
|
|
5125
|
-
const unlockDevice = jest.fn().mockResolvedValue(
|
|
5126
|
-
|
|
5127
|
-
(method as any).device = { unlockDevice, getDeviceState };
|
|
5635
|
+
const unlockDevice = jest.fn().mockResolvedValue(features);
|
|
5636
|
+
(method as any).device = { unlockDevice };
|
|
5128
5637
|
|
|
5129
|
-
await expect(method.run()).resolves.toBe(
|
|
5638
|
+
await expect(method.run()).resolves.toBe(features);
|
|
5130
5639
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
5131
|
-
expect(getDeviceState).toHaveBeenCalledWith();
|
|
5132
5640
|
});
|
|
5133
5641
|
|
|
5134
5642
|
test.each([
|
|
5135
|
-
['label', { label: 'My Pro 2' }],
|
|
5136
5643
|
['language', { language: 'ja' }],
|
|
5137
5644
|
['brightness', { brightness: 80 }],
|
|
5138
5645
|
['haptic feedback', { hapticFeedback: true }],
|
|
5139
5646
|
[
|
|
5140
5647
|
'combined lock-free settings',
|
|
5141
5648
|
{
|
|
5142
|
-
label: 'My Pro 2',
|
|
5143
5649
|
language: 'ja',
|
|
5144
5650
|
brightness: 80,
|
|
5145
5651
|
hapticFeedback: true,
|
|
@@ -5157,8 +5663,10 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5157
5663
|
});
|
|
5158
5664
|
|
|
5159
5665
|
test.each([
|
|
5666
|
+
['label', { label: 'My Pro 2' }],
|
|
5160
5667
|
['auto lock', { autoLockDelayMs: 60_000 }],
|
|
5161
5668
|
['auto shutdown', { autoShutdownDelayMs: 120_000 }],
|
|
5669
|
+
['label mixed with lock-free settings', { label: 'My Pro 2', brightness: 80 }],
|
|
5162
5670
|
['mixed protected settings', { brightness: 80, autoLockDelayMs: 60_000 }],
|
|
5163
5671
|
['other protected settings', { usbLockEnabled: true }],
|
|
5164
5672
|
])('unlocks before changing unified Protocol V2 %s', (_name, settings) => {
|
|
@@ -5169,7 +5677,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5169
5677
|
|
|
5170
5678
|
method.init();
|
|
5171
5679
|
|
|
5172
|
-
expect(method.unlockPolicy).toBe('
|
|
5680
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5173
5681
|
});
|
|
5174
5682
|
|
|
5175
5683
|
test('unlocks before opening Protocol V2 settings pages through the unified method', () => {
|
|
@@ -5180,7 +5688,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5180
5688
|
|
|
5181
5689
|
method.init();
|
|
5182
5690
|
|
|
5183
|
-
expect(method.unlockPolicy).toBe('
|
|
5691
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5184
5692
|
});
|
|
5185
5693
|
|
|
5186
5694
|
test('keeps firmwareUpdateV4 unlock-before-reboot behavior explicit', () => {
|
|
@@ -5191,7 +5699,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5191
5699
|
|
|
5192
5700
|
method.init();
|
|
5193
5701
|
|
|
5194
|
-
expect(method.unlockPolicy).toBe('
|
|
5702
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5195
5703
|
});
|
|
5196
5704
|
|
|
5197
5705
|
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|