@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.22
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 +87 -2
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-identity.test.ts +24 -2
- package/__tests__/device-settings.test.ts +1 -1
- package/__tests__/device-wallet-session-store.test.ts +111 -7
- 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 +200 -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__/open-wallet-session.test.ts +253 -13
- package/__tests__/pro2Wallpaper.test.ts +7 -18
- package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
- package/__tests__/protocol-v2.test.ts +415 -145
- 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/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/firmware/FirmwareArtifactSource.d.ts +26 -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 +24 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +25 -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/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.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/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +265 -226
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3045 -1550
- 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/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/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 +16 -3
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/FirmwareUpdate.ts +8 -1
- package/src/api/FirmwareUpdateV2.ts +284 -119
- package/src/api/FirmwareUpdateV3.ts +248 -55
- package/src/api/FirmwareUpdateV4.ts +816 -328
- 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 +114 -6
- package/src/api/device/DeviceWipe.ts +1 -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 +378 -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/data/messages/messages-protocol-v2.json +77 -4
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +50 -60
- package/src/device/DeviceCommands.ts +58 -14
- package/src/device/DeviceStateMapper.ts +18 -1
- package/src/device/DeviceStateProjector.ts +7 -1
- package/src/events/logBlockEvent.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/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -2
- 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 +68 -12
- 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/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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceRebootType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
@@ -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,8 +897,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
875
897
|
passphraseState: 'hidden-state',
|
|
876
898
|
});
|
|
877
899
|
|
|
878
|
-
expect(promptPassphrase).
|
|
879
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
900
|
+
expect(promptPassphrase).toHaveBeenCalled();
|
|
901
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
|
|
902
|
+
select: { host_passphrase: { passphrase: 'hidden secret' } },
|
|
903
|
+
});
|
|
880
904
|
});
|
|
881
905
|
|
|
882
906
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -980,8 +1004,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
980
1004
|
newSession: 'session-b',
|
|
981
1005
|
});
|
|
982
1006
|
|
|
983
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
984
|
-
session_id: 'session-a',
|
|
1007
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
|
|
1008
|
+
resume: { session_id: 'session-a' },
|
|
985
1009
|
});
|
|
986
1010
|
expect(device.getInternalState()).toBe('session-b');
|
|
987
1011
|
});
|
|
@@ -999,13 +1023,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
999
1023
|
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
1000
1024
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1001
1025
|
type: 'DeviceSession',
|
|
1002
|
-
message: {
|
|
1026
|
+
message: {
|
|
1027
|
+
session_id: 'main-session',
|
|
1028
|
+
btc_test_address: 'main-wallet-state',
|
|
1029
|
+
},
|
|
1003
1030
|
});
|
|
1004
1031
|
(device as any).commands = { typedCall };
|
|
1005
1032
|
|
|
1006
1033
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
1007
1034
|
|
|
1008
|
-
expect(typedCall).
|
|
1035
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1036
|
+
eventless_wallet_session: true,
|
|
1037
|
+
});
|
|
1009
1038
|
expect(device.getInternalState()).toBeUndefined();
|
|
1010
1039
|
});
|
|
1011
1040
|
|
|
@@ -1027,11 +1056,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1027
1056
|
);
|
|
1028
1057
|
device.passphraseState = 'state-a';
|
|
1029
1058
|
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
1030
|
-
const typedCall = jest
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1059
|
+
const typedCall = jest
|
|
1060
|
+
.fn()
|
|
1061
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1062
|
+
.mockRejectedValue(
|
|
1063
|
+
Object.assign(new Error('SE request failed'), {
|
|
1064
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1065
|
+
})
|
|
1066
|
+
);
|
|
1035
1067
|
const promptPassphrase = jest.fn();
|
|
1036
1068
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
1037
1069
|
|
|
@@ -1039,7 +1071,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1039
1071
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
1040
1072
|
});
|
|
1041
1073
|
expect(typedCall.mock.calls).toEqual([
|
|
1042
|
-
['
|
|
1074
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1075
|
+
['DeviceSessionOpen', 'DeviceSession', { resume: { session_id: 'session-a' } }],
|
|
1043
1076
|
]);
|
|
1044
1077
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1045
1078
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1061,11 +1094,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1061
1094
|
},
|
|
1062
1095
|
}
|
|
1063
1096
|
);
|
|
1064
|
-
const typedCall = jest
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1097
|
+
const typedCall = jest
|
|
1098
|
+
.fn()
|
|
1099
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1100
|
+
.mockRejectedValue(
|
|
1101
|
+
Object.assign(new Error('Invalid session'), {
|
|
1102
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1103
|
+
})
|
|
1104
|
+
);
|
|
1069
1105
|
(device as any).commands = {
|
|
1070
1106
|
typedCall,
|
|
1071
1107
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'new-secret' }),
|
|
@@ -1074,7 +1110,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1074
1110
|
await expect(getProtocolV2WalletSession(device)).rejects.toMatchObject({
|
|
1075
1111
|
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1076
1112
|
});
|
|
1077
|
-
expect(typedCall.mock.calls).toEqual([
|
|
1113
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1114
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1115
|
+
[
|
|
1116
|
+
'DeviceSessionOpen',
|
|
1117
|
+
'DeviceSession',
|
|
1118
|
+
{ select: { host_passphrase: { passphrase: 'new-secret' } } },
|
|
1119
|
+
],
|
|
1120
|
+
]);
|
|
1078
1121
|
});
|
|
1079
1122
|
|
|
1080
1123
|
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
@@ -1112,7 +1155,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1112
1155
|
expect(device.getInternalState()).toBeUndefined();
|
|
1113
1156
|
});
|
|
1114
1157
|
|
|
1115
|
-
test('unlocks and retries
|
|
1158
|
+
test('unlocks and retries DeviceSessionOpen when wallet selection is locked', async () => {
|
|
1116
1159
|
const device = Device.fromDescriptor({
|
|
1117
1160
|
id: 'cache-device-4',
|
|
1118
1161
|
path: 'cache-path-4',
|
|
@@ -1127,6 +1170,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1127
1170
|
});
|
|
1128
1171
|
const typedCall = jest
|
|
1129
1172
|
.fn()
|
|
1173
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1130
1174
|
.mockRejectedValueOnce(lockedError)
|
|
1131
1175
|
.mockResolvedValueOnce({
|
|
1132
1176
|
message: {
|
|
@@ -1145,7 +1189,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1145
1189
|
passphraseState: 'state-after-unlock',
|
|
1146
1190
|
});
|
|
1147
1191
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
1148
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
1192
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
1149
1193
|
});
|
|
1150
1194
|
|
|
1151
1195
|
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
@@ -1262,6 +1306,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1262
1306
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'state-pro2-secret' }),
|
|
1263
1307
|
},
|
|
1264
1308
|
updateInternalState,
|
|
1309
|
+
getInternalState: () => 'feature-session',
|
|
1265
1310
|
getCurrentDeviceId: () => 'pro-device-id',
|
|
1266
1311
|
getCurrentPassphraseProtection: () => true,
|
|
1267
1312
|
}) as any;
|
|
@@ -1472,7 +1517,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1472
1517
|
expect(device.getInternalState()).toBeUndefined();
|
|
1473
1518
|
device.passphraseState = 'state-auto';
|
|
1474
1519
|
expect(device.getInternalState()).toBe('session-auto');
|
|
1475
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
1520
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionOpen', 'DeviceSession', {
|
|
1521
|
+
select: { host_passphrase: { passphrase: 'state-auto-secret' } },
|
|
1522
|
+
});
|
|
1476
1523
|
});
|
|
1477
1524
|
|
|
1478
1525
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -1481,6 +1528,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1481
1528
|
type: 'DeviceSession',
|
|
1482
1529
|
message: {
|
|
1483
1530
|
session_id: 'main-pin-session',
|
|
1531
|
+
btc_test_address: 'main-wallet-state',
|
|
1484
1532
|
},
|
|
1485
1533
|
});
|
|
1486
1534
|
|
|
@@ -1510,7 +1558,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1510
1558
|
expect(device.features?.passphraseProtection).toBe(false);
|
|
1511
1559
|
expect(device.features?.sessionId).toBeNull();
|
|
1512
1560
|
expect(device.getInternalState()).toBeUndefined();
|
|
1513
|
-
expect(typedCall).
|
|
1561
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1562
|
+
eventless_wallet_session: true,
|
|
1563
|
+
});
|
|
1514
1564
|
});
|
|
1515
1565
|
|
|
1516
1566
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
@@ -1542,12 +1592,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1542
1592
|
);
|
|
1543
1593
|
|
|
1544
1594
|
expect(device.getInternalState()).toBeUndefined();
|
|
1545
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1595
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionOpen', 'DeviceSession', {
|
|
1596
|
+
select: { host_passphrase: { passphrase: 'expected-secret' } },
|
|
1597
|
+
});
|
|
1546
1598
|
});
|
|
1547
1599
|
|
|
1548
1600
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
1549
1601
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1550
|
-
const typedCall = jest.fn()
|
|
1602
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
1603
|
+
type: 'DeviceSession',
|
|
1604
|
+
message: {
|
|
1605
|
+
session_id: 'main-wallet-session',
|
|
1606
|
+
btc_test_address: 'main-wallet-state',
|
|
1607
|
+
},
|
|
1608
|
+
});
|
|
1551
1609
|
|
|
1552
1610
|
(device as any).features = normalizeProtocolV2Features(
|
|
1553
1611
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1567,7 +1625,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1567
1625
|
await expect(
|
|
1568
1626
|
device.checkPassphraseStateSafety('stale-hidden-state', true, false)
|
|
1569
1627
|
).resolves.toBe(true);
|
|
1570
|
-
expect(typedCall).
|
|
1628
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1629
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1630
|
+
eventless_wallet_session: true,
|
|
1631
|
+
});
|
|
1571
1632
|
});
|
|
1572
1633
|
|
|
1573
1634
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2226,7 +2287,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2226
2287
|
['DeviceSessionAskPin', 'Success', undefined, { timeoutMs: 120_000 }],
|
|
2227
2288
|
]);
|
|
2228
2289
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2229
|
-
'
|
|
2290
|
+
'DeviceSessionOpen',
|
|
2230
2291
|
'DeviceSession',
|
|
2231
2292
|
expect.anything()
|
|
2232
2293
|
);
|
|
@@ -2669,6 +2730,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2669
2730
|
return Promise.resolve({
|
|
2670
2731
|
type: 'DeviceInfo',
|
|
2671
2732
|
message: {
|
|
2733
|
+
hw: { serial_no: 'BLE-PRO2-SERIAL' },
|
|
2672
2734
|
fw: {
|
|
2673
2735
|
bootloader: { version: '0.0.0' },
|
|
2674
2736
|
application: { version: '0.0.0' },
|
|
@@ -2689,6 +2751,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2689
2751
|
});
|
|
2690
2752
|
const commands = { typedCall };
|
|
2691
2753
|
|
|
2754
|
+
(method as any).protocolV2ExpectedSerialNumber = 'BLE-PRO2-SERIAL';
|
|
2692
2755
|
(method as any).isBleReconnect = jest.fn(() => true);
|
|
2693
2756
|
(method as any).device = stubDevice({
|
|
2694
2757
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
@@ -2737,7 +2800,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2737
2800
|
});
|
|
2738
2801
|
});
|
|
2739
2802
|
|
|
2740
|
-
test('
|
|
2803
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
2741
2804
|
const method = new FirmwareUpdateV4({
|
|
2742
2805
|
id: 1,
|
|
2743
2806
|
payload: {
|
|
@@ -2750,6 +2813,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2750
2813
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
2751
2814
|
features: { capabilities: [] },
|
|
2752
2815
|
});
|
|
2816
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
2753
2817
|
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
2754
2818
|
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
2755
2819
|
{
|
|
@@ -2771,7 +2835,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2771
2835
|
|
|
2772
2836
|
await method.run();
|
|
2773
2837
|
|
|
2774
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
2838
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
2775
2839
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
2776
2840
|
fwBinaryMap: [
|
|
2777
2841
|
{
|
|
@@ -2815,6 +2879,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2815
2879
|
});
|
|
2816
2880
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2817
2881
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
2882
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
2818
2883
|
method.postTipMessage = jest.fn();
|
|
2819
2884
|
|
|
2820
2885
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
@@ -2880,7 +2945,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2880
2945
|
});
|
|
2881
2946
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2882
2947
|
(method as any).device.getCommands = () => ({ typedCall });
|
|
2883
|
-
(method as any).
|
|
2948
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
2884
2949
|
|
|
2885
2950
|
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
2886
2951
|
|
|
@@ -2999,7 +3064,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2999
3064
|
fw: { application: { version: '1.0.0' } },
|
|
3000
3065
|
},
|
|
3001
3066
|
});
|
|
3002
|
-
(method as any).
|
|
3067
|
+
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
3003
3068
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3004
3069
|
(method as any).device = stubDevice({
|
|
3005
3070
|
getCommands: () => ({ typedCall }),
|
|
@@ -3025,6 +3090,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3025
3090
|
});
|
|
3026
3091
|
method.postTipMessage = jest.fn();
|
|
3027
3092
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3093
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3028
3094
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
3029
3095
|
message: 'Device rebooted successfully',
|
|
3030
3096
|
});
|
|
@@ -3088,9 +3154,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3088
3154
|
const typedCall = jest
|
|
3089
3155
|
.fn()
|
|
3090
3156
|
.mockRejectedValue(
|
|
3091
|
-
|
|
3092
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
3093
|
-
'NotFoundError'
|
|
3157
|
+
Object.assign(
|
|
3158
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
3159
|
+
{ name: 'NotFoundError' }
|
|
3094
3160
|
)
|
|
3095
3161
|
);
|
|
3096
3162
|
|
|
@@ -3161,6 +3227,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3161
3227
|
getCommands: () => ({ typedCall }),
|
|
3162
3228
|
});
|
|
3163
3229
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3230
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3164
3231
|
method.postProgressMessage = jest.fn();
|
|
3165
3232
|
|
|
3166
3233
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
@@ -3177,7 +3244,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3177
3244
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
3178
3245
|
});
|
|
3179
3246
|
|
|
3180
|
-
test('
|
|
3247
|
+
test('does not treat a missing firmware status handler as install completion', async () => {
|
|
3181
3248
|
const method = new FirmwareUpdateV4({
|
|
3182
3249
|
id: 1,
|
|
3183
3250
|
payload: {
|
|
@@ -3193,14 +3260,42 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3193
3260
|
getCommands: () => ({ typedCall }),
|
|
3194
3261
|
});
|
|
3195
3262
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3263
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3196
3264
|
method.postProgressMessage = jest.fn();
|
|
3197
3265
|
|
|
3198
|
-
await (
|
|
3199
|
-
|
|
3200
|
-
|
|
3266
|
+
await expect(
|
|
3267
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3268
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3269
|
+
])
|
|
3270
|
+
).rejects.toThrow('Protocol V2 firmware install status is unavailable');
|
|
3201
3271
|
|
|
3202
3272
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3203
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3273
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3274
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3275
|
+
'DeviceInfoGet',
|
|
3276
|
+
]);
|
|
3277
|
+
});
|
|
3278
|
+
|
|
3279
|
+
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
3280
|
+
const method = new FirmwareUpdateV4({
|
|
3281
|
+
id: 1,
|
|
3282
|
+
payload: {
|
|
3283
|
+
method: 'firmwareUpdateV4',
|
|
3284
|
+
platform: 'desktop',
|
|
3285
|
+
expectedTargetVersions: {
|
|
3286
|
+
app_v1: '2.0.0',
|
|
3287
|
+
},
|
|
3288
|
+
},
|
|
3289
|
+
});
|
|
3290
|
+
method.init();
|
|
3291
|
+
method.postProgressMessage = jest.fn();
|
|
3292
|
+
|
|
3293
|
+
expect(() =>
|
|
3294
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
3295
|
+
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
3296
|
+
new Set([4])
|
|
3297
|
+
)
|
|
3298
|
+
).toThrow('expected 131072');
|
|
3204
3299
|
});
|
|
3205
3300
|
|
|
3206
3301
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
@@ -3225,6 +3320,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3225
3320
|
getCommands: () => ({ typedCall }),
|
|
3226
3321
|
});
|
|
3227
3322
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
3323
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3228
3324
|
method.postProgressMessage = jest.fn();
|
|
3229
3325
|
|
|
3230
3326
|
await expect(
|
|
@@ -3342,6 +3438,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3342
3438
|
getCommands: () => ({ typedCall }),
|
|
3343
3439
|
});
|
|
3344
3440
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
3441
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3345
3442
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(false);
|
|
3346
3443
|
|
|
3347
3444
|
await expect(
|
|
@@ -3377,10 +3474,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3377
3474
|
const writtenPaths: string[] = [];
|
|
3378
3475
|
method.postTipMessage = jest.fn();
|
|
3379
3476
|
method.postProgressMessage = jest.fn();
|
|
3380
|
-
(method as any).
|
|
3477
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
3381
3478
|
writtenPaths.push(params.filePath);
|
|
3382
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
3479
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
3383
3480
|
});
|
|
3481
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3482
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3483
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
3484
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3384
3485
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3385
3486
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3386
3487
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3420,10 +3521,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3420
3521
|
'vol0:/coprocessor.bin',
|
|
3421
3522
|
1
|
|
3422
3523
|
);
|
|
3423
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
3424
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
3524
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(2);
|
|
3525
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
3526
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
3527
|
+
});
|
|
3528
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
3425
3529
|
targets: [
|
|
3426
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
3427
3530
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
3428
3531
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
3429
3532
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -3433,7 +3536,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3433
3536
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
3434
3537
|
});
|
|
3435
3538
|
|
|
3436
|
-
test('announces
|
|
3539
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
3437
3540
|
const method = new FirmwareUpdateV4({
|
|
3438
3541
|
id: 1,
|
|
3439
3542
|
payload: {
|
|
@@ -3443,11 +3546,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3443
3546
|
|
|
3444
3547
|
method.postTipMessage = jest.fn();
|
|
3445
3548
|
method.postProgressMessage = jest.fn();
|
|
3446
|
-
(method as any).
|
|
3549
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
3447
3550
|
.fn()
|
|
3448
3551
|
.mockImplementation(params =>
|
|
3449
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3552
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
3450
3553
|
);
|
|
3554
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3555
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3556
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3451
3557
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3452
3558
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3453
3559
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3467,21 +3573,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3467
3573
|
{
|
|
3468
3574
|
fileName: 'application_p1.bin',
|
|
3469
3575
|
binary: new Uint8Array([3]).buffer,
|
|
3470
|
-
targetId:
|
|
3576
|
+
targetId: 4,
|
|
3471
3577
|
},
|
|
3472
3578
|
],
|
|
3473
3579
|
});
|
|
3474
3580
|
|
|
3475
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
3581
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
3476
3582
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
3477
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
3478
|
-
expect(
|
|
3583
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
3584
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
3585
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
3479
3586
|
1,
|
|
3480
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
3587
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
3481
3588
|
);
|
|
3482
|
-
expect((method as any).
|
|
3589
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
3483
3590
|
2,
|
|
3484
|
-
expect.objectContaining({ processedSize:
|
|
3591
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
3485
3592
|
);
|
|
3486
3593
|
});
|
|
3487
3594
|
|
|
@@ -3495,7 +3602,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3495
3602
|
|
|
3496
3603
|
method.postTipMessage = jest.fn();
|
|
3497
3604
|
method.postProgressMessage = jest.fn();
|
|
3498
|
-
(method as any).
|
|
3605
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3606
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3499
3607
|
(method as any).device = stubDevice({
|
|
3500
3608
|
getCommands: () => ({
|
|
3501
3609
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -3541,11 +3649,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3541
3649
|
|
|
3542
3650
|
method.postTipMessage = jest.fn();
|
|
3543
3651
|
method.postProgressMessage = jest.fn();
|
|
3544
|
-
(method as any).
|
|
3652
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
3545
3653
|
.fn()
|
|
3546
3654
|
.mockImplementation(params =>
|
|
3547
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3655
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
3548
3656
|
);
|
|
3657
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3658
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3659
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
3549
3660
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3550
3661
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3551
3662
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3725,7 +3836,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3725
3836
|
{
|
|
3726
3837
|
name: 'images',
|
|
3727
3838
|
url: 'https://example.com/images.okpkg',
|
|
3728
|
-
devicePath: '
|
|
3839
|
+
devicePath: 'VOL0:/resource/images/images.okpkg',
|
|
3729
3840
|
version: [2, 0, 0],
|
|
3730
3841
|
payloadHash: 'ab'.repeat(64),
|
|
3731
3842
|
headerHash: 'cd'.repeat(64),
|
|
@@ -3760,6 +3871,58 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3760
3871
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3761
3872
|
});
|
|
3762
3873
|
|
|
3874
|
+
test('rejects remote RESC bundle paths before download or bootloader entry', async () => {
|
|
3875
|
+
const method = new FirmwareUpdateV4({
|
|
3876
|
+
id: 1,
|
|
3877
|
+
payload: {
|
|
3878
|
+
method: 'firmwareUpdateV4',
|
|
3879
|
+
platform: 'web',
|
|
3880
|
+
targetsToUpdate: ['resource'],
|
|
3881
|
+
},
|
|
3882
|
+
});
|
|
3883
|
+
method.init();
|
|
3884
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3885
|
+
|
|
3886
|
+
const getSysResourceBinarySpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
3887
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
3888
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
3889
|
+
.mockReturnValue({
|
|
3890
|
+
required: false,
|
|
3891
|
+
version: [1, 0, 0],
|
|
3892
|
+
url: '',
|
|
3893
|
+
resourceBundles: [
|
|
3894
|
+
{
|
|
3895
|
+
name: 'images',
|
|
3896
|
+
url: 'https://example.com/images.okpkg',
|
|
3897
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
3898
|
+
},
|
|
3899
|
+
],
|
|
3900
|
+
fingerprint: '',
|
|
3901
|
+
changelog: {
|
|
3902
|
+
'zh-CN': '',
|
|
3903
|
+
'en-US': '',
|
|
3904
|
+
},
|
|
3905
|
+
});
|
|
3906
|
+
|
|
3907
|
+
(method as any).device = stubDevice({
|
|
3908
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3909
|
+
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
3910
|
+
});
|
|
3911
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
3912
|
+
bootloaderBinary: null,
|
|
3913
|
+
fwBinaryMap: [],
|
|
3914
|
+
});
|
|
3915
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
3916
|
+
method.postTipMessage = jest.fn();
|
|
3917
|
+
|
|
3918
|
+
await expect(method.run()).rejects.toThrow('resourceBundles[0].devicePath');
|
|
3919
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
3920
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3921
|
+
|
|
3922
|
+
getSysResourceBinarySpy.mockRestore();
|
|
3923
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3924
|
+
});
|
|
3925
|
+
|
|
3763
3926
|
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
3764
3927
|
const method = new FirmwareUpdateV4({
|
|
3765
3928
|
id: 1,
|
|
@@ -3770,6 +3933,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3770
3933
|
},
|
|
3771
3934
|
});
|
|
3772
3935
|
method.init();
|
|
3936
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3773
3937
|
|
|
3774
3938
|
const order: string[] = [];
|
|
3775
3939
|
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
@@ -3789,7 +3953,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3789
3953
|
{
|
|
3790
3954
|
name: 'images',
|
|
3791
3955
|
url: 'https://example.com/images.okpkg',
|
|
3792
|
-
devicePath: '
|
|
3956
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
3793
3957
|
},
|
|
3794
3958
|
],
|
|
3795
3959
|
fingerprint: '',
|
|
@@ -3811,7 +3975,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3811
3975
|
order.push('bootloader');
|
|
3812
3976
|
return Promise.resolve(true);
|
|
3813
3977
|
});
|
|
3814
|
-
(method as any
|
|
3978
|
+
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
3979
|
+
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
3980
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
3815
3981
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3816
3982
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3817
3983
|
bootloaderVersion: '1.0.0',
|
|
@@ -3823,7 +3989,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3823
3989
|
await method.run();
|
|
3824
3990
|
|
|
3825
3991
|
expect(order).toEqual(['download', 'bootloader']);
|
|
3826
|
-
expect(
|
|
3992
|
+
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
3827
3993
|
bootloaderBinary: null,
|
|
3828
3994
|
fwBinaryMap: [],
|
|
3829
3995
|
resourceBundles: [
|
|
@@ -3836,6 +4002,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3836
4002
|
],
|
|
3837
4003
|
});
|
|
3838
4004
|
|
|
4005
|
+
executeProtocolV2UpdateSpy.mockRestore();
|
|
3839
4006
|
getSysResourceBinarySpy.mockRestore();
|
|
3840
4007
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3841
4008
|
});
|
|
@@ -3850,6 +4017,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3850
4017
|
},
|
|
3851
4018
|
});
|
|
3852
4019
|
method.init();
|
|
4020
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3853
4021
|
|
|
3854
4022
|
const getSysResourceBinarySpy = jest
|
|
3855
4023
|
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
@@ -3961,6 +4129,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3961
4129
|
},
|
|
3962
4130
|
});
|
|
3963
4131
|
method.init();
|
|
4132
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3964
4133
|
|
|
3965
4134
|
const remoteBinaries = {
|
|
3966
4135
|
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
@@ -4007,6 +4176,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4007
4176
|
},
|
|
4008
4177
|
});
|
|
4009
4178
|
method.init();
|
|
4179
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4010
4180
|
|
|
4011
4181
|
(method as any).device = stubDevice({
|
|
4012
4182
|
originalDescriptor: { protocolType: 'V2' },
|
|
@@ -4042,12 +4212,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4042
4212
|
resourceBundleFiles: [
|
|
4043
4213
|
{
|
|
4044
4214
|
binary: resourceBundle,
|
|
4045
|
-
devicePath: '
|
|
4215
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
4046
4216
|
},
|
|
4047
4217
|
],
|
|
4048
4218
|
},
|
|
4049
4219
|
});
|
|
4050
4220
|
method.init();
|
|
4221
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4051
4222
|
|
|
4052
4223
|
(method as any).device = stubDevice({
|
|
4053
4224
|
originalDescriptor: { protocolType: 'V2' },
|
|
@@ -4080,6 +4251,36 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4080
4251
|
);
|
|
4081
4252
|
});
|
|
4082
4253
|
|
|
4254
|
+
test('rejects manual RESC bundle paths before bootloader entry', async () => {
|
|
4255
|
+
const method = new FirmwareUpdateV4({
|
|
4256
|
+
id: 1,
|
|
4257
|
+
payload: {
|
|
4258
|
+
method: 'firmwareUpdateV4',
|
|
4259
|
+
platform: 'web',
|
|
4260
|
+
resourceBundleFiles: [
|
|
4261
|
+
{
|
|
4262
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
4263
|
+
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
4264
|
+
},
|
|
4265
|
+
],
|
|
4266
|
+
},
|
|
4267
|
+
});
|
|
4268
|
+
method.init();
|
|
4269
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4270
|
+
|
|
4271
|
+
(method as any).device = stubDevice({
|
|
4272
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
4273
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
4274
|
+
});
|
|
4275
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
4276
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
4277
|
+
method.postTipMessage = jest.fn();
|
|
4278
|
+
|
|
4279
|
+
await expect(method.run()).rejects.toThrow('resourceBundleFiles[0].devicePath');
|
|
4280
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
4281
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
4282
|
+
});
|
|
4283
|
+
|
|
4083
4284
|
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
4084
4285
|
const method = new FirmwareUpdateV4({
|
|
4085
4286
|
id: 1,
|
|
@@ -4090,7 +4291,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4090
4291
|
|
|
4091
4292
|
method.postTipMessage = jest.fn();
|
|
4092
4293
|
method.postProgressMessage = jest.fn();
|
|
4093
|
-
(method as any).
|
|
4294
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4295
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4296
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4297
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4094
4298
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
4095
4299
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
4096
4300
|
|
|
@@ -4106,7 +4310,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4106
4310
|
fwBinaryMap: [],
|
|
4107
4311
|
});
|
|
4108
4312
|
|
|
4109
|
-
expect((method as any).
|
|
4313
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
4110
4314
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
4111
4315
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
4112
4316
|
});
|
|
@@ -4137,12 +4341,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4137
4341
|
});
|
|
4138
4342
|
method.postProgressMessage = jest.fn();
|
|
4139
4343
|
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
filePath: 'vol1:firmware.bin',
|
|
4143
|
-
processedSize: 0,
|
|
4144
|
-
totalSize: 4097,
|
|
4344
|
+
const source = await openFirmwareByteSource({
|
|
4345
|
+
binary: new Uint8Array(4097).buffer,
|
|
4145
4346
|
});
|
|
4347
|
+
try {
|
|
4348
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4349
|
+
source,
|
|
4350
|
+
filePath: 'vol1:firmware.bin',
|
|
4351
|
+
processedSize: 0,
|
|
4352
|
+
totalSize: 4097,
|
|
4353
|
+
});
|
|
4354
|
+
} finally {
|
|
4355
|
+
await source?.close();
|
|
4356
|
+
}
|
|
4146
4357
|
|
|
4147
4358
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4148
4359
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -4193,12 +4404,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4193
4404
|
method.postProgressMessage = jest.fn();
|
|
4194
4405
|
|
|
4195
4406
|
try {
|
|
4196
|
-
|
|
4197
|
-
|
|
4407
|
+
const source = await openFirmwareByteSource({
|
|
4408
|
+
binary: new Uint8Array(8001).buffer,
|
|
4409
|
+
});
|
|
4410
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4411
|
+
source,
|
|
4198
4412
|
filePath: 'vol0:/firmware.bin',
|
|
4199
4413
|
processedSize: 0,
|
|
4200
4414
|
totalSize: 8001,
|
|
4201
4415
|
});
|
|
4416
|
+
await source?.close();
|
|
4202
4417
|
} finally {
|
|
4203
4418
|
setTimeoutSpy.mockRestore();
|
|
4204
4419
|
}
|
|
@@ -4230,16 +4445,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4230
4445
|
});
|
|
4231
4446
|
method.postProgressMessage = jest.fn();
|
|
4232
4447
|
method.postTipMessage = jest.fn();
|
|
4448
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
4233
4449
|
|
|
4450
|
+
const source = await openFirmwareByteSource({
|
|
4451
|
+
binary: new Uint8Array(4097).buffer,
|
|
4452
|
+
});
|
|
4234
4453
|
await expect(
|
|
4235
|
-
(method as any).
|
|
4236
|
-
|
|
4454
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
4455
|
+
source,
|
|
4237
4456
|
filePath: 'vol0:/firmware.bin',
|
|
4238
4457
|
processedSize: 0,
|
|
4239
4458
|
totalSize: 4097,
|
|
4240
4459
|
})
|
|
4241
4460
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
4242
|
-
|
|
4461
|
+
await source?.close();
|
|
4462
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
4243
4463
|
});
|
|
4244
4464
|
|
|
4245
4465
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -4272,12 +4492,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4272
4492
|
});
|
|
4273
4493
|
method.postProgressMessage = jest.fn();
|
|
4274
4494
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
4278
|
-
processedSize: 0,
|
|
4279
|
-
totalSize: 1801,
|
|
4495
|
+
const source = await openFirmwareByteSource({
|
|
4496
|
+
binary: new Uint8Array(1801).buffer,
|
|
4280
4497
|
});
|
|
4498
|
+
try {
|
|
4499
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
4500
|
+
source,
|
|
4501
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
4502
|
+
processedSize: 0,
|
|
4503
|
+
totalSize: 1801,
|
|
4504
|
+
});
|
|
4505
|
+
} finally {
|
|
4506
|
+
await source?.close();
|
|
4507
|
+
}
|
|
4281
4508
|
|
|
4282
4509
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4283
4510
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -4394,9 +4621,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4394
4621
|
const typedCall = jest
|
|
4395
4622
|
.fn()
|
|
4396
4623
|
.mockRejectedValue(
|
|
4397
|
-
|
|
4398
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4399
|
-
'NotFoundError'
|
|
4624
|
+
Object.assign(
|
|
4625
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4626
|
+
{ name: 'NotFoundError' }
|
|
4400
4627
|
)
|
|
4401
4628
|
);
|
|
4402
4629
|
|
|
@@ -4561,29 +4788,64 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4561
4788
|
});
|
|
4562
4789
|
|
|
4563
4790
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
4564
|
-
test('
|
|
4565
|
-
|
|
4791
|
+
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
4792
|
+
const method = new FirmwareUpdateV4({
|
|
4793
|
+
id: 1,
|
|
4794
|
+
payload: {
|
|
4795
|
+
method: 'firmwareUpdateV4',
|
|
4796
|
+
platform: 'native',
|
|
4797
|
+
expectedDeviceId: 'persisted-device-id',
|
|
4798
|
+
},
|
|
4799
|
+
});
|
|
4800
|
+
|
|
4801
|
+
method.init();
|
|
4802
|
+
|
|
4803
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
4804
|
+
});
|
|
4805
|
+
|
|
4806
|
+
test('requires the same physical serial before firmware transfer resumes', () => {
|
|
4807
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
4566
4808
|
'identity mismatch'
|
|
4567
4809
|
);
|
|
4568
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-
|
|
4810
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
4569
4811
|
'identity unavailable'
|
|
4570
4812
|
);
|
|
4571
4813
|
expect(() =>
|
|
4572
|
-
assertProtocolV2ReconnectIdentity('expected-
|
|
4814
|
+
assertProtocolV2ReconnectIdentity('expected-serial', 'expected-serial')
|
|
4573
4815
|
).not.toThrow();
|
|
4816
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).toThrow(
|
|
4817
|
+
'identity unavailable'
|
|
4818
|
+
);
|
|
4574
4819
|
});
|
|
4575
4820
|
|
|
4576
|
-
test('
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4821
|
+
test('captures physical identity from active DeviceInfo instead of wallet deviceId', async () => {
|
|
4822
|
+
const method = new FirmwareUpdateV4({
|
|
4823
|
+
id: 1,
|
|
4824
|
+
payload: {
|
|
4825
|
+
method: 'firmwareUpdateV4',
|
|
4826
|
+
},
|
|
4827
|
+
});
|
|
4828
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4829
|
+
type: 'DeviceInfo',
|
|
4830
|
+
message: {
|
|
4831
|
+
protocol_version: 1,
|
|
4832
|
+
hw: { serial_no: 'PRO2-PHYSICAL-1' },
|
|
4833
|
+
},
|
|
4834
|
+
});
|
|
4835
|
+
(method as any).device = stubDevice({
|
|
4836
|
+
features: { deviceId: 'wallet-derived-id' },
|
|
4837
|
+
getCommands: () => ({ typedCall }),
|
|
4838
|
+
});
|
|
4839
|
+
|
|
4840
|
+
await (method as any).captureProtocolV2PhysicalIdentity();
|
|
4841
|
+
|
|
4842
|
+
expect((method as any).protocolV2ExpectedSerialNumber).toBe('PRO2-PHYSICAL-1');
|
|
4843
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
4844
|
+
'DeviceInfoGet',
|
|
4845
|
+
'DeviceInfo',
|
|
4846
|
+
expect.objectContaining({ targets: expect.objectContaining({ hw: true }) }),
|
|
4847
|
+
{ timeoutMs: 5000 }
|
|
4848
|
+
);
|
|
4587
4849
|
});
|
|
4588
4850
|
});
|
|
4589
4851
|
|
|
@@ -4661,7 +4923,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4661
4923
|
errorCode: HardwareErrorCode.DeviceLocked,
|
|
4662
4924
|
});
|
|
4663
4925
|
|
|
4664
|
-
test('
|
|
4926
|
+
test('does not replay SDK methods unless they are explicitly classified as retry-safe', () => {
|
|
4665
4927
|
class TestBusinessMethod extends BaseMethod {
|
|
4666
4928
|
init() {}
|
|
4667
4929
|
|
|
@@ -4675,7 +4937,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4675
4937
|
payload: { method: 'testBusinessMethod' },
|
|
4676
4938
|
} as any);
|
|
4677
4939
|
|
|
4678
|
-
expect(method.unlockPolicy).toBe('
|
|
4940
|
+
expect(method.unlockPolicy).toBe('none');
|
|
4679
4941
|
});
|
|
4680
4942
|
|
|
4681
4943
|
test.each([
|
|
@@ -4770,7 +5032,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4770
5032
|
const calls: string[] = [];
|
|
4771
5033
|
const method = {
|
|
4772
5034
|
name: 'deviceSettingsPageShow',
|
|
4773
|
-
unlockPolicy: '
|
|
5035
|
+
unlockPolicy: 'unlock-before-run',
|
|
4774
5036
|
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
4775
5037
|
run: jest.fn(() => {
|
|
4776
5038
|
calls.push('run');
|
|
@@ -4854,14 +5116,12 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4854
5116
|
test('keeps auto unlock but suppresses all synthesized UI for eventless methods', async () => {
|
|
4855
5117
|
const method = {
|
|
4856
5118
|
name: 'uploadPortfolio',
|
|
4857
|
-
unlockPolicy: '
|
|
5119
|
+
unlockPolicy: 'unlock-before-run',
|
|
4858
5120
|
protocolV2UiMode: 'none',
|
|
4859
|
-
run: jest
|
|
4860
|
-
.fn()
|
|
4861
|
-
.mockRejectedValueOnce(deviceLockedError())
|
|
4862
|
-
.mockResolvedValueOnce({ message: 'ok' }),
|
|
5121
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
4863
5122
|
};
|
|
4864
5123
|
const device = {
|
|
5124
|
+
features: { unlocked: false },
|
|
4865
5125
|
isProtocolV2: () => true,
|
|
4866
5126
|
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
4867
5127
|
};
|
|
@@ -4880,6 +5140,24 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
4880
5140
|
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
4881
5141
|
});
|
|
4882
5142
|
|
|
5143
|
+
test('does not replay a state-changing method after a locked response', async () => {
|
|
5144
|
+
const error = deviceLockedError();
|
|
5145
|
+
const method = {
|
|
5146
|
+
name: 'uploadPortfolio',
|
|
5147
|
+
unlockPolicy: 'unlock-before-run',
|
|
5148
|
+
run: jest.fn().mockRejectedValue(error),
|
|
5149
|
+
};
|
|
5150
|
+
const device = {
|
|
5151
|
+
features: { unlocked: true },
|
|
5152
|
+
isProtocolV2: () => true,
|
|
5153
|
+
unlockDevice: jest.fn(),
|
|
5154
|
+
};
|
|
5155
|
+
|
|
5156
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
5157
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
5158
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
5159
|
+
});
|
|
5160
|
+
|
|
4883
5161
|
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
4884
5162
|
const initialError = deviceLockedError();
|
|
4885
5163
|
const unlockError = new Error('PIN cancelled');
|
|
@@ -4959,7 +5237,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
4959
5237
|
});
|
|
4960
5238
|
|
|
4961
5239
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
4962
|
-
expect(v2Method.unlockPolicy).toBe('
|
|
5240
|
+
expect(v2Method.unlockPolicy).toBe('unlock-before-run');
|
|
4963
5241
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4964
5242
|
page: DeviceSettingsPage.DeviceReset,
|
|
4965
5243
|
});
|
|
@@ -5002,7 +5280,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5002
5280
|
});
|
|
5003
5281
|
|
|
5004
5282
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
5005
|
-
expect(v2Method.unlockPolicy).toBe('
|
|
5283
|
+
expect(v2Method.unlockPolicy).toBe('unlock-before-run');
|
|
5006
5284
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
5007
5285
|
page: DeviceSettingsPage.DevicePinChange,
|
|
5008
5286
|
});
|
|
@@ -5103,32 +5381,24 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5103
5381
|
});
|
|
5104
5382
|
});
|
|
5105
5383
|
|
|
5106
|
-
test('
|
|
5384
|
+
test('preserves the legacy Features response after explicit device unlock', async () => {
|
|
5107
5385
|
const method = new DeviceUnlock({
|
|
5108
5386
|
id: 1,
|
|
5109
5387
|
payload: { method: 'deviceUnlock' },
|
|
5110
5388
|
});
|
|
5111
5389
|
method.init();
|
|
5112
5390
|
|
|
5113
|
-
const
|
|
5114
|
-
schemaVersion: 1,
|
|
5115
|
-
revision: 2,
|
|
5116
|
-
updatedAt: 1,
|
|
5391
|
+
const features = {
|
|
5117
5392
|
protocol: 'V2',
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
settings: {},
|
|
5122
|
-
versions: {},
|
|
5123
|
-
capabilities: [],
|
|
5393
|
+
unlocked: true,
|
|
5394
|
+
sessionId: null,
|
|
5395
|
+
session_id: null,
|
|
5124
5396
|
};
|
|
5125
|
-
const unlockDevice = jest.fn().mockResolvedValue(
|
|
5126
|
-
|
|
5127
|
-
(method as any).device = { unlockDevice, getDeviceState };
|
|
5397
|
+
const unlockDevice = jest.fn().mockResolvedValue(features);
|
|
5398
|
+
(method as any).device = { unlockDevice };
|
|
5128
5399
|
|
|
5129
|
-
await expect(method.run()).resolves.toBe(
|
|
5400
|
+
await expect(method.run()).resolves.toBe(features);
|
|
5130
5401
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
5131
|
-
expect(getDeviceState).toHaveBeenCalledWith();
|
|
5132
5402
|
});
|
|
5133
5403
|
|
|
5134
5404
|
test.each([
|
|
@@ -5169,7 +5439,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5169
5439
|
|
|
5170
5440
|
method.init();
|
|
5171
5441
|
|
|
5172
|
-
expect(method.unlockPolicy).toBe('
|
|
5442
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5173
5443
|
});
|
|
5174
5444
|
|
|
5175
5445
|
test('unlocks before opening Protocol V2 settings pages through the unified method', () => {
|
|
@@ -5180,7 +5450,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5180
5450
|
|
|
5181
5451
|
method.init();
|
|
5182
5452
|
|
|
5183
|
-
expect(method.unlockPolicy).toBe('
|
|
5453
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5184
5454
|
});
|
|
5185
5455
|
|
|
5186
5456
|
test('keeps firmwareUpdateV4 unlock-before-reboot behavior explicit', () => {
|
|
@@ -5191,7 +5461,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5191
5461
|
|
|
5192
5462
|
method.init();
|
|
5193
5463
|
|
|
5194
|
-
expect(method.unlockPolicy).toBe('
|
|
5464
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
5195
5465
|
});
|
|
5196
5466
|
|
|
5197
5467
|
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|