@onekeyfe/hd-core 1.2.0-alpha.22 → 1.2.0-alpha.24
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 +43 -8
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-lifecycle-events.test.ts +59 -0
- package/__tests__/device-settings.test.ts +1 -1
- package/__tests__/device-wallet-session-store.test.ts +41 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +30 -39
- package/__tests__/logBlockEvent.test.ts +17 -0
- package/__tests__/open-wallet-session.test.ts +233 -111
- package/__tests__/protocol-v2-ping.test.ts +28 -0
- package/__tests__/protocol-v2.test.ts +513 -313
- package/__tests__/public-pro2-api-boundary.test.ts +22 -0
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +5 -21
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts +1 -8
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -7
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/Ping.d.ts +2 -0
- package/dist/api/protocol-v2/Ping.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 +0 -1
- 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/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 +98 -278
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +876 -2647
- 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/unlockRetry.d.ts +2 -3
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +12 -0
- 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 +0 -3
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +0 -69
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +4 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +0 -12
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +4 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/settings.d.ts +0 -13
- package/dist/types/settings.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/CheckAllFirmwareRelease.ts +3 -16
- package/src/api/FirmwareUpdateV2.ts +119 -284
- package/src/api/FirmwareUpdateV3.ts +55 -248
- package/src/api/FirmwareUpdateV4.ts +293 -751
- package/src/api/GetPassphraseState.ts +11 -5
- package/src/api/OpenWalletSession.ts +23 -86
- package/src/api/device/DeviceUpdateBootloader.ts +6 -114
- package/src/api/device/DeviceWipe.ts +2 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -50
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +22 -140
- package/src/api/index.ts +1 -0
- package/src/api/protocol-v2/Ping.ts +32 -1
- package/src/core/index.ts +40 -7
- package/src/data/messages/messages-protocol-v2.json +49 -56
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +29 -8
- package/src/device/DeviceCommands.ts +43 -151
- package/src/events/device.ts +1 -0
- package/src/events/logBlockEvent.ts +1 -15
- package/src/events/ui-request.ts +1 -0
- package/src/index.ts +0 -5
- package/src/inject.ts +4 -22
- package/src/lowLevelInject.ts +1 -5
- package/src/protocols/protocol-v2/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +0 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +20 -3
- package/src/protocols/protocol-v2/walletSession.ts +57 -18
- package/src/topLevelInject.ts +1 -5
- package/src/types/api/checkAllFirmwareRelease.ts +0 -3
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +0 -18
- package/src/types/api/firmwareUpdate.ts +0 -76
- package/src/types/api/getPassphraseState.ts +4 -3
- package/src/types/api/index.ts +3 -14
- package/src/types/api/openWalletSession.ts +0 -19
- package/src/types/api/protocolV2.ts +10 -0
- package/src/types/settings.ts +0 -13
- package/src/utils/deviceInfoUtils.ts +2 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -326
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -200
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -26
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -24
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -27
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -621
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -378
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -36
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DeviceRebootType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { 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,7 +83,6 @@ 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';
|
|
87
86
|
import {
|
|
88
87
|
getPassphraseState,
|
|
89
88
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -98,6 +97,14 @@ jest.mock('../src/data/config', () => ({
|
|
|
98
97
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
99
98
|
}));
|
|
100
99
|
|
|
100
|
+
const createWalletSessionTypedCall = (implementation = jest.fn()) =>
|
|
101
|
+
jest.fn((request: string, ...args: unknown[]) => {
|
|
102
|
+
if (request === 'ProtocolInfoRequest') {
|
|
103
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
104
|
+
}
|
|
105
|
+
return implementation(request, ...args);
|
|
106
|
+
});
|
|
107
|
+
|
|
101
108
|
describe('DeviceUploadWallpaper', () => {
|
|
102
109
|
test('encodes, uploads and applies a Pro2 wallpaper', async () => {
|
|
103
110
|
const rgba = new Uint8Array(604 * 1024 * 4).fill(255);
|
|
@@ -690,13 +697,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
690
697
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
691
698
|
features.firmwareVersion = '1.2.3';
|
|
692
699
|
features.passphraseProtection = true;
|
|
693
|
-
const typedCall =
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
+
const typedCall = createWalletSessionTypedCall(
|
|
701
|
+
jest.fn().mockResolvedValue({
|
|
702
|
+
type: 'DeviceSession',
|
|
703
|
+
message: {
|
|
704
|
+
btc_test_address: 'state-1',
|
|
705
|
+
session_id: 'session-1',
|
|
706
|
+
},
|
|
707
|
+
})
|
|
708
|
+
);
|
|
700
709
|
const commands = { typedCall } as unknown as DeviceCommands;
|
|
701
710
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
702
711
|
(device as any).features = features;
|
|
@@ -724,13 +733,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
724
733
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
725
734
|
{ status: { passphrase_enabled: false, unlocked: true } }
|
|
726
735
|
);
|
|
727
|
-
const typedCall =
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
736
|
+
const typedCall = createWalletSessionTypedCall(
|
|
737
|
+
jest.fn().mockResolvedValue({
|
|
738
|
+
type: 'DeviceSession',
|
|
739
|
+
message: {
|
|
740
|
+
btc_test_address: 'main-wallet-state',
|
|
741
|
+
session_id: 'main-wallet-session',
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
);
|
|
734
745
|
const promptPassphrase = jest
|
|
735
746
|
.fn()
|
|
736
747
|
.mockResolvedValue({ passphrase: 'hidden-wallet-with-disabled-flag' });
|
|
@@ -800,17 +811,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
800
811
|
},
|
|
801
812
|
}
|
|
802
813
|
);
|
|
803
|
-
const typedCall =
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
+
const typedCall = createWalletSessionTypedCall(
|
|
815
|
+
jest.fn().mockImplementation((request: string) => {
|
|
816
|
+
if (request === 'DeviceStatusGet') {
|
|
817
|
+
return Promise.resolve({
|
|
818
|
+
message: { passphrase_enabled: false, unlocked: true },
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
822
|
+
})
|
|
823
|
+
);
|
|
814
824
|
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden-after-unlock' });
|
|
815
825
|
const unlockDevice = jest.spyOn(device, 'unlockDevice').mockImplementation(() => {
|
|
816
826
|
(device as any).features = {
|
|
@@ -844,13 +854,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
844
854
|
);
|
|
845
855
|
device.passphraseState = 'hidden-state';
|
|
846
856
|
preloadSessionCache('main-wallet-device', 'hidden-state', 'hidden-session');
|
|
847
|
-
const typedCall =
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
857
|
+
const typedCall = createWalletSessionTypedCall(
|
|
858
|
+
jest.fn().mockResolvedValue({
|
|
859
|
+
type: 'DeviceSession',
|
|
860
|
+
message: {
|
|
861
|
+
btc_test_address: 'main-wallet-state',
|
|
862
|
+
session_id: 'main-wallet-session',
|
|
863
|
+
},
|
|
864
|
+
})
|
|
865
|
+
);
|
|
854
866
|
(device as any).commands = { typedCall };
|
|
855
867
|
|
|
856
868
|
await expect(
|
|
@@ -885,11 +897,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
885
897
|
},
|
|
886
898
|
}
|
|
887
899
|
);
|
|
888
|
-
const typedCall =
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
900
|
+
const typedCall = createWalletSessionTypedCall(
|
|
901
|
+
jest.fn().mockResolvedValue({
|
|
902
|
+
type: 'DeviceSession',
|
|
903
|
+
message: { session_id: 'hidden-session', btc_test_address: 'hidden-state' },
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
|
|
893
907
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
894
908
|
|
|
895
909
|
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
@@ -898,9 +912,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
898
912
|
});
|
|
899
913
|
|
|
900
914
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
901
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
902
|
-
|
|
903
|
-
});
|
|
915
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
|
|
916
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
904
917
|
});
|
|
905
918
|
|
|
906
919
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -993,10 +1006,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
993
1006
|
);
|
|
994
1007
|
device.passphraseState = 'state-a';
|
|
995
1008
|
preloadSessionCache('stable-device-1', 'state-a', 'session-a');
|
|
996
|
-
const typedCall =
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1009
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1010
|
+
jest.fn().mockResolvedValue({
|
|
1011
|
+
type: 'DeviceSession',
|
|
1012
|
+
message: { session_id: 'session-b', btc_test_address: 'state-a' },
|
|
1013
|
+
})
|
|
1014
|
+
);
|
|
1000
1015
|
(device as any).commands = { typedCall };
|
|
1001
1016
|
|
|
1002
1017
|
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
@@ -1004,8 +1019,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1004
1019
|
newSession: 'session-b',
|
|
1005
1020
|
});
|
|
1006
1021
|
|
|
1007
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
1008
|
-
|
|
1022
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1023
|
+
session_id: 'session-a',
|
|
1009
1024
|
});
|
|
1010
1025
|
expect(device.getInternalState()).toBe('session-b');
|
|
1011
1026
|
});
|
|
@@ -1021,13 +1036,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1021
1036
|
{ status: { device_id: 'stable-device-2', unlocked: true } }
|
|
1022
1037
|
);
|
|
1023
1038
|
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
1024
|
-
const typedCall =
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1039
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1040
|
+
jest.fn().mockResolvedValue({
|
|
1041
|
+
type: 'DeviceSession',
|
|
1042
|
+
message: {
|
|
1043
|
+
session_id: 'main-session',
|
|
1044
|
+
btc_test_address: 'main-wallet-state',
|
|
1045
|
+
},
|
|
1046
|
+
})
|
|
1047
|
+
);
|
|
1031
1048
|
(device as any).commands = { typedCall };
|
|
1032
1049
|
|
|
1033
1050
|
await getProtocolV2WalletSession(device, { onlyMainPin: true });
|
|
@@ -1056,14 +1073,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1056
1073
|
);
|
|
1057
1074
|
device.passphraseState = 'state-a';
|
|
1058
1075
|
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
1059
|
-
const typedCall =
|
|
1060
|
-
.fn()
|
|
1061
|
-
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1062
|
-
.mockRejectedValue(
|
|
1076
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1077
|
+
jest.fn().mockRejectedValue(
|
|
1063
1078
|
Object.assign(new Error('SE request failed'), {
|
|
1064
1079
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
1065
1080
|
})
|
|
1066
|
-
)
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
1067
1083
|
const promptPassphrase = jest.fn();
|
|
1068
1084
|
(device as any).commands = { typedCall, promptPassphrase };
|
|
1069
1085
|
|
|
@@ -1072,7 +1088,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1072
1088
|
});
|
|
1073
1089
|
expect(typedCall.mock.calls).toEqual([
|
|
1074
1090
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1075
|
-
['
|
|
1091
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
1076
1092
|
]);
|
|
1077
1093
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1078
1094
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1094,17 +1110,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1094
1110
|
},
|
|
1095
1111
|
}
|
|
1096
1112
|
);
|
|
1097
|
-
const typedCall =
|
|
1098
|
-
.fn()
|
|
1099
|
-
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1100
|
-
.mockRejectedValue(
|
|
1113
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1114
|
+
jest.fn().mockRejectedValue(
|
|
1101
1115
|
Object.assign(new Error('Invalid session'), {
|
|
1102
1116
|
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1103
1117
|
})
|
|
1104
|
-
)
|
|
1118
|
+
)
|
|
1119
|
+
);
|
|
1105
1120
|
(device as any).commands = {
|
|
1106
1121
|
typedCall,
|
|
1107
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1122
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1108
1123
|
};
|
|
1109
1124
|
|
|
1110
1125
|
await expect(getProtocolV2WalletSession(device)).rejects.toMatchObject({
|
|
@@ -1112,11 +1127,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1112
1127
|
});
|
|
1113
1128
|
expect(typedCall.mock.calls).toEqual([
|
|
1114
1129
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1115
|
-
[
|
|
1116
|
-
'DeviceSessionOpen',
|
|
1117
|
-
'DeviceSession',
|
|
1118
|
-
{ select: { host_passphrase: { passphrase: 'new-secret' } } },
|
|
1119
|
-
],
|
|
1130
|
+
['DeviceSessionAskPassphrase', 'Success', {}],
|
|
1120
1131
|
]);
|
|
1121
1132
|
});
|
|
1122
1133
|
|
|
@@ -1139,10 +1150,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1139
1150
|
device.passphraseState = 'state-a';
|
|
1140
1151
|
preloadSessionCache('stable-device-mismatch', 'state-a', 'session-a');
|
|
1141
1152
|
(device as any).commands = {
|
|
1142
|
-
typedCall:
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1153
|
+
typedCall: createWalletSessionTypedCall(
|
|
1154
|
+
jest.fn().mockResolvedValue({
|
|
1155
|
+
type: 'DeviceSession',
|
|
1156
|
+
message: { session_id: 'session-b', btc_test_address: 'state-b' },
|
|
1157
|
+
})
|
|
1158
|
+
),
|
|
1146
1159
|
};
|
|
1147
1160
|
|
|
1148
1161
|
await expect(
|
|
@@ -1155,7 +1168,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1155
1168
|
expect(device.getInternalState()).toBeUndefined();
|
|
1156
1169
|
});
|
|
1157
1170
|
|
|
1158
|
-
test('unlocks and retries
|
|
1171
|
+
test('unlocks and retries DeviceSessionAskPassphrase when wallet selection is locked', async () => {
|
|
1159
1172
|
const device = Device.fromDescriptor({
|
|
1160
1173
|
id: 'cache-device-4',
|
|
1161
1174
|
path: 'cache-path-4',
|
|
@@ -1168,19 +1181,21 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1168
1181
|
const lockedError = Object.assign(new Error('Device is locked'), {
|
|
1169
1182
|
errorCode: HardwareErrorCode.DeviceLocked,
|
|
1170
1183
|
});
|
|
1171
|
-
const typedCall =
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1184
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1185
|
+
jest
|
|
1186
|
+
.fn()
|
|
1187
|
+
.mockRejectedValueOnce(lockedError)
|
|
1188
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1189
|
+
.mockResolvedValueOnce({
|
|
1190
|
+
message: {
|
|
1191
|
+
session_id: 'session-after-unlock',
|
|
1192
|
+
btc_test_address: 'state-after-unlock',
|
|
1193
|
+
},
|
|
1194
|
+
})
|
|
1195
|
+
);
|
|
1181
1196
|
(device as any).commands = {
|
|
1182
1197
|
typedCall,
|
|
1183
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1198
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1184
1199
|
};
|
|
1185
1200
|
const unlockDevice = jest.spyOn(device, 'unlockDevice').mockResolvedValue(undefined as any);
|
|
1186
1201
|
|
|
@@ -1189,7 +1204,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1189
1204
|
passphraseState: 'state-after-unlock',
|
|
1190
1205
|
});
|
|
1191
1206
|
expect(unlockDevice).toHaveBeenCalledTimes(1);
|
|
1192
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
1207
|
+
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
1193
1208
|
});
|
|
1194
1209
|
|
|
1195
1210
|
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
@@ -1321,24 +1336,29 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1321
1336
|
);
|
|
1322
1337
|
});
|
|
1323
1338
|
|
|
1324
|
-
test('
|
|
1339
|
+
test('maps the legacy getPassphraseState App flow to the Pro2 wallet session protocol', async () => {
|
|
1325
1340
|
const features = {
|
|
1326
|
-
deviceId:
|
|
1341
|
+
deviceId: 'pro2-device-id',
|
|
1327
1342
|
deviceType: 'pro2',
|
|
1328
1343
|
firmwareVersion: '4.15.0',
|
|
1329
1344
|
passphraseProtection: true,
|
|
1330
1345
|
sessionId: 'feature-session',
|
|
1331
1346
|
unlockedAttachPin: true,
|
|
1332
1347
|
};
|
|
1333
|
-
const typedCall = jest
|
|
1334
|
-
|
|
1335
|
-
message: {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1348
|
+
const typedCall = jest
|
|
1349
|
+
.fn()
|
|
1350
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1351
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1352
|
+
.mockResolvedValueOnce({
|
|
1353
|
+
type: 'DeviceSession',
|
|
1354
|
+
message: {
|
|
1355
|
+
btc_test_address: 'state-pro2',
|
|
1356
|
+
session_id: 'session-pro2',
|
|
1357
|
+
},
|
|
1358
|
+
});
|
|
1340
1359
|
const updateInternalState = jest.fn();
|
|
1341
1360
|
const getFeatures = jest.fn().mockResolvedValue(features);
|
|
1361
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
|
|
1342
1362
|
const method = new GetPassphraseState({
|
|
1343
1363
|
payload: {
|
|
1344
1364
|
method: 'getPassphraseState',
|
|
@@ -1350,21 +1370,95 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1350
1370
|
features,
|
|
1351
1371
|
commands: {
|
|
1352
1372
|
typedCall,
|
|
1353
|
-
promptPassphrase
|
|
1373
|
+
promptPassphrase,
|
|
1354
1374
|
},
|
|
1375
|
+
emit: jest.fn(),
|
|
1355
1376
|
updateInternalState,
|
|
1356
1377
|
getFeatures,
|
|
1357
1378
|
getCurrentDeviceType: () => 'pro2',
|
|
1358
|
-
getCurrentDeviceId: () =>
|
|
1379
|
+
getCurrentDeviceId: () => 'pro2-device-id',
|
|
1359
1380
|
getCurrentPassphraseProtection: () => true,
|
|
1360
1381
|
}) as any;
|
|
1361
1382
|
|
|
1362
|
-
await expect(method.run()).
|
|
1363
|
-
|
|
1364
|
-
|
|
1383
|
+
await expect(method.run()).resolves.toBe('state-pro2');
|
|
1384
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
1385
|
+
{
|
|
1386
|
+
existsAttachPinUser: false,
|
|
1387
|
+
source: 'wallet-session-coordinator',
|
|
1388
|
+
reason: 'open-wallet',
|
|
1389
|
+
},
|
|
1390
|
+
{ cancelDeviceOnReject: false }
|
|
1391
|
+
);
|
|
1392
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
1393
|
+
true,
|
|
1394
|
+
'state-pro2',
|
|
1395
|
+
'pro2-device-id',
|
|
1396
|
+
'session-pro2',
|
|
1397
|
+
null
|
|
1398
|
+
);
|
|
1399
|
+
expect(JSON.stringify(typedCall.mock.calls)).not.toContain('legacy app secret');
|
|
1365
1400
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1366
1401
|
});
|
|
1367
1402
|
|
|
1403
|
+
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
1404
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1405
|
+
const typedCall = jest.fn().mockImplementation((request: string) => {
|
|
1406
|
+
if (request === 'ProtocolInfoRequest') {
|
|
1407
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
1408
|
+
}
|
|
1409
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
1410
|
+
return Promise.resolve({ message: { message: 'passphrase prepared' } });
|
|
1411
|
+
}
|
|
1412
|
+
if (request === 'DeviceSessionGet') {
|
|
1413
|
+
return Promise.resolve({
|
|
1414
|
+
type: 'DeviceSession',
|
|
1415
|
+
message: {
|
|
1416
|
+
btc_test_address: 'state-pro2-app',
|
|
1417
|
+
session_id: 'session-pro2-app',
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1425
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1426
|
+
{
|
|
1427
|
+
status: {
|
|
1428
|
+
device_id: 'pro2-app-device-id',
|
|
1429
|
+
unlocked: true,
|
|
1430
|
+
passphrase_enabled: true,
|
|
1431
|
+
},
|
|
1432
|
+
}
|
|
1433
|
+
);
|
|
1434
|
+
(device as any).commands = {
|
|
1435
|
+
typedCall,
|
|
1436
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
const method = new GetPassphraseState({
|
|
1440
|
+
payload: {
|
|
1441
|
+
method: 'getPassphraseState',
|
|
1442
|
+
connectId: 'connect-id',
|
|
1443
|
+
initSession: true,
|
|
1444
|
+
},
|
|
1445
|
+
});
|
|
1446
|
+
method.device = device;
|
|
1447
|
+
|
|
1448
|
+
await expect(method.run()).resolves.toBe('state-pro2-app');
|
|
1449
|
+
|
|
1450
|
+
await device.initialize({ passphraseState: 'state-pro2-app' });
|
|
1451
|
+
typedCall.mockClear();
|
|
1452
|
+
|
|
1453
|
+
await expect(device.checkPassphraseStateSafety('state-pro2-app', false, false)).resolves.toBe(
|
|
1454
|
+
true
|
|
1455
|
+
);
|
|
1456
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1457
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1458
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
1459
|
+
]);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1368
1462
|
test('uses the Pro2 standard wallet without passphraseState when passphrase is disabled', async () => {
|
|
1369
1463
|
const features = {
|
|
1370
1464
|
deviceId: 'pro2-device-id',
|
|
@@ -1373,12 +1467,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1373
1467
|
passphraseProtection: false,
|
|
1374
1468
|
unlocked: true,
|
|
1375
1469
|
};
|
|
1376
|
-
const typedCall =
|
|
1470
|
+
const typedCall = createWalletSessionTypedCall();
|
|
1377
1471
|
const updateInternalState = jest.fn();
|
|
1378
1472
|
const method = new GetPassphraseState({
|
|
1379
1473
|
payload: {
|
|
1380
1474
|
method: 'getPassphraseState',
|
|
1381
1475
|
connectId: 'connect-id',
|
|
1476
|
+
useEmptyPassphrase: true,
|
|
1382
1477
|
},
|
|
1383
1478
|
});
|
|
1384
1479
|
method.device = stubDevice({
|
|
@@ -1386,7 +1481,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1386
1481
|
features,
|
|
1387
1482
|
commands: {
|
|
1388
1483
|
typedCall,
|
|
1389
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1484
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1390
1485
|
},
|
|
1391
1486
|
updateInternalState,
|
|
1392
1487
|
getCurrentDeviceType: () => 'pro2',
|
|
@@ -1394,10 +1489,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1394
1489
|
getCurrentPassphraseProtection: () => false,
|
|
1395
1490
|
}) as any;
|
|
1396
1491
|
|
|
1397
|
-
await expect(method.run()).
|
|
1398
|
-
|
|
1492
|
+
await expect(method.run()).resolves.toBeUndefined();
|
|
1493
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1494
|
+
eventless_wallet_session: true,
|
|
1399
1495
|
});
|
|
1400
|
-
expect(typedCall).not.toHaveBeenCalled();
|
|
1401
1496
|
expect(updateInternalState).not.toHaveBeenCalled();
|
|
1402
1497
|
});
|
|
1403
1498
|
|
|
@@ -1410,12 +1505,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1410
1505
|
sessionId: 'old-feature-session',
|
|
1411
1506
|
unlockedAttachPin: false,
|
|
1412
1507
|
};
|
|
1413
|
-
const typedCall = jest
|
|
1414
|
-
|
|
1415
|
-
message: {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1508
|
+
const typedCall = jest
|
|
1509
|
+
.fn()
|
|
1510
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1511
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
1512
|
+
.mockResolvedValueOnce({
|
|
1513
|
+
type: 'DeviceSession',
|
|
1514
|
+
message: {
|
|
1515
|
+
btc_test_address: 'state-pro2-new',
|
|
1516
|
+
session_id: 'session-pro2-new',
|
|
1517
|
+
},
|
|
1518
|
+
});
|
|
1419
1519
|
const clearInternalState = jest.fn();
|
|
1420
1520
|
const updateInternalState = jest.fn();
|
|
1421
1521
|
const method = new GetPassphraseState({
|
|
@@ -1428,32 +1528,41 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1428
1528
|
method.device = stubDevice({
|
|
1429
1529
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
1430
1530
|
features,
|
|
1531
|
+
passphraseState: 'state-pro2-old',
|
|
1431
1532
|
commands: {
|
|
1432
1533
|
typedCall,
|
|
1433
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1534
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1434
1535
|
},
|
|
1536
|
+
emit: jest.fn(),
|
|
1435
1537
|
clearInternalState,
|
|
1436
1538
|
updateInternalState,
|
|
1437
1539
|
getCurrentDeviceId: () => 'pro2-device-id',
|
|
1438
1540
|
getCurrentPassphraseProtection: () => true,
|
|
1439
1541
|
}) as any;
|
|
1440
1542
|
|
|
1441
|
-
await expect(method.run()).
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
expect(
|
|
1445
|
-
|
|
1543
|
+
await expect(method.run()).resolves.toBe('state-pro2-new');
|
|
1544
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
1545
|
+
expect((method.device as any).passphraseState).toBeUndefined();
|
|
1546
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
1547
|
+
true,
|
|
1548
|
+
'state-pro2-new',
|
|
1549
|
+
'pro2-device-id',
|
|
1550
|
+
'session-pro2-new',
|
|
1551
|
+
null
|
|
1552
|
+
);
|
|
1446
1553
|
});
|
|
1447
1554
|
|
|
1448
1555
|
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
1449
1556
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1450
|
-
const typedCall =
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1557
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1558
|
+
jest.fn().mockResolvedValue({
|
|
1559
|
+
type: 'DeviceSession',
|
|
1560
|
+
message: {
|
|
1561
|
+
btc_test_address: 'state-profile',
|
|
1562
|
+
session_id: 'session-profile',
|
|
1563
|
+
},
|
|
1564
|
+
})
|
|
1565
|
+
);
|
|
1457
1566
|
|
|
1458
1567
|
(device as any).features = {
|
|
1459
1568
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
@@ -1465,7 +1574,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1465
1574
|
};
|
|
1466
1575
|
(device as any).commands = {
|
|
1467
1576
|
typedCall,
|
|
1468
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1577
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1469
1578
|
};
|
|
1470
1579
|
|
|
1471
1580
|
await getPassphraseStateWithRefreshDeviceInfo(device);
|
|
@@ -1477,13 +1586,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1477
1586
|
|
|
1478
1587
|
test('stores Pro2 passphrase sessions without selecting them implicitly', async () => {
|
|
1479
1588
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1480
|
-
const typedCall =
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1589
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1590
|
+
jest.fn().mockResolvedValue({
|
|
1591
|
+
type: 'DeviceSession',
|
|
1592
|
+
message: {
|
|
1593
|
+
btc_test_address: 'state-auto',
|
|
1594
|
+
session_id: 'session-auto',
|
|
1595
|
+
},
|
|
1596
|
+
})
|
|
1597
|
+
);
|
|
1487
1598
|
|
|
1488
1599
|
(device as any).features = normalizeProtocolV2Features(
|
|
1489
1600
|
{
|
|
@@ -1503,7 +1614,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1503
1614
|
);
|
|
1504
1615
|
(device as any).commands = {
|
|
1505
1616
|
typedCall,
|
|
1506
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1617
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1507
1618
|
};
|
|
1508
1619
|
|
|
1509
1620
|
await expect(getPassphraseStateWithRefreshDeviceInfo(device)).resolves.toMatchObject({
|
|
@@ -1517,20 +1628,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1517
1628
|
expect(device.getInternalState()).toBeUndefined();
|
|
1518
1629
|
device.passphraseState = 'state-auto';
|
|
1519
1630
|
expect(device.getInternalState()).toBe('session-auto');
|
|
1520
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
1521
|
-
select: { host_passphrase: { passphrase: 'state-auto-secret' } },
|
|
1522
|
-
});
|
|
1631
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1523
1632
|
});
|
|
1524
1633
|
|
|
1525
1634
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
1526
1635
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1527
|
-
const typedCall =
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1636
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1637
|
+
jest.fn().mockResolvedValue({
|
|
1638
|
+
type: 'DeviceSession',
|
|
1639
|
+
message: {
|
|
1640
|
+
session_id: 'main-pin-session',
|
|
1641
|
+
btc_test_address: 'main-wallet-state',
|
|
1642
|
+
},
|
|
1643
|
+
})
|
|
1644
|
+
);
|
|
1534
1645
|
|
|
1535
1646
|
(device as any).features = normalizeProtocolV2Features(
|
|
1536
1647
|
{
|
|
@@ -1565,13 +1676,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1565
1676
|
|
|
1566
1677
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
1567
1678
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1568
|
-
const typedCall =
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1679
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1680
|
+
jest.fn().mockResolvedValue({
|
|
1681
|
+
type: 'DeviceSession',
|
|
1682
|
+
message: {
|
|
1683
|
+
btc_test_address: 'wrong-state',
|
|
1684
|
+
session_id: 'wrong-session',
|
|
1685
|
+
},
|
|
1686
|
+
})
|
|
1687
|
+
);
|
|
1575
1688
|
|
|
1576
1689
|
(device as any).features = normalizeProtocolV2Features({
|
|
1577
1690
|
...descriptor,
|
|
@@ -1582,7 +1695,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1582
1695
|
(device as any).features.unlocked = true;
|
|
1583
1696
|
(device as any).commands = {
|
|
1584
1697
|
typedCall,
|
|
1585
|
-
promptPassphrase: jest.fn().mockResolvedValue({
|
|
1698
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
1586
1699
|
};
|
|
1587
1700
|
|
|
1588
1701
|
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).rejects.toEqual(
|
|
@@ -1592,20 +1705,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1592
1705
|
);
|
|
1593
1706
|
|
|
1594
1707
|
expect(device.getInternalState()).toBeUndefined();
|
|
1595
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, '
|
|
1596
|
-
select: { host_passphrase: { passphrase: 'expected-secret' } },
|
|
1597
|
-
});
|
|
1708
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
|
|
1598
1709
|
});
|
|
1599
1710
|
|
|
1600
1711
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
1601
1712
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1602
|
-
const typedCall =
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1713
|
+
const typedCall = createWalletSessionTypedCall(
|
|
1714
|
+
jest.fn().mockResolvedValue({
|
|
1715
|
+
type: 'DeviceSession',
|
|
1716
|
+
message: {
|
|
1717
|
+
session_id: 'main-wallet-session',
|
|
1718
|
+
btc_test_address: 'main-wallet-state',
|
|
1719
|
+
},
|
|
1720
|
+
})
|
|
1721
|
+
);
|
|
1609
1722
|
|
|
1610
1723
|
(device as any).features = normalizeProtocolV2Features(
|
|
1611
1724
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1696,7 +1809,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1696
1809
|
uuid: 'CACHED-SERIAL',
|
|
1697
1810
|
deviceId: null,
|
|
1698
1811
|
bleName: 'Cached BLE',
|
|
1699
|
-
name: 'Cached
|
|
1812
|
+
name: 'Cached Label',
|
|
1700
1813
|
label: 'Cached Label',
|
|
1701
1814
|
deviceType: 'pro2',
|
|
1702
1815
|
});
|
|
@@ -1705,6 +1818,39 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1705
1818
|
expect(device.hasUsePassphrase()).toBe(true);
|
|
1706
1819
|
});
|
|
1707
1820
|
|
|
1821
|
+
test('uses the BLE name as the Protocol V2 display label while preserving the raw label', () => {
|
|
1822
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1823
|
+
(device as any).features = {
|
|
1824
|
+
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
1825
|
+
serialNo: 'PR9999999999',
|
|
1826
|
+
label: null,
|
|
1827
|
+
bleName: 'Pro2 6136',
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
expect(device.toMessageObject()).toMatchObject({
|
|
1831
|
+
serialNo: 'PR9999999999',
|
|
1832
|
+
bleName: 'Pro2 6136',
|
|
1833
|
+
name: 'Pro2 6136',
|
|
1834
|
+
label: 'Pro2 6136',
|
|
1835
|
+
});
|
|
1836
|
+
expect(device.features?.label).toBeNull();
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
test('prefers the real Protocol V2 label over the BLE name for display', () => {
|
|
1840
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1841
|
+
(device as any).features = {
|
|
1842
|
+
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
1843
|
+
label: 'My Pro 2',
|
|
1844
|
+
bleName: 'Pro2 6136',
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
expect(device.toMessageObject()).toMatchObject({
|
|
1848
|
+
bleName: 'Pro2 6136',
|
|
1849
|
+
name: 'My Pro 2',
|
|
1850
|
+
label: 'My Pro 2',
|
|
1851
|
+
});
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1708
1854
|
test('syncs Protocol V2 cached features without cached profile', () => {
|
|
1709
1855
|
const cached = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1710
1856
|
(cached as any).features = {
|
|
@@ -2166,7 +2312,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2166
2312
|
await expect(method.run()).resolves.toEqual({ address: '0x0', signature: '0x1' });
|
|
2167
2313
|
});
|
|
2168
2314
|
|
|
2169
|
-
test('unlocks Protocol V2 devices
|
|
2315
|
+
test('unlocks Protocol V2 devices with the main PIN regardless of Pro-series version gates', async () => {
|
|
2170
2316
|
const device = Device.fromDescriptor({
|
|
2171
2317
|
path: 'usb-path',
|
|
2172
2318
|
protocolType: 'V2',
|
|
@@ -2206,7 +2352,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2206
2352
|
const features = await device.unlockDevice();
|
|
2207
2353
|
|
|
2208
2354
|
expect(typedCall.mock.calls).toEqual([
|
|
2209
|
-
['DeviceSessionAskPin', 'Success',
|
|
2355
|
+
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
2210
2356
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2211
2357
|
]);
|
|
2212
2358
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
@@ -2254,7 +2400,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2254
2400
|
await device.unlockDevice();
|
|
2255
2401
|
|
|
2256
2402
|
expect(typedCall.mock.calls).toEqual([
|
|
2257
|
-
['DeviceSessionAskPin', 'Success',
|
|
2403
|
+
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
2258
2404
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
2259
2405
|
]);
|
|
2260
2406
|
expect((device as any).profile).toBeUndefined();
|
|
@@ -2284,10 +2430,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2284
2430
|
);
|
|
2285
2431
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
2286
2432
|
expect(typedCall.mock.calls).toEqual([
|
|
2287
|
-
['DeviceSessionAskPin', 'Success',
|
|
2433
|
+
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
2288
2434
|
]);
|
|
2289
2435
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2290
|
-
'
|
|
2436
|
+
'DeviceSessionGet',
|
|
2291
2437
|
'DeviceSession',
|
|
2292
2438
|
expect.anything()
|
|
2293
2439
|
);
|
|
@@ -2800,7 +2946,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2800
2946
|
});
|
|
2801
2947
|
});
|
|
2802
2948
|
|
|
2803
|
-
test('
|
|
2949
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
2804
2950
|
const method = new FirmwareUpdateV4({
|
|
2805
2951
|
id: 1,
|
|
2806
2952
|
payload: {
|
|
@@ -2835,7 +2981,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2835
2981
|
|
|
2836
2982
|
await method.run();
|
|
2837
2983
|
|
|
2838
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
2984
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
2839
2985
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
2840
2986
|
fwBinaryMap: [
|
|
2841
2987
|
{
|
|
@@ -3154,9 +3300,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3154
3300
|
const typedCall = jest
|
|
3155
3301
|
.fn()
|
|
3156
3302
|
.mockRejectedValue(
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3303
|
+
new DOMException(
|
|
3304
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
3305
|
+
'NotFoundError'
|
|
3160
3306
|
)
|
|
3161
3307
|
);
|
|
3162
3308
|
|
|
@@ -3244,7 +3390,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3244
3390
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
3245
3391
|
});
|
|
3246
3392
|
|
|
3247
|
-
test('
|
|
3393
|
+
test('accepts a missing firmware status handler as an app reboot signal', async () => {
|
|
3248
3394
|
const method = new FirmwareUpdateV4({
|
|
3249
3395
|
id: 1,
|
|
3250
3396
|
payload: {
|
|
@@ -3263,39 +3409,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3263
3409
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
3264
3410
|
method.postProgressMessage = jest.fn();
|
|
3265
3411
|
|
|
3266
|
-
await
|
|
3267
|
-
|
|
3268
|
-
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3269
|
-
])
|
|
3270
|
-
).rejects.toThrow('Protocol V2 firmware install status is unavailable');
|
|
3271
|
-
|
|
3272
|
-
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3273
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
3274
|
-
'DeviceFirmwareUpdateStatusGet',
|
|
3275
|
-
'DeviceInfoGet',
|
|
3412
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
3413
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
3276
3414
|
]);
|
|
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
3415
|
|
|
3293
|
-
expect(()
|
|
3294
|
-
|
|
3295
|
-
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
3296
|
-
new Set([4])
|
|
3297
|
-
)
|
|
3298
|
-
).toThrow('expected 131072');
|
|
3416
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
3417
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
3299
3418
|
});
|
|
3300
3419
|
|
|
3301
3420
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
@@ -3474,14 +3593,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3474
3593
|
const writtenPaths: string[] = [];
|
|
3475
3594
|
method.postTipMessage = jest.fn();
|
|
3476
3595
|
method.postProgressMessage = jest.fn();
|
|
3477
|
-
(method as any).
|
|
3596
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
3478
3597
|
writtenPaths.push(params.filePath);
|
|
3479
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
3598
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
3480
3599
|
});
|
|
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({});
|
|
3485
3600
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3486
3601
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3487
3602
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3521,12 +3636,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3521
3636
|
'vol0:/coprocessor.bin',
|
|
3522
3637
|
1
|
|
3523
3638
|
);
|
|
3524
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
3525
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
3526
|
-
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
3527
|
-
});
|
|
3528
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
3639
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
3640
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
3529
3641
|
targets: [
|
|
3642
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
3530
3643
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
3531
3644
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
3532
3645
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -3536,7 +3649,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3536
3649
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
3537
3650
|
});
|
|
3538
3651
|
|
|
3539
|
-
test('announces
|
|
3652
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
3540
3653
|
const method = new FirmwareUpdateV4({
|
|
3541
3654
|
id: 1,
|
|
3542
3655
|
payload: {
|
|
@@ -3546,14 +3659,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3546
3659
|
|
|
3547
3660
|
method.postTipMessage = jest.fn();
|
|
3548
3661
|
method.postProgressMessage = jest.fn();
|
|
3549
|
-
(method as any).
|
|
3662
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
3550
3663
|
.fn()
|
|
3551
3664
|
.mockImplementation(params =>
|
|
3552
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3665
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
3553
3666
|
);
|
|
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({});
|
|
3557
3667
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3558
3668
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3559
3669
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3573,22 +3683,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3573
3683
|
{
|
|
3574
3684
|
fileName: 'application_p1.bin',
|
|
3575
3685
|
binary: new Uint8Array([3]).buffer,
|
|
3576
|
-
targetId:
|
|
3686
|
+
targetId: 3,
|
|
3577
3687
|
},
|
|
3578
3688
|
],
|
|
3579
3689
|
});
|
|
3580
3690
|
|
|
3581
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
3691
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
3582
3692
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
3583
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
3584
|
-
expect(method.
|
|
3585
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
3693
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
3694
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
3586
3695
|
1,
|
|
3587
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
3696
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
3588
3697
|
);
|
|
3589
|
-
expect((method as any).
|
|
3698
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
3590
3699
|
2,
|
|
3591
|
-
expect.objectContaining({ processedSize:
|
|
3700
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
3592
3701
|
);
|
|
3593
3702
|
});
|
|
3594
3703
|
|
|
@@ -3602,8 +3711,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3602
3711
|
|
|
3603
3712
|
method.postTipMessage = jest.fn();
|
|
3604
3713
|
method.postProgressMessage = jest.fn();
|
|
3605
|
-
(method as any).
|
|
3606
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
3714
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3607
3715
|
(method as any).device = stubDevice({
|
|
3608
3716
|
getCommands: () => ({
|
|
3609
3717
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -3649,14 +3757,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3649
3757
|
|
|
3650
3758
|
method.postTipMessage = jest.fn();
|
|
3651
3759
|
method.postProgressMessage = jest.fn();
|
|
3652
|
-
(method as any).
|
|
3760
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
3653
3761
|
.fn()
|
|
3654
3762
|
.mockImplementation(params =>
|
|
3655
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
3763
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
3656
3764
|
);
|
|
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({});
|
|
3660
3765
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
3661
3766
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
3662
3767
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -3975,9 +4080,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3975
4080
|
order.push('bootloader');
|
|
3976
4081
|
return Promise.resolve(true);
|
|
3977
4082
|
});
|
|
3978
|
-
|
|
3979
|
-
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
3980
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
4083
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3981
4084
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3982
4085
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3983
4086
|
bootloaderVersion: '1.0.0',
|
|
@@ -3989,7 +4092,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3989
4092
|
await method.run();
|
|
3990
4093
|
|
|
3991
4094
|
expect(order).toEqual(['download', 'bootloader']);
|
|
3992
|
-
expect(
|
|
4095
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3993
4096
|
bootloaderBinary: null,
|
|
3994
4097
|
fwBinaryMap: [],
|
|
3995
4098
|
resourceBundles: [
|
|
@@ -4002,7 +4105,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4002
4105
|
],
|
|
4003
4106
|
});
|
|
4004
4107
|
|
|
4005
|
-
executeProtocolV2UpdateSpy.mockRestore();
|
|
4006
4108
|
getSysResourceBinarySpy.mockRestore();
|
|
4007
4109
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
4008
4110
|
});
|
|
@@ -4291,10 +4393,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4291
4393
|
|
|
4292
4394
|
method.postTipMessage = jest.fn();
|
|
4293
4395
|
method.postProgressMessage = jest.fn();
|
|
4294
|
-
(method as any).
|
|
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);
|
|
4396
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4298
4397
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
4299
4398
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
4300
4399
|
|
|
@@ -4310,7 +4409,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4310
4409
|
fwBinaryMap: [],
|
|
4311
4410
|
});
|
|
4312
4411
|
|
|
4313
|
-
expect((method as any).
|
|
4412
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
4314
4413
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
4315
4414
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
4316
4415
|
});
|
|
@@ -4341,19 +4440,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4341
4440
|
});
|
|
4342
4441
|
method.postProgressMessage = jest.fn();
|
|
4343
4442
|
|
|
4344
|
-
|
|
4345
|
-
|
|
4443
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
4444
|
+
payload: new Uint8Array(4097).buffer,
|
|
4445
|
+
filePath: 'vol1:firmware.bin',
|
|
4446
|
+
processedSize: 0,
|
|
4447
|
+
totalSize: 4097,
|
|
4346
4448
|
});
|
|
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
|
-
}
|
|
4357
4449
|
|
|
4358
4450
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4359
4451
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -4404,16 +4496,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4404
4496
|
method.postProgressMessage = jest.fn();
|
|
4405
4497
|
|
|
4406
4498
|
try {
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
});
|
|
4410
|
-
await (method as any).protocolV2SourceUpdateProcess({
|
|
4411
|
-
source,
|
|
4499
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
4500
|
+
payload: new Uint8Array(8001).buffer,
|
|
4412
4501
|
filePath: 'vol0:/firmware.bin',
|
|
4413
4502
|
processedSize: 0,
|
|
4414
4503
|
totalSize: 8001,
|
|
4415
4504
|
});
|
|
4416
|
-
await source?.close();
|
|
4417
4505
|
} finally {
|
|
4418
4506
|
setTimeoutSpy.mockRestore();
|
|
4419
4507
|
}
|
|
@@ -4445,21 +4533,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4445
4533
|
});
|
|
4446
4534
|
method.postProgressMessage = jest.fn();
|
|
4447
4535
|
method.postTipMessage = jest.fn();
|
|
4448
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
4449
4536
|
|
|
4450
|
-
const source = await openFirmwareByteSource({
|
|
4451
|
-
binary: new Uint8Array(4097).buffer,
|
|
4452
|
-
});
|
|
4453
4537
|
await expect(
|
|
4454
|
-
(method as any).
|
|
4455
|
-
|
|
4538
|
+
(method as any).protocolV2WriteWholeFile({
|
|
4539
|
+
payload: new Uint8Array(4097).buffer,
|
|
4456
4540
|
filePath: 'vol0:/firmware.bin',
|
|
4457
4541
|
processedSize: 0,
|
|
4458
4542
|
totalSize: 4097,
|
|
4459
4543
|
})
|
|
4460
4544
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
4461
|
-
|
|
4462
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
4545
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4463
4546
|
});
|
|
4464
4547
|
|
|
4465
4548
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -4492,19 +4575,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4492
4575
|
});
|
|
4493
4576
|
method.postProgressMessage = jest.fn();
|
|
4494
4577
|
|
|
4495
|
-
|
|
4496
|
-
|
|
4578
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
4579
|
+
payload: new Uint8Array(1801).buffer,
|
|
4580
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
4581
|
+
processedSize: 0,
|
|
4582
|
+
totalSize: 1801,
|
|
4497
4583
|
});
|
|
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
|
-
}
|
|
4508
4584
|
|
|
4509
4585
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
4510
4586
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -4621,9 +4697,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4621
4697
|
const typedCall = jest
|
|
4622
4698
|
.fn()
|
|
4623
4699
|
.mockRejectedValue(
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4700
|
+
new DOMException(
|
|
4701
|
+
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4702
|
+
'NotFoundError'
|
|
4627
4703
|
)
|
|
4628
4704
|
);
|
|
4629
4705
|
|
|
@@ -4788,33 +4864,24 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
4788
4864
|
});
|
|
4789
4865
|
|
|
4790
4866
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
4791
|
-
test('
|
|
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', () => {
|
|
4867
|
+
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
4807
4868
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
4808
4869
|
'identity mismatch'
|
|
4809
4870
|
);
|
|
4810
|
-
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
4811
|
-
'identity unavailable'
|
|
4812
|
-
);
|
|
4813
4871
|
expect(() =>
|
|
4814
4872
|
assertProtocolV2ReconnectIdentity('expected-serial', 'expected-serial')
|
|
4815
4873
|
).not.toThrow();
|
|
4874
|
+
});
|
|
4875
|
+
|
|
4876
|
+
test('fails closed when the physical serial is unavailable', () => {
|
|
4877
|
+
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', undefined)).toThrow(
|
|
4878
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
4879
|
+
);
|
|
4816
4880
|
expect(() => assertProtocolV2ReconnectIdentity(undefined, 'actual-serial')).toThrow(
|
|
4817
|
-
|
|
4881
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
4882
|
+
);
|
|
4883
|
+
expect(() => assertProtocolV2ReconnectIdentity(undefined, undefined)).toThrow(
|
|
4884
|
+
expect.objectContaining({ errorCode: HardwareErrorCode.DeviceNotFound })
|
|
4818
4885
|
);
|
|
4819
4886
|
});
|
|
4820
4887
|
|
|
@@ -4847,6 +4914,54 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
4847
4914
|
{ timeoutMs: 5000 }
|
|
4848
4915
|
);
|
|
4849
4916
|
});
|
|
4917
|
+
|
|
4918
|
+
test('rejects firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
4919
|
+
const method = new FirmwareUpdateV4({
|
|
4920
|
+
id: 1,
|
|
4921
|
+
payload: { method: 'firmwareUpdateV4' },
|
|
4922
|
+
});
|
|
4923
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
4924
|
+
type: 'DeviceInfo',
|
|
4925
|
+
message: { protocol_version: 1, hw: {} },
|
|
4926
|
+
});
|
|
4927
|
+
(method as any).device = stubDevice({
|
|
4928
|
+
getCommands: () => ({ typedCall }),
|
|
4929
|
+
});
|
|
4930
|
+
|
|
4931
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).rejects.toMatchObject({
|
|
4932
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
4933
|
+
});
|
|
4934
|
+
});
|
|
4935
|
+
|
|
4936
|
+
test('uses the BLE read limit when reading an existing firmware bundle header', async () => {
|
|
4937
|
+
const method = new FirmwareUpdateV4({
|
|
4938
|
+
id: 1,
|
|
4939
|
+
payload: {
|
|
4940
|
+
method: 'firmwareUpdateV4',
|
|
4941
|
+
platform: 'native',
|
|
4942
|
+
chunkSize: 1800,
|
|
4943
|
+
},
|
|
4944
|
+
});
|
|
4945
|
+
method.init();
|
|
4946
|
+
const readChunkLengths: number[] = [];
|
|
4947
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
4948
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
4949
|
+
return Promise.resolve({ message: { exist: true, directory: false, size: 0x52a0 } });
|
|
4950
|
+
}
|
|
4951
|
+
if (requestType === 'FilesystemFileRead') {
|
|
4952
|
+
readChunkLengths.push(request.chunk_len);
|
|
4953
|
+
return Promise.resolve({ message: { data: new Uint8Array(request.chunk_len) } });
|
|
4954
|
+
}
|
|
4955
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
4956
|
+
});
|
|
4957
|
+
(method as any).device = stubDevice({
|
|
4958
|
+
getCommands: () => ({ typedCall }),
|
|
4959
|
+
});
|
|
4960
|
+
|
|
4961
|
+
await expect((method as any).readProtocolV2DeviceFileHeader('bundle.okpp')).resolves.toBeNull();
|
|
4962
|
+
expect(readChunkLengths.length).toBeGreaterThan(1);
|
|
4963
|
+
expect(Math.max(...readChunkLengths)).toBe(900);
|
|
4964
|
+
});
|
|
4850
4965
|
});
|
|
4851
4966
|
|
|
4852
4967
|
describe('Protocol V2 explicit USB device selection', () => {
|
|
@@ -5028,6 +5143,70 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
5028
5143
|
]);
|
|
5029
5144
|
});
|
|
5030
5145
|
|
|
5146
|
+
test('restores the expected hidden-wallet session before retrying after unlock', async () => {
|
|
5147
|
+
const calls: string[] = [];
|
|
5148
|
+
const method = {
|
|
5149
|
+
name: 'evmSignMessage',
|
|
5150
|
+
payload: { passphraseState: 'hidden-state' },
|
|
5151
|
+
useDevicePassphraseState: true,
|
|
5152
|
+
unlockPolicy: 'retry-on-locked',
|
|
5153
|
+
run: jest
|
|
5154
|
+
.fn()
|
|
5155
|
+
.mockImplementationOnce(() => {
|
|
5156
|
+
calls.push('run-1');
|
|
5157
|
+
return Promise.reject(deviceLockedError());
|
|
5158
|
+
})
|
|
5159
|
+
.mockImplementationOnce(() => {
|
|
5160
|
+
calls.push('run-2');
|
|
5161
|
+
return Promise.resolve({ message: 'ok' });
|
|
5162
|
+
}),
|
|
5163
|
+
};
|
|
5164
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
5165
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
5166
|
+
calls.push('negotiate-session');
|
|
5167
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
5168
|
+
}
|
|
5169
|
+
if (requestType === 'DeviceSessionGet') {
|
|
5170
|
+
calls.push('resume-hidden-session');
|
|
5171
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
5172
|
+
return Promise.resolve({
|
|
5173
|
+
message: {
|
|
5174
|
+
session_id: 'hidden-session',
|
|
5175
|
+
btc_test_address: 'hidden-state',
|
|
5176
|
+
},
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
5180
|
+
});
|
|
5181
|
+
const device = {
|
|
5182
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
5183
|
+
passphraseState: 'hidden-state',
|
|
5184
|
+
commands: { typedCall },
|
|
5185
|
+
isProtocolV2: () => true,
|
|
5186
|
+
unlockDevice: jest.fn(() => {
|
|
5187
|
+
calls.push('unlock');
|
|
5188
|
+
return Promise.resolve();
|
|
5189
|
+
}),
|
|
5190
|
+
getCurrentPassphraseProtection: () => true,
|
|
5191
|
+
getInternalState: () => 'hidden-session',
|
|
5192
|
+
clearInternalState: jest.fn(),
|
|
5193
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
5194
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
5195
|
+
};
|
|
5196
|
+
|
|
5197
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
5198
|
+
message: 'ok',
|
|
5199
|
+
});
|
|
5200
|
+
expect(calls).toEqual([
|
|
5201
|
+
'run-1',
|
|
5202
|
+
'unlock',
|
|
5203
|
+
'negotiate-session',
|
|
5204
|
+
'resume-hidden-session',
|
|
5205
|
+
'validate-hidden-session',
|
|
5206
|
+
'run-2',
|
|
5207
|
+
]);
|
|
5208
|
+
});
|
|
5209
|
+
|
|
5031
5210
|
test('unlocks before showing the method interaction when cached status is locked', async () => {
|
|
5032
5211
|
const calls: string[] = [];
|
|
5033
5212
|
const method = {
|
|
@@ -5218,22 +5397,27 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
5218
5397
|
describe('Protocol V2 current low-level methods', () => {
|
|
5219
5398
|
test('routes device wipe to the Pro2 reset page until the operation completes', async () => {
|
|
5220
5399
|
const v1TypedCall = jest.fn().mockResolvedValue({ message: { message: 'wiped' } });
|
|
5400
|
+
const v1InvalidateAfterWipe = jest.fn();
|
|
5221
5401
|
const v1Method = new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } });
|
|
5222
5402
|
v1Method.init();
|
|
5223
5403
|
(v1Method as any).device = stubDevice({
|
|
5224
5404
|
isProtocolV2: () => false,
|
|
5225
5405
|
commands: { typedCall: v1TypedCall },
|
|
5406
|
+
invalidateAfterWipe: v1InvalidateAfterWipe,
|
|
5226
5407
|
});
|
|
5227
5408
|
|
|
5228
5409
|
await v1Method.run();
|
|
5229
5410
|
expect(v1TypedCall).toHaveBeenCalledWith('WipeDevice', 'Success');
|
|
5411
|
+
expect(v1InvalidateAfterWipe).toHaveBeenCalledTimes(1);
|
|
5230
5412
|
|
|
5231
5413
|
const v2TypedCall = jest.fn().mockResolvedValue({ message: { message: 'accepted' } });
|
|
5414
|
+
const v2InvalidateAfterWipe = jest.fn();
|
|
5232
5415
|
const v2Method = new DeviceWipe({ id: 2, payload: { method: 'deviceWipe' } });
|
|
5233
5416
|
v2Method.init();
|
|
5234
5417
|
(v2Method as any).device = stubDevice({
|
|
5235
5418
|
isProtocolV2: () => true,
|
|
5236
5419
|
commands: { typedCall: v2TypedCall },
|
|
5420
|
+
invalidateAfterWipe: v2InvalidateAfterWipe,
|
|
5237
5421
|
});
|
|
5238
5422
|
|
|
5239
5423
|
await expect(v2Method.run()).resolves.toEqual({ message: 'accepted' });
|
|
@@ -5241,6 +5425,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5241
5425
|
expect(v2TypedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
5242
5426
|
page: DeviceSettingsPage.DeviceReset,
|
|
5243
5427
|
});
|
|
5428
|
+
expect(v2InvalidateAfterWipe).toHaveBeenCalledTimes(1);
|
|
5244
5429
|
expect(v2Method.protocolV2UiInteraction).toEqual({
|
|
5245
5430
|
request: 'button',
|
|
5246
5431
|
source: 'method-lifecycle',
|
|
@@ -5252,6 +5437,21 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5252
5437
|
});
|
|
5253
5438
|
});
|
|
5254
5439
|
|
|
5440
|
+
test('keeps the existing identity cache when device wipe fails', async () => {
|
|
5441
|
+
const wipeError = new Error('wipe failed');
|
|
5442
|
+
const invalidateAfterWipe = jest.fn();
|
|
5443
|
+
const method = new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } });
|
|
5444
|
+
method.init();
|
|
5445
|
+
(method as any).device = stubDevice({
|
|
5446
|
+
isProtocolV2: () => true,
|
|
5447
|
+
commands: { typedCall: jest.fn().mockRejectedValue(wipeError) },
|
|
5448
|
+
invalidateAfterWipe,
|
|
5449
|
+
});
|
|
5450
|
+
|
|
5451
|
+
await expect(method.run()).rejects.toBe(wipeError);
|
|
5452
|
+
expect(invalidateAfterWipe).not.toHaveBeenCalled();
|
|
5453
|
+
});
|
|
5454
|
+
|
|
5255
5455
|
test('routes Change PIN by protocol and waits for the Pro2 operation result', async () => {
|
|
5256
5456
|
const v1TypedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
5257
5457
|
const v1Method = new DeviceChangePin({
|
|
@@ -5402,14 +5602,12 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5402
5602
|
});
|
|
5403
5603
|
|
|
5404
5604
|
test.each([
|
|
5405
|
-
['label', { label: 'My Pro 2' }],
|
|
5406
5605
|
['language', { language: 'ja' }],
|
|
5407
5606
|
['brightness', { brightness: 80 }],
|
|
5408
5607
|
['haptic feedback', { hapticFeedback: true }],
|
|
5409
5608
|
[
|
|
5410
5609
|
'combined lock-free settings',
|
|
5411
5610
|
{
|
|
5412
|
-
label: 'My Pro 2',
|
|
5413
5611
|
language: 'ja',
|
|
5414
5612
|
brightness: 80,
|
|
5415
5613
|
hapticFeedback: true,
|
|
@@ -5427,8 +5625,10 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
5427
5625
|
});
|
|
5428
5626
|
|
|
5429
5627
|
test.each([
|
|
5628
|
+
['label', { label: 'My Pro 2' }],
|
|
5430
5629
|
['auto lock', { autoLockDelayMs: 60_000 }],
|
|
5431
5630
|
['auto shutdown', { autoShutdownDelayMs: 120_000 }],
|
|
5631
|
+
['label mixed with lock-free settings', { label: 'My Pro 2', brightness: 80 }],
|
|
5432
5632
|
['mixed protected settings', { brightness: 80, autoLockDelayMs: 60_000 }],
|
|
5433
5633
|
['other protected settings', { usbLockEnabled: true }],
|
|
5434
5634
|
])('unlocks before changing unified Protocol V2 %s', (_name, settings) => {
|