@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.35
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 +84 -0
- package/__tests__/device-settings.test.ts +3 -0
- package/__tests__/device-wallet-session-store.test.ts +80 -7
- package/__tests__/evmSignTransaction.test.ts +69 -0
- package/__tests__/get-device-state.test.ts +78 -30
- package/__tests__/open-wallet-session.test.ts +186 -51
- package/__tests__/protocol-v2.test.ts +490 -112
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/evm/protocol.d.ts +3 -0
- package/dist/api/evm/protocol.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
- package/dist/device/Device.d.ts +3 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +606 -641
- package/dist/protocols/protocol-v2/features.d.ts +17 -6
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -1
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +2 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -1
- package/dist/types/device.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/FirmwareUpdateV4.ts +26 -8
- package/src/api/GetPassphraseState.ts +11 -0
- package/src/api/OpenWalletSession.ts +15 -7
- package/src/api/evm/EVMGetAddress.ts +2 -2
- package/src/api/evm/EVMGetPublicKey.ts +2 -2
- package/src/api/evm/EVMSignMessage.ts +2 -2
- package/src/api/evm/EVMSignTransaction.ts +2 -2
- package/src/api/evm/EVMSignTypedData.ts +35 -45
- package/src/api/evm/EVMVerifyMessage.ts +2 -2
- package/src/api/evm/protocol.ts +6 -0
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/data/messages/messages-protocol-v2.json +393 -485
- package/src/device/Device.ts +63 -37
- package/src/device/DeviceCommands.ts +12 -2
- package/src/device/DeviceWalletSessionStore.ts +0 -14
- package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
- package/src/protocols/protocol-v2/features.ts +58 -16
- package/src/protocols/protocol-v2/index.ts +5 -0
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
- package/src/protocols/protocol-v2/unlockRetry.ts +18 -7
- package/src/protocols/protocol-v2/walletSession.ts +35 -35
- package/src/types/api/openWalletSession.ts +1 -1
- package/src/types/api/protocolV2.ts +2 -2
- package/src/types/device.ts +2 -0
|
@@ -19,6 +19,90 @@ const createCommands = () => {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
describe('DeviceCommands failure mapping', () => {
|
|
22
|
+
it('logs passphrase call request and response without exposing the passphrase', async () => {
|
|
23
|
+
const commands = createCommands();
|
|
24
|
+
const requestLog = getLogger(LoggerNames.DeviceCommands);
|
|
25
|
+
const responseLog = getLogger(LoggerNames.Core);
|
|
26
|
+
requestLog.messages.length = 0;
|
|
27
|
+
responseLog.messages.length = 0;
|
|
28
|
+
commands.mainId = 'main-id';
|
|
29
|
+
commands.transport = {
|
|
30
|
+
call: jest.fn().mockResolvedValue({
|
|
31
|
+
type: 'Success',
|
|
32
|
+
message: {
|
|
33
|
+
message: 'Passphrase accepted',
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
} as any;
|
|
37
|
+
|
|
38
|
+
await expect(
|
|
39
|
+
commands.call('DeviceSessionAskPassphrase', {
|
|
40
|
+
passphrase: 'hidden-wallet-secret',
|
|
41
|
+
on_device: false,
|
|
42
|
+
})
|
|
43
|
+
).resolves.toMatchObject({ type: 'Success' });
|
|
44
|
+
|
|
45
|
+
expect(requestLog.messages.at(-1)?.message).toEqual([
|
|
46
|
+
'[DeviceCommands] [call] Sending',
|
|
47
|
+
'DeviceSessionAskPassphrase',
|
|
48
|
+
{
|
|
49
|
+
passphrase: '[REDACTED]',
|
|
50
|
+
on_device: false,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
expect(responseLog.messages.at(-1)?.message).toEqual([
|
|
54
|
+
'[DeviceCommands] [call] Received',
|
|
55
|
+
'Success',
|
|
56
|
+
{
|
|
57
|
+
message: 'Passphrase accepted',
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
expect(JSON.stringify([...requestLog.messages, ...responseLog.messages])).not.toContain(
|
|
61
|
+
'hidden-wallet-secret'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('logs DeviceStatus response fields without exposing the device ID', async () => {
|
|
66
|
+
const commands = createCommands();
|
|
67
|
+
const log = getLogger(LoggerNames.Core);
|
|
68
|
+
log.messages.length = 0;
|
|
69
|
+
commands.mainId = 'main-id';
|
|
70
|
+
commands.transport = {
|
|
71
|
+
call: jest.fn().mockResolvedValue({
|
|
72
|
+
type: 'DeviceStatus',
|
|
73
|
+
message: {
|
|
74
|
+
device_id: 'sensitive-device-id',
|
|
75
|
+
unlocked: true,
|
|
76
|
+
init_states: true,
|
|
77
|
+
backup_required: false,
|
|
78
|
+
passphrase_enabled: true,
|
|
79
|
+
attach_to_pin_enabled: false,
|
|
80
|
+
unlocked_by_attach_to_pin: false,
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
} as any;
|
|
84
|
+
|
|
85
|
+
await expect(commands.call('DeviceStatusGet', {})).resolves.toMatchObject({
|
|
86
|
+
type: 'DeviceStatus',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const receivedLog = log.messages.at(-1)?.message;
|
|
90
|
+
expect(receivedLog).toEqual([
|
|
91
|
+
'[DeviceCommands] [call] Received',
|
|
92
|
+
'DeviceStatus',
|
|
93
|
+
{
|
|
94
|
+
device_id: '[REDACTED]',
|
|
95
|
+
unlocked: true,
|
|
96
|
+
init_states: true,
|
|
97
|
+
backup_required: false,
|
|
98
|
+
passphrase_enabled: true,
|
|
99
|
+
attach_to_pin_enabled: false,
|
|
100
|
+
unlocked_by_attach_to_pin: false,
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
expect(JSON.stringify(receivedLog)).not.toContain('sensitive-device-id');
|
|
104
|
+
});
|
|
105
|
+
|
|
22
106
|
it('does not log DeviceSessionGet response secrets', async () => {
|
|
23
107
|
const commands = createCommands();
|
|
24
108
|
const log = getLogger(LoggerNames.DeviceCommands);
|
|
@@ -40,6 +40,9 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
40
40
|
['language', { language: 'ja-JP' }],
|
|
41
41
|
['brightness', { brightness: 80 }],
|
|
42
42
|
['haptic feedback', { hapticFeedback: true }],
|
|
43
|
+
['animation', { animationEnabled: true }],
|
|
44
|
+
['tap to wake', { tapToWake: true }],
|
|
45
|
+
['device name display', { deviceNameDisplayEnabled: true }],
|
|
43
46
|
])('does not unlock Protocol V2 before changing %s', (_name, settings) => {
|
|
44
47
|
const method = new DeviceSettings({
|
|
45
48
|
id: 1,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import ClearSessionCache from '../src/api/ClearSessionCache';
|
|
4
|
+
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
4
5
|
import DeviceWipe from '../src/api/device/DeviceWipe';
|
|
5
6
|
import {
|
|
6
7
|
DeviceWalletSessionStore,
|
|
@@ -35,15 +36,18 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
35
36
|
expect(store.get('device-2', 'hidden-a')).toBe('session-c');
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
test('
|
|
39
|
+
test('keeps SDK session handles without mirroring hardware eviction policy', () => {
|
|
39
40
|
const store = new DeviceWalletSessionStore();
|
|
40
41
|
store.setStandard('device-1', 'wallet-a', 'session-a');
|
|
41
42
|
store.set('device-1', 'wallet-b', 'session-b');
|
|
42
43
|
store.set('device-1', 'wallet-c', 'session-c');
|
|
43
44
|
store.set('device-1', 'wallet-d', 'session-d');
|
|
44
45
|
|
|
45
|
-
expect(store.get('device-1', 'wallet-a')).
|
|
46
|
-
expect(store.getStandard('device-1')).
|
|
46
|
+
expect(store.get('device-1', 'wallet-a')).toBe('session-a');
|
|
47
|
+
expect(store.getStandard('device-1')).toEqual({
|
|
48
|
+
passphraseState: 'wallet-a',
|
|
49
|
+
sessionId: 'session-a',
|
|
50
|
+
});
|
|
47
51
|
expect(store.get('device-1', 'wallet-b')).toBe('session-b');
|
|
48
52
|
expect(store.get('device-1', 'wallet-c')).toBe('session-c');
|
|
49
53
|
expect(store.get('device-1', 'wallet-d')).toBe('session-d');
|
|
@@ -94,7 +98,7 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
94
98
|
expect(store.getPending('stable-device-id')).toBe('pending-session');
|
|
95
99
|
});
|
|
96
100
|
|
|
97
|
-
test('keeps
|
|
101
|
+
test('keeps every known handle when descriptor sessions merge into a stable device id', () => {
|
|
98
102
|
const store = new DeviceWalletSessionStore();
|
|
99
103
|
store.setStandard('stable-device-id', 'wallet-a', 'session-a');
|
|
100
104
|
store.set('stable-device-id', 'wallet-b', 'session-b');
|
|
@@ -106,8 +110,11 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
106
110
|
nextDeviceId: 'stable-device-id',
|
|
107
111
|
});
|
|
108
112
|
|
|
109
|
-
expect(store.get('stable-device-id', 'wallet-a')).
|
|
110
|
-
expect(store.getStandard('stable-device-id')).
|
|
113
|
+
expect(store.get('stable-device-id', 'wallet-a')).toBe('session-a');
|
|
114
|
+
expect(store.getStandard('stable-device-id')).toEqual({
|
|
115
|
+
passphraseState: 'wallet-a',
|
|
116
|
+
sessionId: 'session-a',
|
|
117
|
+
});
|
|
111
118
|
expect(store.get('stable-device-id', 'wallet-b')).toBe('session-b');
|
|
112
119
|
expect(store.get('stable-device-id', 'wallet-c')).toBe('session-c');
|
|
113
120
|
expect(store.get('stable-device-id', 'wallet-d')).toBe('session-d');
|
|
@@ -402,4 +409,70 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
402
409
|
expect.any(Object)
|
|
403
410
|
);
|
|
404
411
|
});
|
|
412
|
+
|
|
413
|
+
test('selects the standard V1 wallet after a non-destructive live identity read', async () => {
|
|
414
|
+
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
415
|
+
device.features = {
|
|
416
|
+
protocol: 'V1',
|
|
417
|
+
deviceId: 'device-a',
|
|
418
|
+
unlocked: true,
|
|
419
|
+
passphraseProtection: false,
|
|
420
|
+
} as never;
|
|
421
|
+
const typedCall = jest
|
|
422
|
+
.fn()
|
|
423
|
+
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
|
|
424
|
+
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } });
|
|
425
|
+
device.commands = { typedCall } as never;
|
|
426
|
+
jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
427
|
+
|
|
428
|
+
await device.initialize({ deviceId: 'device-a' });
|
|
429
|
+
|
|
430
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
431
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
|
|
432
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
433
|
+
2,
|
|
434
|
+
'Initialize',
|
|
435
|
+
'Features',
|
|
436
|
+
{
|
|
437
|
+
passphrase_state: undefined,
|
|
438
|
+
is_contains_attach: true,
|
|
439
|
+
},
|
|
440
|
+
expect.any(Object)
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe('Protocol V1 passphrase state classification', () => {
|
|
446
|
+
test('does not expose a standard-wallet address as passphrase state', async () => {
|
|
447
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
448
|
+
type: 'Address',
|
|
449
|
+
message: { address: 'standard-wallet-address' },
|
|
450
|
+
});
|
|
451
|
+
const method = new GetPassphraseState({
|
|
452
|
+
payload: {
|
|
453
|
+
method: 'getPassphraseState',
|
|
454
|
+
connectId: 'connect-a',
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
method.device = {
|
|
458
|
+
features: {},
|
|
459
|
+
commands: { typedCall },
|
|
460
|
+
isProtocolV2: () => false,
|
|
461
|
+
getCurrentDeviceType: () => EDeviceType.Classic,
|
|
462
|
+
getCurrentFirmwareVersionString: () => '1.0.0',
|
|
463
|
+
getCurrentPassphraseProtection: () => false,
|
|
464
|
+
getCurrentDeviceId: () => 'device-a',
|
|
465
|
+
getFeatures: jest.fn(),
|
|
466
|
+
getInternalState: () => undefined,
|
|
467
|
+
updateInternalState: jest.fn(),
|
|
468
|
+
} as never;
|
|
469
|
+
|
|
470
|
+
await expect(method.run()).resolves.toBeUndefined();
|
|
471
|
+
expect(typedCall).toHaveBeenCalledWith('GetAddress', 'Address', {
|
|
472
|
+
address_n: expect.any(Array),
|
|
473
|
+
coin_name: 'Testnet',
|
|
474
|
+
script_type: 'SPENDADDRESS',
|
|
475
|
+
show_display: false,
|
|
476
|
+
});
|
|
477
|
+
});
|
|
405
478
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EVMSignTransaction from '../src/api/evm/EVMSignTransaction';
|
|
2
|
+
import TransportManager from '../src/data-manager/TransportManager';
|
|
2
3
|
|
|
3
4
|
import type { EVMTransactionEIP7702 } from '../src/types';
|
|
4
5
|
|
|
@@ -17,13 +18,17 @@ jest.mock('../src/device/Device', () => ({
|
|
|
17
18
|
Device: jest.fn(),
|
|
18
19
|
}));
|
|
19
20
|
|
|
21
|
+
const mockTransportManager = jest.mocked(TransportManager);
|
|
22
|
+
|
|
20
23
|
describe('EVMSignTransaction EIP-7702', () => {
|
|
21
24
|
let mockDevice: any;
|
|
22
25
|
let mockTypedCall: jest.Mock;
|
|
23
26
|
|
|
24
27
|
beforeEach(() => {
|
|
28
|
+
mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1CurrentSchema');
|
|
25
29
|
mockTypedCall = jest.fn();
|
|
26
30
|
mockDevice = {
|
|
31
|
+
isProtocolV2: jest.fn(() => false),
|
|
27
32
|
commands: {
|
|
28
33
|
typedCall: {
|
|
29
34
|
bind: jest.fn(() => mockTypedCall),
|
|
@@ -32,6 +37,70 @@ describe('EVMSignTransaction EIP-7702', () => {
|
|
|
32
37
|
};
|
|
33
38
|
});
|
|
34
39
|
|
|
40
|
+
describe('Protocol-specific schema routing', () => {
|
|
41
|
+
const transaction: EVMTransactionEIP7702 = {
|
|
42
|
+
to: '0x4Cd241E8d1510e30b2076397afc7508Ae59C66c9',
|
|
43
|
+
value: '0x0',
|
|
44
|
+
gasLimit: '0x5208',
|
|
45
|
+
nonce: '0x0',
|
|
46
|
+
chainId: 1,
|
|
47
|
+
maxFeePerGas: '0xbebc200',
|
|
48
|
+
maxPriorityFeePerGas: '0x9502f900',
|
|
49
|
+
authorizationList: [
|
|
50
|
+
{
|
|
51
|
+
chainId: 1,
|
|
52
|
+
address: '0x4Cd241E8d1510e30b2076397afc7508Ae59C66c9',
|
|
53
|
+
nonce: '0x1',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const createTransactionMethod = () => {
|
|
59
|
+
const method = new EVMSignTransaction({
|
|
60
|
+
id: 1,
|
|
61
|
+
payload: {
|
|
62
|
+
method: 'evmSignTransaction',
|
|
63
|
+
path: "m/44'/60'/0'/0/0",
|
|
64
|
+
transaction,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
method.device = mockDevice;
|
|
68
|
+
method.init();
|
|
69
|
+
return method;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
it('keeps Protocol V2 on current OneKey messages after a legacy V1 device', async () => {
|
|
73
|
+
mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1LegacySchema');
|
|
74
|
+
mockDevice.isProtocolV2.mockReturnValue(true);
|
|
75
|
+
mockTypedCall.mockResolvedValue({
|
|
76
|
+
message: {
|
|
77
|
+
signature_v: 27,
|
|
78
|
+
signature_r: '01',
|
|
79
|
+
signature_s: '02',
|
|
80
|
+
authorization_signatures: [],
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await expect(createTransactionMethod().run()).resolves.toMatchObject({
|
|
85
|
+
v: '0x1b',
|
|
86
|
+
});
|
|
87
|
+
expect(mockTypedCall).toHaveBeenCalledWith(
|
|
88
|
+
'EthereumSignTxEIP7702OneKey',
|
|
89
|
+
'EthereumTxRequestOneKey',
|
|
90
|
+
expect.any(Object)
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('continues to use legacy messages for a Protocol V1 legacy device', async () => {
|
|
95
|
+
mockTransportManager.getProtocolV1MessageSchema.mockReturnValue('v1LegacySchema');
|
|
96
|
+
|
|
97
|
+
await expect(createTransactionMethod().run()).rejects.toThrow(
|
|
98
|
+
'EIP7702 not supported by Trezor'
|
|
99
|
+
);
|
|
100
|
+
expect(mockTypedCall).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
35
104
|
describe('EIP-7702 Transaction Detection', () => {
|
|
36
105
|
it('should detect EIP-7702 transaction with authorization list', () => {
|
|
37
106
|
const transaction: EVMTransactionEIP7702 = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { DeviceType } from '@onekeyfe/hd-transport';
|
|
2
3
|
|
|
3
4
|
import { Device } from '../src/device/Device';
|
|
4
5
|
import { DEVICE } from '../src/events';
|
|
6
|
+
import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
|
|
5
7
|
|
|
6
8
|
jest.mock('../src/data/config', () => ({
|
|
7
9
|
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
@@ -28,6 +30,18 @@ const createV1Device = (typedCall: jest.Mock) => {
|
|
|
28
30
|
return device;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
const protocolV2ApplicationInfo = {
|
|
34
|
+
version: 1,
|
|
35
|
+
build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
|
|
36
|
+
supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getProtocolV2LoaderInfo = (mode: 'bootloader' | 'romloader') => ({
|
|
40
|
+
version: 1,
|
|
41
|
+
build_fingerprint: `${mode}__1.0.0__abcdef0__PROD__RELEASE`,
|
|
42
|
+
supported_messages: [],
|
|
43
|
+
});
|
|
44
|
+
|
|
31
45
|
describe('getDeviceState', () => {
|
|
32
46
|
test('does not expose the internal wallet session', async () => {
|
|
33
47
|
const device = createV2Device(jest.fn());
|
|
@@ -39,45 +53,55 @@ describe('getDeviceState', () => {
|
|
|
39
53
|
});
|
|
40
54
|
|
|
41
55
|
test('hydrates Protocol V2 with separate DeviceInfoGet and DeviceStatusGet', async () => {
|
|
42
|
-
const typedCall = jest.fn().mockImplementation((requestType: string) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
57
|
+
if (requestType === 'DeviceInfoGet') {
|
|
58
|
+
return {
|
|
59
|
+
message: {
|
|
60
|
+
protocol_version: 2,
|
|
61
|
+
hw: { serial_no: 'SERIAL-1' },
|
|
62
|
+
fw: { application: { version: '5.0.0' } },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
67
|
+
return { message: protocolV2ApplicationInfo };
|
|
68
|
+
}
|
|
69
|
+
return { message: { init_states: true, unlocked: true } };
|
|
70
|
+
});
|
|
52
71
|
const device = createV2Device(typedCall);
|
|
53
72
|
|
|
54
73
|
const state = await device.getDeviceState();
|
|
55
74
|
|
|
56
75
|
expect(state.identity.serialNo).toBe('SERIAL-1');
|
|
57
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
76
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
58
77
|
expect(typedCall.mock.calls[0][0]).toBe('DeviceInfoGet');
|
|
59
78
|
expect(typedCall.mock.calls[0][2]?.targets?.status).not.toBe(true);
|
|
60
79
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
61
80
|
});
|
|
62
81
|
|
|
63
82
|
test('uses one full DeviceInfoGet plus DeviceStatusGet when firmware refresh initializes state', async () => {
|
|
64
|
-
const typedCall = jest.fn().mockImplementation((requestType: string) =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
84
|
+
if (requestType === 'DeviceInfoGet') {
|
|
85
|
+
return {
|
|
86
|
+
message: {
|
|
87
|
+
protocol_version: 2,
|
|
88
|
+
hw: { serial_no: 'SERIAL-1' },
|
|
89
|
+
fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
94
|
+
return { message: protocolV2ApplicationInfo };
|
|
95
|
+
}
|
|
96
|
+
return { message: { init_states: true, unlocked: true } };
|
|
97
|
+
});
|
|
74
98
|
const device = createV2Device(typedCall);
|
|
75
99
|
|
|
76
100
|
await device.getDeviceState({
|
|
77
101
|
refreshSections: ['identity', 'versions', 'verification'],
|
|
78
102
|
});
|
|
79
103
|
|
|
80
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
104
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
81
105
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
82
106
|
1,
|
|
83
107
|
'DeviceInfoGet',
|
|
@@ -115,13 +139,13 @@ describe('getDeviceState', () => {
|
|
|
115
139
|
});
|
|
116
140
|
|
|
117
141
|
test.each(['bootloader', 'romloader'] as const)(
|
|
118
|
-
'
|
|
142
|
+
'uses ProtocolInfo to preserve %s mode without DeviceStatusGet',
|
|
119
143
|
async mode => {
|
|
120
144
|
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
121
145
|
if (requestType === 'DeviceInfoGet') {
|
|
122
146
|
return {
|
|
123
147
|
message: {
|
|
124
|
-
hw: { serial_no: 'SERIAL-1' },
|
|
148
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
|
|
125
149
|
fw:
|
|
126
150
|
mode === 'romloader'
|
|
127
151
|
? { romloader: { version: '1.0.0' } }
|
|
@@ -129,24 +153,38 @@ describe('getDeviceState', () => {
|
|
|
129
153
|
},
|
|
130
154
|
};
|
|
131
155
|
}
|
|
132
|
-
|
|
156
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
157
|
+
return { message: getProtocolV2LoaderInfo(mode) };
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
133
160
|
});
|
|
134
161
|
const device = createV2Device(typedCall);
|
|
135
162
|
device.updateState({ protocol: 'V2', status: { mode } }, 'initialize');
|
|
136
163
|
|
|
137
164
|
const state = await device.getDeviceState({ refreshSections: ['status'] });
|
|
138
165
|
|
|
139
|
-
expect(state.status.mode).toBe(
|
|
166
|
+
expect(state.status.mode).toBe(mode);
|
|
140
167
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
141
168
|
'DeviceInfoGet',
|
|
142
|
-
'
|
|
169
|
+
'ProtocolInfoRequest',
|
|
143
170
|
]);
|
|
144
171
|
}
|
|
145
172
|
);
|
|
146
173
|
|
|
147
174
|
test('refreshes Protocol V2 status only when explicitly requested in normal mode', async () => {
|
|
148
|
-
const typedCall = jest.fn().
|
|
149
|
-
|
|
175
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
176
|
+
if (requestType === 'DeviceInfoGet') {
|
|
177
|
+
return {
|
|
178
|
+
message: {
|
|
179
|
+
hw: { serial_no: 'SERIAL-1' },
|
|
180
|
+
fw: { application: { version: '5.0.0' } },
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
185
|
+
return { message: protocolV2ApplicationInfo };
|
|
186
|
+
}
|
|
187
|
+
return { message: { init_states: true, unlocked: true, device_id: 'device-1' } };
|
|
150
188
|
});
|
|
151
189
|
const device = createV2Device(typedCall);
|
|
152
190
|
device.updateState({ protocol: 'V2', status: { mode: 'normal' } }, 'initialize');
|
|
@@ -203,6 +241,9 @@ describe('getDeviceState', () => {
|
|
|
203
241
|
message: { init_states: true, unlocked: false, device_id: 'device-1' },
|
|
204
242
|
};
|
|
205
243
|
}
|
|
244
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
245
|
+
return { message: protocolV2ApplicationInfo };
|
|
246
|
+
}
|
|
206
247
|
if (requestType === 'DeviceSettingsGet') {
|
|
207
248
|
return { message: { label: 'Renamed Pro 2' } };
|
|
208
249
|
}
|
|
@@ -215,6 +256,7 @@ describe('getDeviceState', () => {
|
|
|
215
256
|
|
|
216
257
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
217
258
|
'DeviceInfoGet',
|
|
259
|
+
'ProtocolInfoRequest',
|
|
218
260
|
'DeviceStatusGet',
|
|
219
261
|
'DeviceSettingsGet',
|
|
220
262
|
]);
|
|
@@ -237,6 +279,9 @@ describe('getDeviceState', () => {
|
|
|
237
279
|
message: { init_states: true, unlocked: false, device_id: 'device-1' },
|
|
238
280
|
};
|
|
239
281
|
}
|
|
282
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
283
|
+
return { message: protocolV2ApplicationInfo };
|
|
284
|
+
}
|
|
240
285
|
throw Object.assign(new Error('Device locked'), {
|
|
241
286
|
errorCode: HardwareErrorCode.DeviceLocked,
|
|
242
287
|
});
|
|
@@ -383,7 +428,10 @@ describe('getDeviceState', () => {
|
|
|
383
428
|
},
|
|
384
429
|
};
|
|
385
430
|
}
|
|
386
|
-
|
|
431
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
432
|
+
return { message: getProtocolV2LoaderInfo('bootloader') };
|
|
433
|
+
}
|
|
434
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
387
435
|
});
|
|
388
436
|
const device = createV2Device(typedCall);
|
|
389
437
|
device.updateState(
|