@onekeyfe/hd-core 1.2.0-alpha.2 → 1.2.0-alpha.4
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__/preInitialize.test.ts +33 -0
- package/__tests__/protocol-v2.test.ts +1577 -624
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +20 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +4 -4
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +4 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/index.d.ts +5 -10
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +7 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +1 -1
- package/dist/api/protocol-v2/{DeviceGetDeviceInfo.d.ts → DeviceInfoGet.d.ts} +10 -10
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/{FilesystemFixPermission.d.ts → FilesystemPermissionFix.d.ts} +2 -2
- package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +6 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -0
- package/dist/api/protocol-v2/helpers.d.ts +24 -19
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignData.d.ts +1 -5
- package/dist/api/ton/TonSignData.d.ts.map +1 -1
- package/dist/constants/index.d.ts +2 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +5 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/index.d.ts +85 -58
- package/dist/index.js +1672 -1640
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +12 -14
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts +10 -9
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +1 -6
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/getDeviceInfo.d.ts +2 -0
- package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts +4 -4
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +6 -11
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +12 -17
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +1 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +29 -1
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FileRead.ts +14 -1
- package/src/api/FileWrite.ts +14 -1
- package/src/api/FirmwareUpdateV4.ts +933 -197
- package/src/api/GetOnekeyFeatures.ts +1 -1
- package/src/api/GetPassphraseState.ts +7 -4
- package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
- package/src/api/device/DeviceRebootToBootloader.ts +4 -4
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +11 -4
- package/src/api/index.ts +5 -10
- package/src/api/protocol-v2/{FactoryGetDeviceInfo.ts → DeviceFactoryInfoGet.ts} +3 -3
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +32 -0
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +3 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +2 -2
- package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
- package/src/api/protocol-v2/DeviceReboot.ts +2 -2
- package/src/api/protocol-v2/{FilesystemFixPermission.ts → FilesystemPermissionFix.ts} +2 -2
- package/src/api/protocol-v2/{GetProtoVersion.ts → ProtocolInfoRequest.ts} +2 -2
- package/src/api/protocol-v2/helpers.ts +48 -57
- package/src/api/ton/TonSignData.ts +1 -5
- package/src/constants/index.ts +10 -1
- package/src/core/index.ts +19 -3
- package/src/data/messages/messages-protocol-v2.json +1224 -1804
- package/src/data-manager/DataManager.ts +7 -0
- package/src/device/Device.ts +34 -12
- package/src/device/DevicePool.ts +6 -6
- package/src/deviceProfile/buildDeviceFeatures.ts +61 -63
- package/src/deviceProfile/buildDeviceProfile.ts +78 -60
- package/src/inject.ts +10 -16
- package/src/protocols/protocol-v2/features.ts +26 -62
- package/src/protocols/protocol-v2/firmware.ts +11 -36
- package/src/types/api/firmwareUpdate.ts +10 -20
- package/src/types/api/getDeviceInfo.ts +2 -0
- package/src/types/api/getPassphraseState.ts +4 -4
- package/src/types/api/index.ts +10 -20
- package/src/types/api/protocolV2.ts +21 -42
- package/src/types/device.ts +1 -0
- package/src/types/params.ts +4 -0
- package/src/types/settings.ts +44 -30
- package/src/utils/deviceFeaturesUtils.ts +52 -11
- package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
- package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +0 -6
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +0 -1
- package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +0 -7
- package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +0 -1
- package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +0 -6
- package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +0 -1
- package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +0 -1
- package/dist/api/protocol-v2/GetProtoVersion.d.ts +0 -6
- package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +0 -1
- package/src/api/protocol-v2/DevReboot.ts +0 -24
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +0 -16
- package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +0 -29
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import {
|
|
2
|
+
import { DeviceRebootType } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
5
5
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
6
6
|
import DnxSignTransaction from '../src/api/dynex/DnxSignTransaction';
|
|
7
7
|
import DirList from '../src/api/DirList';
|
|
8
8
|
import FileRead from '../src/api/FileRead';
|
|
9
9
|
import FileWrite from '../src/api/FileWrite';
|
|
10
|
-
import
|
|
10
|
+
import DeviceFactoryInfoGet from '../src/api/protocol-v2/DeviceFactoryInfoGet';
|
|
11
|
+
import DeviceFactoryInfoSet from '../src/api/protocol-v2/DeviceFactoryInfoSet';
|
|
11
12
|
import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
|
|
12
|
-
import
|
|
13
|
-
import DeviceGetOnboardingStatus from '../src/api/protocol-v2/DeviceGetOnboardingStatus';
|
|
13
|
+
import DeviceInfoGet from '../src/api/protocol-v2/DeviceInfoGet';
|
|
14
14
|
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
15
|
+
import FilesystemPermissionFix from '../src/api/protocol-v2/FilesystemPermissionFix';
|
|
16
|
+
import ProtocolInfoRequest from '../src/api/protocol-v2/ProtocolInfoRequest';
|
|
15
17
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
16
18
|
import EVMSignMessageEIP712 from '../src/api/evm/EVMSignMessageEIP712';
|
|
17
19
|
import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
|
|
@@ -35,8 +37,6 @@ import TonSignData from '../src/api/ton/TonSignData';
|
|
|
35
37
|
import TonSignMessage from '../src/api/ton/TonSignMessage';
|
|
36
38
|
import TonSignProof from '../src/api/ton/TonSignProof';
|
|
37
39
|
import TronGetAddress from '../src/api/tron/TronGetAddress';
|
|
38
|
-
import TronSignMessage from '../src/api/tron/TronSignMessage';
|
|
39
|
-
import XrpSignTransaction from '../src/api/xrp/XrpSignTransaction';
|
|
40
40
|
import StellarGetAddress from '../src/api/stellar/StellarGetAddress';
|
|
41
41
|
import BenfenSignMessage from '../src/api/benfen/BenfenSignMessage';
|
|
42
42
|
import { getBitcoinForkVersionRange } from '../src/api/btc/helpers/versionLimit';
|
|
@@ -45,17 +45,14 @@ import { Device } from '../src/device/Device';
|
|
|
45
45
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
46
46
|
import {
|
|
47
47
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
48
|
-
buildMockProtocolV2DeviceInfo,
|
|
49
48
|
requestProtocolV2DeviceInfo,
|
|
50
|
-
setProtocolV2DeviceInfoMock,
|
|
51
49
|
} from '../src/protocols/protocol-v2/features';
|
|
52
|
-
import {
|
|
53
|
-
buildProfileFromProtocolV2,
|
|
54
|
-
buildProtocolV2FeaturesPayload,
|
|
55
|
-
} from '../src/deviceProfile';
|
|
50
|
+
import { buildProfileFromProtocolV2, buildProtocolV2FeaturesPayload } from '../src/deviceProfile';
|
|
56
51
|
import {
|
|
57
52
|
getDeviceType,
|
|
58
53
|
getFirmwareType,
|
|
54
|
+
getFirmwareUpdateField,
|
|
55
|
+
getFirmwareUpdateFieldArray,
|
|
59
56
|
getMethodVersionRange,
|
|
60
57
|
isMethodVersionRangeUnsupported,
|
|
61
58
|
} from '../src/utils';
|
|
@@ -74,12 +71,6 @@ jest.mock('../src/data/config', () => ({
|
|
|
74
71
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
75
72
|
}));
|
|
76
73
|
|
|
77
|
-
// DeviceGetDeviceInfo mock 开关默认开启(早期工程板固件没有该消息)。
|
|
78
|
-
// 本测试文件验证的是真实 wire 调用行为,统一关闭;mock 行为单独有用例覆盖。
|
|
79
|
-
beforeAll(() => {
|
|
80
|
-
setProtocolV2DeviceInfoMock(false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
74
|
const descriptor = {
|
|
84
75
|
id: 'ble-id',
|
|
85
76
|
path: 'usb-path',
|
|
@@ -97,12 +88,11 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
97
88
|
d.getCurrentFirmwareType ??= () => getFirmwareType(d.features);
|
|
98
89
|
d.getCurrentFirmwareVersionString ??= () =>
|
|
99
90
|
d.features ? getDeviceFirmwareVersion(d.features).join('.') : undefined;
|
|
100
|
-
d.getCurrentBLEFirmwareVersionString ??= () => d.features?.
|
|
101
|
-
d.getCurrentSafetyChecks ??= () => d.features?.
|
|
102
|
-
d.getCurrentDeviceId ??= () => d.features?.
|
|
103
|
-
d.getCurrentSerialNo ??= () => d.features?.
|
|
104
|
-
d.getCurrentPassphraseProtection ??= () =>
|
|
105
|
-
d.features?.passphrase_protection;
|
|
91
|
+
d.getCurrentBLEFirmwareVersionString ??= () => d.features?.bleVersion;
|
|
92
|
+
d.getCurrentSafetyChecks ??= () => d.features?.safetyChecks;
|
|
93
|
+
d.getCurrentDeviceId ??= () => d.features?.deviceId || undefined;
|
|
94
|
+
d.getCurrentSerialNo ??= () => d.features?.serialNo || '';
|
|
95
|
+
d.getCurrentPassphraseProtection ??= () => d.features?.passphraseProtection;
|
|
106
96
|
d.getCurrentMethodVersionRange ??= (fn: (model: any) => any) => {
|
|
107
97
|
const deviceType = d.getCurrentDeviceType();
|
|
108
98
|
const range = fn(deviceType);
|
|
@@ -121,7 +111,19 @@ function normalizeProtocolV2Features(_descriptor: unknown, deviceInfo?: Protocol
|
|
|
121
111
|
return buildProtocolV2FeaturesPayload(deviceInfo);
|
|
122
112
|
}
|
|
123
113
|
|
|
124
|
-
|
|
114
|
+
const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
|
|
115
|
+
protocol_version: 1,
|
|
116
|
+
hw: {
|
|
117
|
+
serial_no: 'PR9999999999',
|
|
118
|
+
},
|
|
119
|
+
fw: {
|
|
120
|
+
bootloader: {
|
|
121
|
+
version: '0.0.1',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
async function requestProtocolV2Features({
|
|
125
127
|
commands,
|
|
126
128
|
descriptor: inputDescriptor,
|
|
127
129
|
}: {
|
|
@@ -133,30 +135,6 @@ async function requestProtocolV2LegacyFeatures({
|
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
describe('Protocol V2 feature adapter', () => {
|
|
136
|
-
test('skips DeviceGetDeviceInfo wire call and returns mock when mock is enabled', async () => {
|
|
137
|
-
const typedCall = jest.fn();
|
|
138
|
-
setProtocolV2DeviceInfoMock(true);
|
|
139
|
-
try {
|
|
140
|
-
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
141
|
-
commands: { typedCall } as unknown as DeviceCommands,
|
|
142
|
-
});
|
|
143
|
-
expect(typedCall).not.toHaveBeenCalled();
|
|
144
|
-
expect(deviceInfo).toEqual(buildMockProtocolV2DeviceInfo());
|
|
145
|
-
const profile = buildProfileFromProtocolV2({
|
|
146
|
-
deviceInfo,
|
|
147
|
-
});
|
|
148
|
-
expect(profile).toMatchObject({
|
|
149
|
-
protocol: 'V2',
|
|
150
|
-
deviceId: '',
|
|
151
|
-
serialNo: '',
|
|
152
|
-
status: { initialized: true },
|
|
153
|
-
versions: { firmware: '0.1.0' },
|
|
154
|
-
});
|
|
155
|
-
} finally {
|
|
156
|
-
setProtocolV2DeviceInfoMock(false);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
138
|
test('normalizes Protocol V2 DeviceInfo into existing Features fields', () => {
|
|
161
139
|
const features = normalizeProtocolV2Features(descriptor as any, {
|
|
162
140
|
protocol_version: 1,
|
|
@@ -164,31 +142,31 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
164
142
|
serial_no: 'PR2SERIAL',
|
|
165
143
|
},
|
|
166
144
|
fw: {
|
|
167
|
-
|
|
145
|
+
application_data: {
|
|
168
146
|
version: '0.1.0',
|
|
169
147
|
hash: [1, 2, 255],
|
|
170
148
|
},
|
|
171
|
-
|
|
149
|
+
bootloader: {
|
|
172
150
|
version: '0.2.0',
|
|
173
151
|
build_id: 'boot-build',
|
|
174
152
|
hash: new Uint8Array([10, 11]),
|
|
175
153
|
},
|
|
176
|
-
|
|
154
|
+
application: {
|
|
177
155
|
version: '1.2.3',
|
|
178
156
|
build_id: 'app-build',
|
|
179
157
|
hash: 'abc123',
|
|
180
158
|
},
|
|
181
159
|
},
|
|
182
|
-
|
|
183
|
-
|
|
160
|
+
coprocessor: {
|
|
161
|
+
application: {
|
|
184
162
|
version: '4.5.6',
|
|
185
163
|
build_id: 'bt-build',
|
|
186
164
|
hash: [12, 13],
|
|
187
165
|
},
|
|
188
|
-
|
|
166
|
+
bt_adv_name: 'Pro2 BLE',
|
|
189
167
|
},
|
|
190
168
|
se1: {
|
|
191
|
-
|
|
169
|
+
application: {
|
|
192
170
|
version: '7.8.9',
|
|
193
171
|
build_id: 'se-build',
|
|
194
172
|
hash: [14, 15],
|
|
@@ -196,61 +174,79 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
196
174
|
state: 85,
|
|
197
175
|
},
|
|
198
176
|
se2: {
|
|
199
|
-
|
|
177
|
+
application: {
|
|
200
178
|
version: '8.0.0',
|
|
201
179
|
},
|
|
202
180
|
state: 0,
|
|
203
181
|
},
|
|
204
182
|
status: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
bt_enable: true,
|
|
183
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
184
|
+
unlocked: true,
|
|
208
185
|
init_states: false,
|
|
209
186
|
backup_required: true,
|
|
210
|
-
|
|
187
|
+
passphrase_enabled: true,
|
|
188
|
+
attach_to_pin_enabled: true,
|
|
189
|
+
unlocked_by_attach_to_pin: true,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(features.deviceId).toBe('PRO2-DEVICE-ID');
|
|
194
|
+
expect(features.serialNo).toBe('PR2SERIAL');
|
|
195
|
+
expect(features.deviceType).toBe('pro2');
|
|
196
|
+
expect(features.protocolVersion).toBe(1);
|
|
197
|
+
expect(features.firmwareVersion).toBe('1.2.3');
|
|
198
|
+
expect(features.verify?.firmwareBuildId).toBe('app-build');
|
|
199
|
+
expect(features.verify?.firmwareHash).toBe('abc123');
|
|
200
|
+
expect(features.bootloaderVersion).toBe('0.2.0');
|
|
201
|
+
expect(features.verify?.bootloaderBuildId).toBe('boot-build');
|
|
202
|
+
expect(features.verify?.bootloaderHash).toBe('0a0b');
|
|
203
|
+
expect(features.verify?.boardHash).toBe('0102ff');
|
|
204
|
+
expect(features.bleName).toBe('Pro2 BLE');
|
|
205
|
+
expect(features.bleVersion).toBe('4.5.6');
|
|
206
|
+
expect(features.verify?.bleHash).toBe('0c0d');
|
|
207
|
+
expect(features.se01Version).toBe('7.8.9');
|
|
208
|
+
expect(features.verify?.se01Hash).toBe('0e0f');
|
|
209
|
+
expect(features.label).toBeNull();
|
|
210
|
+
expect(features.language).toBeNull();
|
|
211
|
+
expect(features.initialized).toBe(false);
|
|
212
|
+
expect(features.unlocked).toBe(true);
|
|
213
|
+
expect(features.backupRequired).toBe(true);
|
|
214
|
+
expect(features.passphraseProtection).toBe(true);
|
|
215
|
+
expect(features.attachToPinEnabled).toBe(true);
|
|
216
|
+
expect(features.unlockedAttachPin).toBe(true);
|
|
217
|
+
expect(features.bleEnabled).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('marks Protocol V2 DeviceInfo without status as bootloader mode', () => {
|
|
221
|
+
const deviceInfo = {
|
|
222
|
+
protocol_version: 1,
|
|
223
|
+
hw: {
|
|
224
|
+
serial_no: 'PR2BOOT',
|
|
211
225
|
},
|
|
212
|
-
|
|
226
|
+
fw: {
|
|
227
|
+
bootloader: {
|
|
228
|
+
version: '0.2.0',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
const features = normalizeProtocolV2Features(descriptor as any, deviceInfo);
|
|
233
|
+
const profile = buildProfileFromProtocolV2({ deviceInfo });
|
|
213
234
|
|
|
214
|
-
expect(features.
|
|
215
|
-
expect(features.
|
|
216
|
-
expect(features.
|
|
217
|
-
expect(
|
|
218
|
-
expect(
|
|
219
|
-
expect(features.major_version).toBe(1);
|
|
220
|
-
expect(features.minor_version).toBe(2);
|
|
221
|
-
expect(features.patch_version).toBe(3);
|
|
222
|
-
expect(features.onekey_firmware_version).toBe('1.2.3');
|
|
223
|
-
expect(features.onekey_firmware_build_id).toBe('app-build');
|
|
224
|
-
expect(features.onekey_firmware_hash).toBe('abc123');
|
|
225
|
-
expect(features.bootloader_version).toBe('0.2.0');
|
|
226
|
-
expect(features.onekey_boot_build_id).toBe('boot-build');
|
|
227
|
-
expect(features.onekey_boot_hash).toBe('0a0b');
|
|
228
|
-
expect(features.onekey_board_hash).toBe('0102ff');
|
|
229
|
-
expect(features.ble_name).toBe('Pro2 BLE');
|
|
230
|
-
expect(features.onekey_ble_version).toBe('4.5.6');
|
|
231
|
-
expect(features.onekey_ble_hash).toBe('0c0d');
|
|
232
|
-
expect(features.onekey_se01_version).toBe('7.8.9');
|
|
233
|
-
expect(features.onekey_se01_hash).toBe('0e0f');
|
|
234
|
-
expect(features.onekey_se01_state).toBe('APP');
|
|
235
|
-
expect(features.onekey_se02_state).toBe('BOOT');
|
|
236
|
-
expect(features.label).toBe('My Pro2');
|
|
237
|
-
expect(features.language).toBe('en-US');
|
|
238
|
-
expect(features.initialized).toBe(false);
|
|
239
|
-
expect(features.needs_backup).toBe(true);
|
|
240
|
-
expect(features.passphrase_protection).toBe(true);
|
|
241
|
-
expect(features.ble_enable).toBe(true);
|
|
235
|
+
expect(features.mode).toBe('bootloader');
|
|
236
|
+
expect(features.bootloaderMode).toBe(true);
|
|
237
|
+
expect(features.initialized).toBeNull();
|
|
238
|
+
expect(profile.status.mode).toBe('bootloader');
|
|
239
|
+
expect(profile.status.bootloaderMode).toBe(true);
|
|
242
240
|
});
|
|
243
241
|
|
|
244
|
-
test('uses
|
|
242
|
+
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
245
243
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
246
|
-
|
|
247
|
-
features.onekey_firmware_version = '1.2.3';
|
|
244
|
+
features.firmwareVersion = '1.2.3';
|
|
248
245
|
const typedCall = jest.fn().mockResolvedValue({
|
|
249
|
-
type: '
|
|
246
|
+
type: 'DeviceSession',
|
|
250
247
|
message: {
|
|
251
|
-
|
|
248
|
+
btc_test_address: 'state-1',
|
|
252
249
|
session_id: 'session-1',
|
|
253
|
-
unlocked_attach_pin: false,
|
|
254
250
|
},
|
|
255
251
|
});
|
|
256
252
|
const commands = { typedCall } as unknown as DeviceCommands;
|
|
@@ -261,35 +257,28 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
261
257
|
await getPassphraseState(device, {
|
|
262
258
|
expectPassphraseState: 'state-1',
|
|
263
259
|
});
|
|
264
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
265
|
-
passphrase_state: 'state-1',
|
|
266
|
-
});
|
|
260
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
267
261
|
|
|
268
262
|
await getPassphraseState(device, {
|
|
269
263
|
expectPassphraseState: 'state-2',
|
|
270
264
|
allowCreateAttachPin: true,
|
|
271
265
|
});
|
|
272
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
273
|
-
passphrase_state: 'state-2',
|
|
274
|
-
allow_create_attach_pin: true,
|
|
275
|
-
});
|
|
266
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
276
267
|
|
|
277
268
|
await getPassphraseState(device, {
|
|
278
269
|
onlyMainPin: true,
|
|
279
270
|
});
|
|
280
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
281
|
-
_only_main_pin: true,
|
|
282
|
-
});
|
|
271
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
283
272
|
});
|
|
284
273
|
|
|
285
274
|
test('returns unified GetPassphraseState object payload for existing Pro devices', async () => {
|
|
286
275
|
const features = {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
276
|
+
deviceId: 'pro-device-id',
|
|
277
|
+
deviceType: 'pro',
|
|
278
|
+
firmwareVersion: '4.15.0',
|
|
279
|
+
passphraseProtection: true,
|
|
280
|
+
sessionId: 'feature-session',
|
|
281
|
+
unlockedAttachPin: true,
|
|
293
282
|
};
|
|
294
283
|
const typedCall = jest.fn().mockResolvedValue({
|
|
295
284
|
type: 'PassphraseState',
|
|
@@ -307,7 +296,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
307
296
|
},
|
|
308
297
|
});
|
|
309
298
|
method.device = stubDevice({
|
|
310
|
-
originalDescriptor: { ...descriptor, protocolType: '
|
|
299
|
+
originalDescriptor: { ...descriptor, protocolType: 'V1' },
|
|
311
300
|
features,
|
|
312
301
|
commands: { typedCall },
|
|
313
302
|
updateInternalState,
|
|
@@ -316,10 +305,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
316
305
|
}) as any;
|
|
317
306
|
|
|
318
307
|
await expect(method.run()).resolves.toEqual({
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
308
|
+
passphraseState: 'state-pro',
|
|
309
|
+
sessionId: 'session-pro',
|
|
310
|
+
unlockedAttachPin: false,
|
|
311
|
+
passphraseProtection: true,
|
|
323
312
|
});
|
|
324
313
|
expect(updateInternalState).toHaveBeenCalledWith(
|
|
325
314
|
true,
|
|
@@ -332,19 +321,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
332
321
|
|
|
333
322
|
test('uses features for GetPassphraseState response metadata', async () => {
|
|
334
323
|
const features = {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
324
|
+
deviceId: null,
|
|
325
|
+
deviceType: 'pro2',
|
|
326
|
+
firmwareVersion: '4.15.0',
|
|
327
|
+
passphraseProtection: true,
|
|
328
|
+
sessionId: 'feature-session',
|
|
329
|
+
unlockedAttachPin: true,
|
|
341
330
|
};
|
|
342
331
|
const typedCall = jest.fn().mockResolvedValue({
|
|
343
|
-
type: '
|
|
332
|
+
type: 'DeviceSession',
|
|
344
333
|
message: {
|
|
345
|
-
|
|
334
|
+
btc_test_address: 'state-pro2',
|
|
346
335
|
session_id: 'session-pro2',
|
|
347
|
-
unlocked_attach_pin: false,
|
|
348
336
|
},
|
|
349
337
|
});
|
|
350
338
|
const updateInternalState = jest.fn();
|
|
@@ -367,30 +355,79 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
367
355
|
}) as any;
|
|
368
356
|
|
|
369
357
|
await expect(method.run()).resolves.toEqual({
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
358
|
+
passphraseState: 'state-pro2',
|
|
359
|
+
sessionId: 'session-pro2',
|
|
360
|
+
unlockedAttachPin: true,
|
|
361
|
+
passphraseProtection: true,
|
|
374
362
|
});
|
|
375
363
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
376
364
|
});
|
|
377
365
|
|
|
366
|
+
test('honors initSession when getting Pro2 passphrase state', async () => {
|
|
367
|
+
const features = {
|
|
368
|
+
deviceId: 'pro2-device-id',
|
|
369
|
+
deviceType: 'pro2',
|
|
370
|
+
firmwareVersion: '9.9.9',
|
|
371
|
+
passphraseProtection: true,
|
|
372
|
+
sessionId: 'old-feature-session',
|
|
373
|
+
unlockedAttachPin: false,
|
|
374
|
+
};
|
|
375
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
376
|
+
type: 'DeviceSession',
|
|
377
|
+
message: {
|
|
378
|
+
btc_test_address: 'state-pro2-new',
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const clearInternalState = jest.fn();
|
|
382
|
+
const updateInternalState = jest.fn();
|
|
383
|
+
const method = new GetPassphraseState({
|
|
384
|
+
payload: {
|
|
385
|
+
method: 'getPassphraseState',
|
|
386
|
+
connectId: 'connect-id',
|
|
387
|
+
initSession: true,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
method.device = stubDevice({
|
|
391
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
392
|
+
features,
|
|
393
|
+
commands: { typedCall },
|
|
394
|
+
clearInternalState,
|
|
395
|
+
updateInternalState,
|
|
396
|
+
getCurrentDeviceId: () => 'pro2-device-id',
|
|
397
|
+
getCurrentPassphraseProtection: () => true,
|
|
398
|
+
}) as any;
|
|
399
|
+
|
|
400
|
+
await expect(method.run()).resolves.toEqual({
|
|
401
|
+
passphraseState: 'state-pro2-new',
|
|
402
|
+
sessionId: undefined,
|
|
403
|
+
unlockedAttachPin: false,
|
|
404
|
+
passphraseProtection: true,
|
|
405
|
+
});
|
|
406
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
407
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
408
|
+
true,
|
|
409
|
+
'state-pro2-new',
|
|
410
|
+
'pro2-device-id',
|
|
411
|
+
undefined,
|
|
412
|
+
null
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
378
416
|
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
379
417
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
380
418
|
const typedCall = jest.fn().mockResolvedValue({
|
|
381
|
-
type: '
|
|
419
|
+
type: 'DeviceSession',
|
|
382
420
|
message: {
|
|
383
|
-
|
|
421
|
+
btc_test_address: 'state-profile',
|
|
384
422
|
session_id: 'session-profile',
|
|
385
|
-
unlocked_attach_pin: false,
|
|
386
423
|
},
|
|
387
424
|
});
|
|
388
425
|
|
|
389
426
|
(device as any).features = {
|
|
390
427
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
391
428
|
hw: { serial_no: 'PR2SERIAL' },
|
|
392
|
-
fw: {
|
|
393
|
-
status: {
|
|
429
|
+
fw: { application: { version: '4.15.0' } },
|
|
430
|
+
status: { passphrase_enabled: true },
|
|
394
431
|
}),
|
|
395
432
|
unlocked: true,
|
|
396
433
|
};
|
|
@@ -400,17 +437,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
400
437
|
|
|
401
438
|
device.passphraseState = 'state-profile';
|
|
402
439
|
expect(device.getInternalState()).toBe('session-profile');
|
|
403
|
-
expect(device.getInternalState('
|
|
440
|
+
expect(device.getInternalState('CACHED-ID')).toBeUndefined();
|
|
404
441
|
});
|
|
405
442
|
|
|
406
443
|
test('stores Pro2 passphrase sessions without selecting them implicitly', async () => {
|
|
407
444
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
408
445
|
const typedCall = jest.fn().mockResolvedValue({
|
|
409
|
-
type: '
|
|
446
|
+
type: 'DeviceSession',
|
|
410
447
|
message: {
|
|
411
|
-
|
|
448
|
+
btc_test_address: 'state-auto',
|
|
412
449
|
session_id: 'session-auto',
|
|
413
|
-
unlocked_attach_pin: false,
|
|
414
450
|
},
|
|
415
451
|
});
|
|
416
452
|
|
|
@@ -423,8 +459,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
423
459
|
hw: { serial_no: 'PR2SERIAL' },
|
|
424
460
|
}
|
|
425
461
|
);
|
|
426
|
-
(device as any).features.
|
|
427
|
-
(device as any).features.
|
|
462
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
463
|
+
(device as any).features.passphraseProtection = true;
|
|
428
464
|
(device as any).features.unlocked = true;
|
|
429
465
|
(device as any).commands = { typedCall };
|
|
430
466
|
|
|
@@ -434,23 +470,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
434
470
|
});
|
|
435
471
|
|
|
436
472
|
expect(device.passphraseState).toBeUndefined();
|
|
437
|
-
expect(device.features?.
|
|
438
|
-
expect(device.features?.
|
|
473
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
474
|
+
expect(device.features?.sessionId).toBeNull();
|
|
439
475
|
expect(device.getInternalState()).toBeUndefined();
|
|
440
476
|
device.passphraseState = 'state-auto';
|
|
441
477
|
expect(device.getInternalState()).toBe('session-auto');
|
|
442
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
443
|
-
passphrase_state: undefined,
|
|
444
|
-
});
|
|
478
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
445
479
|
});
|
|
446
480
|
|
|
447
481
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
448
482
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
449
483
|
const typedCall = jest.fn().mockResolvedValue({
|
|
450
|
-
type: '
|
|
484
|
+
type: 'DeviceSession',
|
|
451
485
|
message: {
|
|
452
486
|
session_id: 'main-pin-session',
|
|
453
|
-
unlocked_attach_pin: false,
|
|
454
487
|
},
|
|
455
488
|
});
|
|
456
489
|
|
|
@@ -461,11 +494,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
461
494
|
} as any,
|
|
462
495
|
{
|
|
463
496
|
status: {
|
|
464
|
-
|
|
497
|
+
passphrase_enabled: false,
|
|
465
498
|
},
|
|
466
499
|
}
|
|
467
500
|
);
|
|
468
|
-
(device as any).features.
|
|
501
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
469
502
|
(device as any).features.unlocked = true;
|
|
470
503
|
(device as any).commands = { typedCall };
|
|
471
504
|
|
|
@@ -476,22 +509,19 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
476
509
|
newSession: 'main-pin-session',
|
|
477
510
|
});
|
|
478
511
|
|
|
479
|
-
expect(device.features?.
|
|
480
|
-
expect(device.features?.
|
|
512
|
+
expect(device.features?.passphraseProtection).toBe(false);
|
|
513
|
+
expect(device.features?.sessionId).toBeNull();
|
|
481
514
|
expect(device.getInternalState()).toBeUndefined();
|
|
482
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
483
|
-
_only_main_pin: true,
|
|
484
|
-
});
|
|
515
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
485
516
|
});
|
|
486
517
|
|
|
487
518
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
488
519
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
489
520
|
const typedCall = jest.fn().mockResolvedValue({
|
|
490
|
-
type: '
|
|
521
|
+
type: 'DeviceSession',
|
|
491
522
|
message: {
|
|
492
|
-
|
|
523
|
+
btc_test_address: 'wrong-state',
|
|
493
524
|
session_id: 'wrong-session',
|
|
494
|
-
unlocked_attach_pin: false,
|
|
495
525
|
},
|
|
496
526
|
});
|
|
497
527
|
|
|
@@ -499,8 +529,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
499
529
|
...descriptor,
|
|
500
530
|
protocolType: 'V2',
|
|
501
531
|
} as any);
|
|
502
|
-
(device as any).features.
|
|
503
|
-
(device as any).features.
|
|
532
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
533
|
+
(device as any).features.passphraseProtection = true;
|
|
504
534
|
(device as any).commands = { typedCall };
|
|
505
535
|
|
|
506
536
|
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).resolves.toBe(
|
|
@@ -508,23 +538,41 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
508
538
|
);
|
|
509
539
|
|
|
510
540
|
expect(device.getInternalState()).toBeUndefined();
|
|
511
|
-
expect(typedCall).toHaveBeenNthCalledWith(1, '
|
|
512
|
-
passphrase_state: 'expected-state',
|
|
513
|
-
});
|
|
541
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'DeviceSessionGet', 'DeviceSession', {});
|
|
514
542
|
});
|
|
515
543
|
|
|
516
544
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
517
545
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
518
546
|
|
|
519
|
-
expect(features.
|
|
520
|
-
expect(features.
|
|
521
|
-
expect(features.onekey_serial_no).toBeUndefined();
|
|
547
|
+
expect(features.deviceId).toBeNull();
|
|
548
|
+
expect(features.serialNo).toBe('');
|
|
522
549
|
expect(features.initialized).toBeNull();
|
|
523
550
|
expect(features.unlocked).toBeNull();
|
|
524
|
-
expect(features.
|
|
551
|
+
expect(features.firmwarePresent).toBeNull();
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('uses Protocol V2 status.device_id and does not fall back to serial_no', () => {
|
|
555
|
+
const features = normalizeProtocolV2Features(
|
|
556
|
+
{
|
|
557
|
+
id: 'PR2000000000',
|
|
558
|
+
path: 'PR2000000000',
|
|
559
|
+
protocolType: 'V2',
|
|
560
|
+
} as any,
|
|
561
|
+
{
|
|
562
|
+
hw: {
|
|
563
|
+
serial_no: 'PR9999999999',
|
|
564
|
+
},
|
|
565
|
+
status: {
|
|
566
|
+
device_id: 'DEVICE-ID-9999',
|
|
567
|
+
},
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
expect(features.deviceId).toBe('DEVICE-ID-9999');
|
|
572
|
+
expect(features.serialNo).toBe('PR9999999999');
|
|
525
573
|
});
|
|
526
574
|
|
|
527
|
-
test('does not use Protocol V2 serial_no as device_id', () => {
|
|
575
|
+
test('does not use Protocol V2 serial_no as deviceId when hw.device_id is absent', () => {
|
|
528
576
|
const features = normalizeProtocolV2Features(
|
|
529
577
|
{
|
|
530
578
|
id: 'PR2000000000',
|
|
@@ -538,26 +586,25 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
538
586
|
}
|
|
539
587
|
);
|
|
540
588
|
|
|
541
|
-
expect(features.
|
|
542
|
-
expect(features.
|
|
543
|
-
expect(features.serial_no).toBe('PR9999999999');
|
|
589
|
+
expect(features.deviceId).toBeNull();
|
|
590
|
+
expect(features.serialNo).toBe('PR9999999999');
|
|
544
591
|
});
|
|
545
592
|
|
|
546
593
|
test('uses Protocol V2 features directly when profile is absent', () => {
|
|
547
594
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
548
595
|
(device as any).features = {
|
|
549
596
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
550
|
-
|
|
551
|
-
label: '
|
|
552
|
-
|
|
553
|
-
|
|
597
|
+
serialNo: 'CACHED-SERIAL',
|
|
598
|
+
label: 'Cached Label',
|
|
599
|
+
bleName: 'Cached BLE',
|
|
600
|
+
passphraseProtection: true,
|
|
554
601
|
};
|
|
555
602
|
|
|
556
603
|
expect(device.toMessageObject()).toMatchObject({
|
|
557
|
-
uuid: '
|
|
604
|
+
uuid: 'CACHED-SERIAL',
|
|
558
605
|
deviceId: null,
|
|
559
|
-
bleName: '
|
|
560
|
-
label: '
|
|
606
|
+
bleName: 'Cached BLE',
|
|
607
|
+
label: 'Cached Label',
|
|
561
608
|
deviceType: 'pro2',
|
|
562
609
|
});
|
|
563
610
|
expect(device.getCurrentPassphraseProtection()).toBe(true);
|
|
@@ -568,54 +615,54 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
568
615
|
const cached = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
569
616
|
(cached as any).features = {
|
|
570
617
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
onekey_serial_no: 'LEGACY-SERIAL',
|
|
618
|
+
deviceId: null,
|
|
619
|
+
serialNo: 'CACHED-SERIAL',
|
|
574
620
|
};
|
|
575
621
|
|
|
576
622
|
const current = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
577
623
|
current.updateFromCache(cached);
|
|
578
624
|
|
|
579
625
|
expect(current.getCurrentDeviceId()).toBeUndefined();
|
|
580
|
-
expect(current.getCurrentSerialNo()).toBe('
|
|
626
|
+
expect(current.getCurrentSerialNo()).toBe('CACHED-SERIAL');
|
|
581
627
|
expect(current.toMessageObject()).toMatchObject({
|
|
582
|
-
uuid: '
|
|
628
|
+
uuid: 'CACHED-SERIAL',
|
|
583
629
|
deviceId: null,
|
|
584
630
|
});
|
|
585
631
|
});
|
|
586
632
|
|
|
587
|
-
test('initializes Protocol V2 features from lightweight
|
|
633
|
+
test('initializes Protocol V2 features from lightweight DeviceInfoGet', async () => {
|
|
588
634
|
const commands = {
|
|
589
635
|
typedCall: jest.fn().mockResolvedValueOnce({
|
|
590
636
|
type: 'DeviceInfo',
|
|
591
637
|
message: {
|
|
592
638
|
hw: { serial_no: 'PR2SERIAL' },
|
|
593
639
|
status: {
|
|
640
|
+
device_id: 'PRO2-STATUS-ID',
|
|
594
641
|
init_states: true,
|
|
595
|
-
|
|
642
|
+
passphrase_enabled: true,
|
|
596
643
|
},
|
|
597
644
|
},
|
|
598
645
|
}),
|
|
599
646
|
};
|
|
600
647
|
|
|
601
|
-
const features = await
|
|
648
|
+
const features = await requestProtocolV2Features({
|
|
602
649
|
commands: commands as unknown as DeviceCommands,
|
|
603
650
|
descriptor: descriptor as any,
|
|
604
651
|
});
|
|
605
652
|
|
|
606
|
-
expect(features.
|
|
653
|
+
expect(features.deviceId).toBe('PRO2-STATUS-ID');
|
|
607
654
|
expect(features.initialized).toBe(true);
|
|
608
|
-
expect(features.
|
|
655
|
+
expect(features.passphraseProtection).toBe(true);
|
|
609
656
|
expect(commands.typedCall).toHaveBeenCalledTimes(1);
|
|
610
657
|
expect(commands.typedCall).toHaveBeenNthCalledWith(
|
|
611
658
|
1,
|
|
612
|
-
'
|
|
659
|
+
'DeviceInfoGet',
|
|
613
660
|
'DeviceInfo',
|
|
614
661
|
{
|
|
615
662
|
targets: {
|
|
616
663
|
hw: true,
|
|
617
664
|
fw: true,
|
|
618
|
-
|
|
665
|
+
coprocessor: true,
|
|
619
666
|
status: true,
|
|
620
667
|
},
|
|
621
668
|
types: {
|
|
@@ -627,13 +674,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
627
674
|
);
|
|
628
675
|
});
|
|
629
676
|
|
|
630
|
-
test('fails initialization when Protocol V2
|
|
677
|
+
test('fails initialization when Protocol V2 DeviceInfoGet fails', async () => {
|
|
631
678
|
const commands = {
|
|
632
679
|
typedCall: jest.fn().mockRejectedValueOnce(new Error('DeviceInfo not supported')),
|
|
633
680
|
};
|
|
634
681
|
|
|
635
682
|
await expect(
|
|
636
|
-
|
|
683
|
+
requestProtocolV2Features({
|
|
637
684
|
commands: commands as unknown as DeviceCommands,
|
|
638
685
|
descriptor: descriptor as any,
|
|
639
686
|
})
|
|
@@ -646,20 +693,21 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
646
693
|
message: {
|
|
647
694
|
hw: { serial_no: 'PR2SERIAL' },
|
|
648
695
|
fw: {
|
|
649
|
-
|
|
696
|
+
application: {
|
|
650
697
|
version: '5.6.7',
|
|
651
698
|
},
|
|
652
699
|
},
|
|
653
|
-
|
|
654
|
-
|
|
700
|
+
coprocessor: {
|
|
701
|
+
application: {
|
|
655
702
|
version: '8.9.10',
|
|
656
703
|
},
|
|
657
|
-
|
|
704
|
+
bt_adv_name: 'Raw Pro2 BLE',
|
|
658
705
|
},
|
|
659
706
|
status: {
|
|
707
|
+
device_id: 'PRO2-BASIC-ID',
|
|
708
|
+
unlocked_by_attach_to_pin: true,
|
|
660
709
|
init_states: true,
|
|
661
|
-
|
|
662
|
-
passphrase_protection: true,
|
|
710
|
+
passphrase_enabled: true,
|
|
663
711
|
},
|
|
664
712
|
},
|
|
665
713
|
});
|
|
@@ -676,9 +724,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
676
724
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
677
725
|
features: {
|
|
678
726
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
679
|
-
device_id: '
|
|
680
|
-
serial_no: '
|
|
681
|
-
label: '
|
|
727
|
+
device_id: 'CACHED-ID',
|
|
728
|
+
serial_no: 'CACHED-SERIAL',
|
|
729
|
+
label: 'Cached Pro2',
|
|
682
730
|
onekey_firmware_version: '1.2.3',
|
|
683
731
|
onekey_ble_version: '2.3.4',
|
|
684
732
|
},
|
|
@@ -689,13 +737,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
689
737
|
const result = await method.run();
|
|
690
738
|
|
|
691
739
|
expect(typedCall).toHaveBeenCalledWith(
|
|
692
|
-
'
|
|
740
|
+
'DeviceInfoGet',
|
|
693
741
|
'DeviceInfo',
|
|
694
742
|
{
|
|
695
743
|
targets: {
|
|
696
744
|
hw: true,
|
|
697
745
|
fw: true,
|
|
698
|
-
|
|
746
|
+
coprocessor: true,
|
|
699
747
|
status: true,
|
|
700
748
|
},
|
|
701
749
|
types: {
|
|
@@ -708,12 +756,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
708
756
|
expect(result).toMatchObject({
|
|
709
757
|
protocol: 'V2',
|
|
710
758
|
deviceType: 'pro2',
|
|
711
|
-
deviceId: '',
|
|
759
|
+
deviceId: 'PRO2-BASIC-ID',
|
|
712
760
|
serialNo: 'PR2SERIAL',
|
|
713
|
-
label:
|
|
761
|
+
label: null,
|
|
714
762
|
bleName: 'Raw Pro2 BLE',
|
|
715
763
|
status: {
|
|
716
764
|
initialized: true,
|
|
765
|
+
unlockedAttachPin: true,
|
|
717
766
|
passphraseProtection: true,
|
|
718
767
|
},
|
|
719
768
|
versions: {
|
|
@@ -723,7 +772,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
723
772
|
});
|
|
724
773
|
});
|
|
725
774
|
|
|
726
|
-
test('does not fill Protocol V2 DeviceProfile from
|
|
775
|
+
test('does not fill Protocol V2 DeviceProfile from cached V1-shaped fields', async () => {
|
|
727
776
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
728
777
|
type: 'DeviceInfo',
|
|
729
778
|
message: {
|
|
@@ -744,10 +793,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
744
793
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
745
794
|
features: {
|
|
746
795
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
747
|
-
device_id: '
|
|
748
|
-
serial_no: '
|
|
749
|
-
label: '
|
|
750
|
-
ble_name: '
|
|
796
|
+
device_id: 'STALE-ID',
|
|
797
|
+
serial_no: 'STALE-SERIAL',
|
|
798
|
+
label: 'Stale Pro2',
|
|
799
|
+
ble_name: 'Stale BLE',
|
|
751
800
|
passphrase_protection: true,
|
|
752
801
|
onekey_firmware_version: '1.2.3',
|
|
753
802
|
onekey_ble_version: '2.3.4',
|
|
@@ -759,7 +808,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
759
808
|
|
|
760
809
|
expect(result).toMatchObject({
|
|
761
810
|
protocol: 'V2',
|
|
762
|
-
//
|
|
811
|
+
// 身份字段不得取自缓存的 V1-shaped features(STALE-ID / STALE-SERIAL),
|
|
763
812
|
// hw.serial_no 缺失时也不回退 descriptor.path。
|
|
764
813
|
deviceId: '',
|
|
765
814
|
serialNo: '',
|
|
@@ -802,13 +851,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
802
851
|
await method.run();
|
|
803
852
|
|
|
804
853
|
expect(typedCall).toHaveBeenCalledWith(
|
|
805
|
-
'
|
|
854
|
+
'DeviceInfoGet',
|
|
806
855
|
'DeviceInfo',
|
|
807
856
|
{
|
|
808
857
|
targets: {
|
|
809
858
|
hw: true,
|
|
810
859
|
fw: true,
|
|
811
|
-
|
|
860
|
+
coprocessor: true,
|
|
812
861
|
se1: true,
|
|
813
862
|
se2: true,
|
|
814
863
|
se3: true,
|
|
@@ -832,12 +881,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
832
881
|
message: {
|
|
833
882
|
hw: { serial_no: 'PR2SERIAL' },
|
|
834
883
|
se1: {
|
|
835
|
-
|
|
836
|
-
|
|
884
|
+
application: { version: '1.0.1' },
|
|
885
|
+
bootloader: { version: '1.0.0' },
|
|
837
886
|
},
|
|
838
887
|
se2: {
|
|
839
|
-
|
|
840
|
-
|
|
888
|
+
application: { version: '2.0.1' },
|
|
889
|
+
bootloader: { version: '2.0.0' },
|
|
841
890
|
},
|
|
842
891
|
status: { init_states: true },
|
|
843
892
|
},
|
|
@@ -859,13 +908,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
859
908
|
const result = await method.run();
|
|
860
909
|
|
|
861
910
|
expect(typedCall).toHaveBeenCalledWith(
|
|
862
|
-
'
|
|
911
|
+
'DeviceInfoGet',
|
|
863
912
|
'DeviceInfo',
|
|
864
913
|
{
|
|
865
914
|
targets: {
|
|
866
915
|
hw: true,
|
|
867
916
|
fw: true,
|
|
868
|
-
|
|
917
|
+
coprocessor: true,
|
|
869
918
|
se1: true,
|
|
870
919
|
se2: true,
|
|
871
920
|
se3: true,
|
|
@@ -904,10 +953,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
904
953
|
expect(checkedTypes).toEqual(['pro2']);
|
|
905
954
|
});
|
|
906
955
|
|
|
956
|
+
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
957
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any);
|
|
958
|
+
|
|
959
|
+
expect(
|
|
960
|
+
getFirmwareUpdateField({
|
|
961
|
+
features,
|
|
962
|
+
updateType: 'firmware',
|
|
963
|
+
firmwareType: 'universal',
|
|
964
|
+
})
|
|
965
|
+
).toBe('firmware-v1');
|
|
966
|
+
expect(getFirmwareUpdateFieldArray(features, 'firmware')).toEqual(['firmware-v1']);
|
|
967
|
+
});
|
|
968
|
+
|
|
907
969
|
test('marks known unsupported public-chain methods as unsupported on Protocol V2', () => {
|
|
908
970
|
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
909
971
|
fw: {
|
|
910
|
-
|
|
972
|
+
application: {
|
|
911
973
|
version: '9.9.9',
|
|
912
974
|
},
|
|
913
975
|
},
|
|
@@ -957,7 +1019,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
957
1019
|
expect(isMethodVersionRangeUnsupported(neuraiRange)).toBe(true);
|
|
958
1020
|
});
|
|
959
1021
|
|
|
960
|
-
test('does not block
|
|
1022
|
+
test('does not block batch public key support checks on Protocol V2', async () => {
|
|
961
1023
|
const paths = [{ address_n: [0x8000002c, 0x80000000, 0x80000000] }] as any;
|
|
962
1024
|
const typedCall = jest.fn().mockResolvedValue({
|
|
963
1025
|
type: 'EcdsaPublicKeys',
|
|
@@ -971,7 +1033,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
971
1033
|
originalDescriptor: { protocolType: 'V2' },
|
|
972
1034
|
features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
973
1035
|
fw: {
|
|
974
|
-
|
|
1036
|
+
application: {
|
|
975
1037
|
version: '4.14.0',
|
|
976
1038
|
},
|
|
977
1039
|
},
|
|
@@ -993,7 +1055,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
993
1055
|
});
|
|
994
1056
|
});
|
|
995
1057
|
|
|
996
|
-
test('
|
|
1058
|
+
test('does not call V1 OnekeyGetFeatures for Protocol V2 devices', async () => {
|
|
997
1059
|
const method = new GetOnekeyFeatures({
|
|
998
1060
|
id: 1,
|
|
999
1061
|
payload: {
|
|
@@ -1005,9 +1067,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1005
1067
|
message: {
|
|
1006
1068
|
protocol_version: 1,
|
|
1007
1069
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1008
|
-
fw: {
|
|
1009
|
-
|
|
1010
|
-
status: {
|
|
1070
|
+
fw: { application: { version: '1.2.3', build_id: 'app-build' } },
|
|
1071
|
+
coprocessor: { bt_adv_name: 'Pro2 BLE' },
|
|
1072
|
+
status: { init_states: true },
|
|
1011
1073
|
},
|
|
1012
1074
|
});
|
|
1013
1075
|
|
|
@@ -1018,10 +1080,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1018
1080
|
|
|
1019
1081
|
const message = await method.run();
|
|
1020
1082
|
|
|
1021
|
-
// V2 走
|
|
1083
|
+
// V2 走 DeviceInfoGet 完整请求(含 SE 与 hash/build_id),而不是 V1 OnekeyGetFeatures
|
|
1022
1084
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1023
1085
|
expect(typedCall).toHaveBeenCalledWith(
|
|
1024
|
-
'
|
|
1086
|
+
'DeviceInfoGet',
|
|
1025
1087
|
'DeviceInfo',
|
|
1026
1088
|
expect.objectContaining({
|
|
1027
1089
|
targets: expect.objectContaining({ se1: true, se2: true, se3: true, se4: true }),
|
|
@@ -1029,13 +1091,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1029
1091
|
}),
|
|
1030
1092
|
expect.anything()
|
|
1031
1093
|
);
|
|
1032
|
-
expect(message).
|
|
1033
|
-
onekey_device_type: 'pro2',
|
|
1034
|
-
onekey_firmware_version: '1.2.3',
|
|
1035
|
-
onekey_firmware_build_id: 'app-build',
|
|
1036
|
-
onekey_serial_no: 'PR2SERIAL',
|
|
1037
|
-
onekey_ble_name: 'Pro2 BLE',
|
|
1038
|
-
});
|
|
1094
|
+
expect(message).toEqual({});
|
|
1039
1095
|
expect(message).not.toHaveProperty('label');
|
|
1040
1096
|
});
|
|
1041
1097
|
|
|
@@ -1050,9 +1106,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1050
1106
|
type: 'DeviceInfo',
|
|
1051
1107
|
message: {
|
|
1052
1108
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1053
|
-
fw: {
|
|
1109
|
+
fw: { application: { version: '1.2.3' } },
|
|
1054
1110
|
status: {
|
|
1055
|
-
|
|
1111
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1112
|
+
passphrase_enabled: true,
|
|
1056
1113
|
},
|
|
1057
1114
|
},
|
|
1058
1115
|
})
|
|
@@ -1062,8 +1119,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1062
1119
|
message: {
|
|
1063
1120
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1064
1121
|
status: {
|
|
1065
|
-
|
|
1066
|
-
|
|
1122
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1123
|
+
passphrase_enabled: false,
|
|
1067
1124
|
},
|
|
1068
1125
|
},
|
|
1069
1126
|
});
|
|
@@ -1074,27 +1131,27 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1074
1131
|
await device.initialize();
|
|
1075
1132
|
|
|
1076
1133
|
expect(device.features).toMatchObject({
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
label:
|
|
1134
|
+
deviceId: 'PRO2-REFRESH-ID',
|
|
1135
|
+
firmwareVersion: '1.2.3',
|
|
1136
|
+
passphraseProtection: false,
|
|
1137
|
+
label: null,
|
|
1081
1138
|
});
|
|
1082
1139
|
expect((device as any).profile).toBeUndefined();
|
|
1083
1140
|
// status 字段被第二次刷新更新
|
|
1084
|
-
expect(device.features?.
|
|
1085
|
-
expect(device.features?.label).
|
|
1141
|
+
expect(device.features?.passphraseProtection).toBe(false);
|
|
1142
|
+
expect(device.features?.label).toBeNull();
|
|
1086
1143
|
// 轻量刷新不含 fw target,已有版本信息按字段级合并保留
|
|
1087
|
-
expect(device.features?.
|
|
1144
|
+
expect(device.features?.firmwareVersion).toBe('1.2.3');
|
|
1088
1145
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1089
1146
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1090
1147
|
1,
|
|
1091
|
-
'
|
|
1148
|
+
'DeviceInfoGet',
|
|
1092
1149
|
'DeviceInfo',
|
|
1093
1150
|
{
|
|
1094
1151
|
targets: {
|
|
1095
1152
|
hw: true,
|
|
1096
1153
|
fw: true,
|
|
1097
|
-
|
|
1154
|
+
coprocessor: true,
|
|
1098
1155
|
status: true,
|
|
1099
1156
|
},
|
|
1100
1157
|
types: {
|
|
@@ -1106,15 +1163,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1106
1163
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
1107
1164
|
}
|
|
1108
1165
|
);
|
|
1109
|
-
// 第二次为 status-only 轻量请求(hw/
|
|
1166
|
+
// 第二次为 status-only 轻量请求(hw/coprocessor 仅用于身份字段,避免顶层覆盖清空)
|
|
1110
1167
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1111
1168
|
2,
|
|
1112
|
-
'
|
|
1169
|
+
'DeviceInfoGet',
|
|
1113
1170
|
'DeviceInfo',
|
|
1114
1171
|
{
|
|
1115
1172
|
targets: {
|
|
1116
1173
|
hw: true,
|
|
1117
|
-
|
|
1174
|
+
coprocessor: true,
|
|
1118
1175
|
status: true,
|
|
1119
1176
|
},
|
|
1120
1177
|
types: {
|
|
@@ -1128,7 +1185,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1128
1185
|
);
|
|
1129
1186
|
});
|
|
1130
1187
|
|
|
1131
|
-
test('refreshes Protocol V2 features without falling back to
|
|
1188
|
+
test('refreshes Protocol V2 features without falling back to V1 GetFeatures', async () => {
|
|
1132
1189
|
const device = Device.fromDescriptor({
|
|
1133
1190
|
path: 'usb-path',
|
|
1134
1191
|
protocolType: 'V2',
|
|
@@ -1139,7 +1196,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1139
1196
|
type: 'DeviceInfo',
|
|
1140
1197
|
message: {
|
|
1141
1198
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1142
|
-
fw: {
|
|
1199
|
+
fw: { application: { version: '1.2.3' } },
|
|
1143
1200
|
status: { init_states: true },
|
|
1144
1201
|
},
|
|
1145
1202
|
})
|
|
@@ -1147,8 +1204,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1147
1204
|
type: 'DeviceInfo',
|
|
1148
1205
|
message: {
|
|
1149
1206
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1150
|
-
fw: {
|
|
1151
|
-
status: { init_states: true,
|
|
1207
|
+
fw: { application: { version: '1.2.4' } },
|
|
1208
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1152
1209
|
},
|
|
1153
1210
|
});
|
|
1154
1211
|
|
|
@@ -1158,26 +1215,26 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1158
1215
|
const features = await device.getFeatures();
|
|
1159
1216
|
|
|
1160
1217
|
expect(device.features).toMatchObject({
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1218
|
+
deviceId: null,
|
|
1219
|
+
firmwareVersion: '1.2.4',
|
|
1220
|
+
passphraseProtection: true,
|
|
1164
1221
|
});
|
|
1165
1222
|
expect(features).toMatchObject({
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1223
|
+
deviceType: 'pro2',
|
|
1224
|
+
serialNo: 'PR2SERIAL',
|
|
1225
|
+
firmwareVersion: '1.2.4',
|
|
1226
|
+
passphraseProtection: true,
|
|
1170
1227
|
});
|
|
1171
1228
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1172
1229
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1173
1230
|
2,
|
|
1174
|
-
'
|
|
1231
|
+
'DeviceInfoGet',
|
|
1175
1232
|
'DeviceInfo',
|
|
1176
1233
|
{
|
|
1177
1234
|
targets: {
|
|
1178
1235
|
hw: true,
|
|
1179
1236
|
fw: true,
|
|
1180
|
-
|
|
1237
|
+
coprocessor: true,
|
|
1181
1238
|
status: true,
|
|
1182
1239
|
},
|
|
1183
1240
|
types: {
|
|
@@ -1190,14 +1247,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1190
1247
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1191
1248
|
});
|
|
1192
1249
|
|
|
1193
|
-
test('keeps Protocol V2 features available for
|
|
1250
|
+
test('keeps Protocol V2 features available for method internals such as evmSignTypedData', async () => {
|
|
1194
1251
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1195
1252
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
1196
1253
|
type: 'DeviceInfo',
|
|
1197
1254
|
message: {
|
|
1198
1255
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1199
|
-
fw: {
|
|
1200
|
-
status: { init_states: true,
|
|
1256
|
+
fw: { application: { version: '1.2.4' } },
|
|
1257
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1201
1258
|
},
|
|
1202
1259
|
});
|
|
1203
1260
|
(device as any).commands = { typedCall };
|
|
@@ -1251,7 +1308,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1251
1308
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1252
1309
|
{
|
|
1253
1310
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1254
|
-
fw: {
|
|
1311
|
+
fw: { application: { version: '1.2.3' } },
|
|
1255
1312
|
status: { init_states: true },
|
|
1256
1313
|
}
|
|
1257
1314
|
);
|
|
@@ -1262,9 +1319,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1262
1319
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1263
1320
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1264
1321
|
expect(features).toMatchObject({
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1322
|
+
deviceType: 'pro2',
|
|
1323
|
+
deviceId: null,
|
|
1324
|
+
firmwareVersion: '1.2.3',
|
|
1268
1325
|
unlocked: true,
|
|
1269
1326
|
});
|
|
1270
1327
|
});
|
|
@@ -1275,14 +1332,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1275
1332
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1276
1333
|
{
|
|
1277
1334
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1278
|
-
fw: {
|
|
1279
|
-
status: {
|
|
1335
|
+
fw: { application: { version: '4.15.0' } },
|
|
1336
|
+
status: { passphrase_enabled: false },
|
|
1280
1337
|
}
|
|
1281
1338
|
);
|
|
1282
1339
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1283
1340
|
type: 'UnLockDeviceResponse',
|
|
1284
1341
|
message: {
|
|
1285
1342
|
unlocked: true,
|
|
1343
|
+
unlocked_attach_pin: true,
|
|
1286
1344
|
passphrase_protection: true,
|
|
1287
1345
|
},
|
|
1288
1346
|
});
|
|
@@ -1291,7 +1349,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1291
1349
|
await device.unlockDevice();
|
|
1292
1350
|
|
|
1293
1351
|
expect((device as any).profile).toBeUndefined();
|
|
1294
|
-
expect(device.features?.
|
|
1352
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
1353
|
+
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
1295
1354
|
});
|
|
1296
1355
|
});
|
|
1297
1356
|
|
|
@@ -1310,7 +1369,7 @@ describe('API compatibility handling', () => {
|
|
|
1310
1369
|
method.init();
|
|
1311
1370
|
(method as any).device = stubDevice({
|
|
1312
1371
|
features: {
|
|
1313
|
-
|
|
1372
|
+
deviceType: 'pro2',
|
|
1314
1373
|
},
|
|
1315
1374
|
originalDescriptor: {
|
|
1316
1375
|
protocolType: 'V2',
|
|
@@ -1328,27 +1387,9 @@ describe('API compatibility handling', () => {
|
|
|
1328
1387
|
);
|
|
1329
1388
|
});
|
|
1330
1389
|
|
|
1331
|
-
test('
|
|
1332
|
-
const method = new TronSignMessage({
|
|
1333
|
-
id: 1,
|
|
1334
|
-
payload: {
|
|
1335
|
-
method: 'tronSignMessage',
|
|
1336
|
-
path: "m/44'/195'/0'/0/0",
|
|
1337
|
-
messageHex: '0x1234',
|
|
1338
|
-
messageType: 'V1',
|
|
1339
|
-
},
|
|
1340
|
-
});
|
|
1341
|
-
|
|
1342
|
-
expect(() => method.init()).toThrow(
|
|
1343
|
-
expect.objectContaining({
|
|
1344
|
-
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1345
|
-
})
|
|
1346
|
-
);
|
|
1347
|
-
});
|
|
1348
|
-
|
|
1349
|
-
test('does not mark Pro2 Tron, Solana, TON, SUI and Polkadot methods as unsupported', () => {
|
|
1390
|
+
test('does not mark supported Pro2 Tron, Solana, TON, SUI and Polkadot methods as unsupported', () => {
|
|
1350
1391
|
const features = {
|
|
1351
|
-
|
|
1392
|
+
deviceType: 'pro2',
|
|
1352
1393
|
} as Features;
|
|
1353
1394
|
|
|
1354
1395
|
const tronMethod = new TronGetAddress({
|
|
@@ -1467,9 +1508,7 @@ describe('API compatibility handling', () => {
|
|
|
1467
1508
|
});
|
|
1468
1509
|
expect(
|
|
1469
1510
|
getMethodVersionRange(features, type => tonSignDataMethod.getVersionRange()[type])
|
|
1470
|
-
).
|
|
1471
|
-
min: '0.0.0',
|
|
1472
|
-
});
|
|
1511
|
+
).toBeUndefined();
|
|
1473
1512
|
expect(
|
|
1474
1513
|
getMethodVersionRange(features, type => suiGetAddressMethod.getVersionRange()[type])
|
|
1475
1514
|
).toEqual({
|
|
@@ -1499,7 +1538,7 @@ describe('API compatibility handling', () => {
|
|
|
1499
1538
|
|
|
1500
1539
|
test('allows Pro2 Solana signing methods through Protocol V2 version checks', () => {
|
|
1501
1540
|
const features = {
|
|
1502
|
-
|
|
1541
|
+
deviceType: 'pro2',
|
|
1503
1542
|
} as Features;
|
|
1504
1543
|
|
|
1505
1544
|
const solSignMessageMethod = new SolSignMessage({
|
|
@@ -1563,15 +1602,6 @@ describe('API compatibility handling', () => {
|
|
|
1563
1602
|
});
|
|
1564
1603
|
});
|
|
1565
1604
|
|
|
1566
|
-
test('includes TON signData in the Protocol V2 protobuf schema', () => {
|
|
1567
|
-
const protocolV2Messages = DataManager.getProtobufMessages('v2Schema') as any;
|
|
1568
|
-
|
|
1569
|
-
expect(protocolV2Messages.nested.TonSignData).toBeDefined();
|
|
1570
|
-
expect(protocolV2Messages.nested.TonSignedData).toBeDefined();
|
|
1571
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignData).toBe(11908);
|
|
1572
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignedData).toBe(11909);
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
1605
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
1576
1606
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
1577
1607
|
const typedCall = jest.fn(() => ({
|
|
@@ -1593,7 +1623,7 @@ describe('API compatibility handling', () => {
|
|
|
1593
1623
|
method.init();
|
|
1594
1624
|
(method as any).device = stubDevice({
|
|
1595
1625
|
features: {
|
|
1596
|
-
|
|
1626
|
+
deviceType: 'pro2',
|
|
1597
1627
|
},
|
|
1598
1628
|
originalDescriptor: {
|
|
1599
1629
|
protocolType: 'V2',
|
|
@@ -1616,54 +1646,6 @@ describe('API compatibility handling', () => {
|
|
|
1616
1646
|
expect(params.data_initial_chunk).toHaveLength(2048);
|
|
1617
1647
|
});
|
|
1618
1648
|
|
|
1619
|
-
test('accepts string XRP payment amount values', () => {
|
|
1620
|
-
const method = new XrpSignTransaction({
|
|
1621
|
-
id: 1,
|
|
1622
|
-
payload: {
|
|
1623
|
-
method: 'xrpSignTransaction',
|
|
1624
|
-
path: "m/44'/144'/0'/0/0",
|
|
1625
|
-
transaction: {
|
|
1626
|
-
fee: '100000',
|
|
1627
|
-
flags: 2147483648,
|
|
1628
|
-
sequence: 25,
|
|
1629
|
-
maxLedgerVersion: 8820051,
|
|
1630
|
-
payment: {
|
|
1631
|
-
amount: '100000000',
|
|
1632
|
-
destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws',
|
|
1633
|
-
},
|
|
1634
|
-
},
|
|
1635
|
-
},
|
|
1636
|
-
});
|
|
1637
|
-
|
|
1638
|
-
expect(() => method.init()).not.toThrow();
|
|
1639
|
-
expect(method.params.payment?.amount).toBe('100000000');
|
|
1640
|
-
});
|
|
1641
|
-
|
|
1642
|
-
test('accepts Conflux base32 recipient addresses without hex formatting them', () => {
|
|
1643
|
-
const to = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91';
|
|
1644
|
-
const method = new ConfluxSignTransaction({
|
|
1645
|
-
id: 1,
|
|
1646
|
-
payload: {
|
|
1647
|
-
method: 'confluxSignTransaction',
|
|
1648
|
-
path: "m/44'/503'/0'/0/0",
|
|
1649
|
-
transaction: {
|
|
1650
|
-
to,
|
|
1651
|
-
value: '0x0',
|
|
1652
|
-
data: '0x',
|
|
1653
|
-
chainId: 1,
|
|
1654
|
-
nonce: '0x00',
|
|
1655
|
-
epochHeight: '0x00',
|
|
1656
|
-
gasLimit: '0x5208',
|
|
1657
|
-
storageLimit: '0x5208',
|
|
1658
|
-
gasPrice: '0xbebc200',
|
|
1659
|
-
},
|
|
1660
|
-
},
|
|
1661
|
-
});
|
|
1662
|
-
|
|
1663
|
-
expect(() => method.init()).not.toThrow();
|
|
1664
|
-
expect(method.formattedTx?.to).toBe(to);
|
|
1665
|
-
});
|
|
1666
|
-
|
|
1667
1649
|
test('returns a typed unsupported error for Dynex signing on Protocol V2', async () => {
|
|
1668
1650
|
const method = new DnxSignTransaction({
|
|
1669
1651
|
id: 1,
|
|
@@ -1719,7 +1701,41 @@ describe('API compatibility handling', () => {
|
|
|
1719
1701
|
});
|
|
1720
1702
|
|
|
1721
1703
|
describe('Protocol V2 firmware update targets', () => {
|
|
1722
|
-
|
|
1704
|
+
const OKPP_HEADER_SIZE = 0x52a0;
|
|
1705
|
+
|
|
1706
|
+
const buildOkppHeader = ({
|
|
1707
|
+
type = 'RESC',
|
|
1708
|
+
version = [1, 0, 0],
|
|
1709
|
+
payloadHash = 'ab'.repeat(64),
|
|
1710
|
+
headerHash = 'cd'.repeat(64),
|
|
1711
|
+
}: {
|
|
1712
|
+
type?: string;
|
|
1713
|
+
version?: [number, number, number];
|
|
1714
|
+
payloadHash?: string;
|
|
1715
|
+
headerHash?: string;
|
|
1716
|
+
} = {}) => {
|
|
1717
|
+
const header = new Uint8Array(OKPP_HEADER_SIZE);
|
|
1718
|
+
const view = new DataView(header.buffer);
|
|
1719
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
1720
|
+
header[index] = char.charCodeAt(0);
|
|
1721
|
+
});
|
|
1722
|
+
view.setUint32(0x04, 1, true);
|
|
1723
|
+
type.split('').forEach((char, index) => {
|
|
1724
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
1725
|
+
});
|
|
1726
|
+
view.setUint32(0x0c, OKPP_HEADER_SIZE, true);
|
|
1727
|
+
view.setUint32(0x10, version[0] * 0x10000 + version[1] * 0x100 + version[2], true);
|
|
1728
|
+
const writeHex = (offset: number, hex: string) => {
|
|
1729
|
+
for (let i = 0; i < hex.length / 2; i++) {
|
|
1730
|
+
header[offset + i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
writeHex(0x200, payloadHash);
|
|
1734
|
+
writeHex(0x240, headerHash);
|
|
1735
|
+
return header;
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
test('keeps Protocol V2 firmware updates off the firmwareUpdateV3 path', async () => {
|
|
1723
1739
|
const method = new FirmwareUpdateV3({
|
|
1724
1740
|
id: 1,
|
|
1725
1741
|
payload: {
|
|
@@ -1733,25 +1749,26 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1733
1749
|
await expect(method.run()).rejects.toThrow('firmwareUpdateV4');
|
|
1734
1750
|
});
|
|
1735
1751
|
|
|
1736
|
-
test('uses Protocol V2 features after BLE final reconnect without
|
|
1752
|
+
test('uses Protocol V2 features after BLE final reconnect without V1 Initialize', async () => {
|
|
1737
1753
|
const method = new FirmwareUpdateV4({
|
|
1738
1754
|
id: 1,
|
|
1739
1755
|
payload: {
|
|
1740
1756
|
method: 'firmwareUpdateV4',
|
|
1757
|
+
coprocessorBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
1741
1758
|
},
|
|
1742
1759
|
});
|
|
1743
1760
|
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-session' });
|
|
1744
1761
|
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
1745
|
-
if (name === '
|
|
1762
|
+
if (name === 'DeviceInfoGet') {
|
|
1746
1763
|
return Promise.resolve({
|
|
1747
1764
|
type: 'DeviceInfo',
|
|
1748
1765
|
message: {
|
|
1749
1766
|
fw: {
|
|
1750
|
-
|
|
1751
|
-
|
|
1767
|
+
bootloader: { version: '0.0.0' },
|
|
1768
|
+
application: { version: '0.0.0' },
|
|
1752
1769
|
},
|
|
1753
|
-
|
|
1754
|
-
|
|
1770
|
+
coprocessor: {
|
|
1771
|
+
application: { version: '0.0.0' },
|
|
1755
1772
|
},
|
|
1756
1773
|
},
|
|
1757
1774
|
});
|
|
@@ -1765,7 +1782,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1765
1782
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1766
1783
|
deviceConnector: { acquire },
|
|
1767
1784
|
getCommands: () => commands,
|
|
1768
|
-
|
|
1785
|
+
updateProtocolV2Features: jest.fn(() => ({
|
|
1786
|
+
bootloaderMode: false,
|
|
1787
|
+
mode: 'normal',
|
|
1788
|
+
firmwareVersion: '0.0.0',
|
|
1789
|
+
bootloaderVersion: '0.0.0',
|
|
1790
|
+
bleVersion: '0.0.0',
|
|
1791
|
+
})),
|
|
1769
1792
|
});
|
|
1770
1793
|
|
|
1771
1794
|
const versions = await (method as any).waitForProtocolV2FinalFeatures();
|
|
@@ -1774,13 +1797,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1774
1797
|
// 更新完成判定使用 VERSIONS 请求(含 SE targets),scope 与请求内容一致
|
|
1775
1798
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1776
1799
|
1,
|
|
1777
|
-
'
|
|
1800
|
+
'DeviceInfoGet',
|
|
1778
1801
|
'DeviceInfo',
|
|
1779
1802
|
{
|
|
1780
1803
|
targets: {
|
|
1781
1804
|
hw: true,
|
|
1782
1805
|
fw: true,
|
|
1783
|
-
|
|
1806
|
+
coprocessor: true,
|
|
1784
1807
|
se1: true,
|
|
1785
1808
|
se2: true,
|
|
1786
1809
|
se3: true,
|
|
@@ -1803,7 +1826,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1803
1826
|
});
|
|
1804
1827
|
});
|
|
1805
1828
|
|
|
1806
|
-
test('
|
|
1829
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
1807
1830
|
const method = new FirmwareUpdateV4({
|
|
1808
1831
|
id: 1,
|
|
1809
1832
|
payload: {
|
|
@@ -1816,14 +1839,15 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1816
1839
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1817
1840
|
features: { capabilities: [] },
|
|
1818
1841
|
});
|
|
1819
|
-
(method as any).
|
|
1820
|
-
(method as any).
|
|
1842
|
+
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
1843
|
+
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
1844
|
+
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
1821
1845
|
{
|
|
1822
|
-
fileName: '
|
|
1846
|
+
fileName: 'coprocessor.bin',
|
|
1823
1847
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
1848
|
+
targetId: 6,
|
|
1824
1849
|
},
|
|
1825
1850
|
]);
|
|
1826
|
-
(method as any).prepareBootloaderBinary = jest.fn().mockResolvedValue(null);
|
|
1827
1851
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
1828
1852
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
1829
1853
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
@@ -1837,17 +1861,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1837
1861
|
|
|
1838
1862
|
await method.run();
|
|
1839
1863
|
|
|
1864
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
1840
1865
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
1841
|
-
|
|
1866
|
+
resourceBinaryMap: [],
|
|
1842
1867
|
fwBinaryMap: [
|
|
1843
1868
|
{
|
|
1844
|
-
fileName: '
|
|
1869
|
+
fileName: 'coprocessor.bin',
|
|
1845
1870
|
binary: expect.any(ArrayBuffer),
|
|
1871
|
+
targetId: 6,
|
|
1846
1872
|
},
|
|
1847
1873
|
],
|
|
1848
1874
|
bootloaderBinary: null,
|
|
1849
1875
|
});
|
|
1850
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
1851
1876
|
});
|
|
1852
1877
|
|
|
1853
1878
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -1857,31 +1882,70 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1857
1882
|
method: 'firmwareUpdateV4',
|
|
1858
1883
|
},
|
|
1859
1884
|
});
|
|
1860
|
-
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
1861
1885
|
(method as any).device = stubDevice({
|
|
1862
1886
|
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
1863
1887
|
features: { bootloader_mode: false, capabilities: [] },
|
|
1864
1888
|
isBootloader: () => false,
|
|
1865
|
-
acquire,
|
|
1866
1889
|
});
|
|
1867
1890
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
1868
1891
|
message: 'Device rebooted successfully',
|
|
1869
1892
|
});
|
|
1870
|
-
(method as any).checkDeviceToBootloader = jest.fn(
|
|
1871
|
-
|
|
1893
|
+
(method as any).checkDeviceToBootloader = jest.fn();
|
|
1894
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
1895
|
+
type: 'DeviceInfo',
|
|
1896
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
1897
|
+
});
|
|
1898
|
+
const reconnectProtocolV2Device = jest.fn().mockImplementation(() => {
|
|
1899
|
+
(method as any).device.isBootloader = () => true;
|
|
1900
|
+
return Promise.resolve();
|
|
1872
1901
|
});
|
|
1902
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
1903
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
1873
1904
|
method.postTipMessage = jest.fn();
|
|
1874
1905
|
|
|
1875
1906
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
1876
1907
|
|
|
1877
1908
|
expect(method.postTipMessage).toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
1878
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
1909
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Bootloader);
|
|
1879
1910
|
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
1880
|
-
expect((method as any).checkDeviceToBootloader).
|
|
1881
|
-
expect(
|
|
1911
|
+
expect((method as any).checkDeviceToBootloader).not.toHaveBeenCalled();
|
|
1912
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
test('polls until Protocol V2 bootloader descriptor is ready after reboot', async () => {
|
|
1916
|
+
const method = new FirmwareUpdateV4({
|
|
1917
|
+
id: 1,
|
|
1918
|
+
payload: {
|
|
1919
|
+
method: 'firmwareUpdateV4',
|
|
1920
|
+
},
|
|
1921
|
+
});
|
|
1922
|
+
(method as any).device = stubDevice({
|
|
1923
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
1924
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
1925
|
+
isBootloader: () => false,
|
|
1926
|
+
});
|
|
1927
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
1928
|
+
type: 'DeviceInfo',
|
|
1929
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
1930
|
+
});
|
|
1931
|
+
const reconnectProtocolV2Device = jest
|
|
1932
|
+
.fn()
|
|
1933
|
+
.mockRejectedValueOnce(new Error('Device not found'))
|
|
1934
|
+
.mockRejectedValueOnce(new Error('Device not found'))
|
|
1935
|
+
.mockImplementation(() => {
|
|
1936
|
+
(method as any).device.isBootloader = () => true;
|
|
1937
|
+
return Promise.resolve();
|
|
1938
|
+
});
|
|
1939
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
1940
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
1941
|
+
|
|
1942
|
+
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
1943
|
+
|
|
1944
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
1945
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1882
1946
|
});
|
|
1883
1947
|
|
|
1884
|
-
test('reboots Protocol V2 firmware flow back to normal
|
|
1948
|
+
test('reboots Protocol V2 firmware flow back to normal after install status is finished', async () => {
|
|
1885
1949
|
const method = new FirmwareUpdateV4({
|
|
1886
1950
|
id: 1,
|
|
1887
1951
|
payload: {
|
|
@@ -1896,7 +1960,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1896
1960
|
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
1897
1961
|
|
|
1898
1962
|
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
1899
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
1963
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
1900
1964
|
});
|
|
1901
1965
|
|
|
1902
1966
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
@@ -1916,7 +1980,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1916
1980
|
getCommands: () => ({ typedCall }),
|
|
1917
1981
|
});
|
|
1918
1982
|
|
|
1919
|
-
await expect((method as any).protocolV2Reboot(
|
|
1983
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
1920
1984
|
message: 'Device rebooted successfully',
|
|
1921
1985
|
});
|
|
1922
1986
|
});
|
|
@@ -1936,7 +2000,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1936
2000
|
getCommands: () => ({ typedCall }),
|
|
1937
2001
|
});
|
|
1938
2002
|
|
|
1939
|
-
await expect((method as any).protocolV2Reboot(
|
|
2003
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
1940
2004
|
message: 'Device rebooted successfully',
|
|
1941
2005
|
});
|
|
1942
2006
|
});
|
|
@@ -1964,191 +2028,1011 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1964
2028
|
|
|
1965
2029
|
await expect(
|
|
1966
2030
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
1967
|
-
targets: [{ target_id:
|
|
2031
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
1968
2032
|
})
|
|
1969
2033
|
).resolves.toBeUndefined();
|
|
1970
2034
|
|
|
1971
2035
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
1972
2036
|
});
|
|
1973
2037
|
|
|
1974
|
-
test('continues Protocol V2 install polling
|
|
2038
|
+
test('continues Protocol V2 install polling when update request transfer times out', async () => {
|
|
1975
2039
|
const method = new FirmwareUpdateV4({
|
|
1976
2040
|
id: 1,
|
|
1977
2041
|
payload: {
|
|
1978
2042
|
method: 'firmwareUpdateV4',
|
|
1979
2043
|
},
|
|
1980
2044
|
});
|
|
1981
|
-
const typedCall = jest
|
|
1982
|
-
.fn()
|
|
1983
|
-
.mockRejectedValueOnce(
|
|
1984
|
-
new Error(
|
|
1985
|
-
'Device protocol mismatch: expected V2, but device did not respond to expected protocol'
|
|
1986
|
-
)
|
|
1987
|
-
)
|
|
1988
|
-
.mockResolvedValueOnce({
|
|
1989
|
-
type: 'DevFirmwareUpdateStatus',
|
|
1990
|
-
message: {
|
|
1991
|
-
targets: [{ target_id: 2, status: 0 }],
|
|
1992
|
-
},
|
|
1993
|
-
});
|
|
1994
|
-
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2045
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
1995
2046
|
|
|
1996
2047
|
(method as any).device = stubDevice({
|
|
1997
2048
|
getCommands: () => ({ typedCall }),
|
|
1998
2049
|
});
|
|
1999
|
-
|
|
2000
|
-
method.postProgressMessage = jest.fn();
|
|
2050
|
+
method.postTipMessage = jest.fn();
|
|
2001
2051
|
|
|
2002
|
-
await (
|
|
2003
|
-
|
|
2004
|
-
|
|
2052
|
+
await expect(
|
|
2053
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
2054
|
+
targets: [{ target_id: 1, path: 'vol1:resource.bin' }],
|
|
2055
|
+
})
|
|
2056
|
+
).resolves.toBeUndefined();
|
|
2005
2057
|
|
|
2006
|
-
expect(
|
|
2007
|
-
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2058
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2008
2059
|
});
|
|
2009
2060
|
|
|
2010
|
-
test('
|
|
2061
|
+
test('continues Protocol V2 install polling through temporary expected V2 probe failures', async () => {
|
|
2011
2062
|
const method = new FirmwareUpdateV4({
|
|
2012
2063
|
id: 1,
|
|
2013
2064
|
payload: {
|
|
2014
2065
|
method: 'firmwareUpdateV4',
|
|
2015
2066
|
},
|
|
2016
2067
|
});
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2068
|
+
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
2069
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
2070
|
+
const callCount = typedCall.mock.calls.filter(call => call[0] === name).length;
|
|
2071
|
+
if (callCount === 1) {
|
|
2072
|
+
return Promise.reject(
|
|
2073
|
+
new Error(
|
|
2074
|
+
'Device protocol mismatch: expected V2, but device did not respond to expected protocol'
|
|
2075
|
+
)
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
return Promise.resolve({
|
|
2079
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
2080
|
+
message: {
|
|
2081
|
+
records: [{ target_id: 6, status: 2 }],
|
|
2082
|
+
},
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
if (name === 'DeviceInfoGet') {
|
|
2086
|
+
return Promise.reject(new Error('DeviceInfo not ready'));
|
|
2087
|
+
}
|
|
2088
|
+
if (name === 'Ping') {
|
|
2089
|
+
return Promise.resolve({ type: 'Success', message: { message: 'ready' } });
|
|
2090
|
+
}
|
|
2091
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2092
|
+
});
|
|
2093
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2020
2094
|
|
|
2021
|
-
|
|
2022
|
-
(
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
).toBe(true);
|
|
2095
|
+
(method as any).device = stubDevice({
|
|
2096
|
+
getCommands: () => ({ typedCall }),
|
|
2097
|
+
});
|
|
2098
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2099
|
+
method.postProgressMessage = jest.fn();
|
|
2027
2100
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
expectedTargetIds
|
|
2032
|
-
)
|
|
2033
|
-
).toBe(false);
|
|
2034
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2101
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
2102
|
+
{ target_id: 6, path: 'vol1:ble-firmware.bin' },
|
|
2103
|
+
]);
|
|
2035
2104
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2044
|
-
}
|
|
2105
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2106
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
2107
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2108
|
+
'DeviceInfoGet',
|
|
2109
|
+
'Ping',
|
|
2110
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2111
|
+
]);
|
|
2045
2112
|
});
|
|
2046
2113
|
|
|
2047
|
-
test('
|
|
2114
|
+
test('treats Protocol V2 normal mode after reconnect as firmware update complete', async () => {
|
|
2048
2115
|
const method = new FirmwareUpdateV4({
|
|
2049
2116
|
id: 1,
|
|
2050
2117
|
payload: {
|
|
2051
2118
|
method: 'firmwareUpdateV4',
|
|
2052
2119
|
},
|
|
2053
2120
|
});
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2121
|
+
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
2122
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
2123
|
+
return Promise.reject(new Error('Device disconnected'));
|
|
2124
|
+
}
|
|
2125
|
+
if (name === 'DeviceInfoGet') {
|
|
2126
|
+
return Promise.resolve({
|
|
2127
|
+
type: 'DeviceInfo',
|
|
2128
|
+
message: {
|
|
2129
|
+
hw: { serial_no: 'PR9999999999' },
|
|
2130
|
+
fw: {
|
|
2131
|
+
bootloader: { version: '9.9.9' },
|
|
2132
|
+
application: { version: '9.9.9' },
|
|
2133
|
+
},
|
|
2134
|
+
status: {
|
|
2135
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
2136
|
+
init_states: true,
|
|
2137
|
+
},
|
|
2138
|
+
},
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2142
|
+
});
|
|
2143
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2144
|
+
const updateProtocolV2Features = jest.fn((deviceInfo: ProtocolV2DeviceInfo) =>
|
|
2145
|
+
normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, deviceInfo)
|
|
2146
|
+
);
|
|
2147
|
+
|
|
2148
|
+
(method as any).device = stubDevice({
|
|
2149
|
+
getCommands: () => ({ typedCall }),
|
|
2150
|
+
updateProtocolV2Features,
|
|
2151
|
+
});
|
|
2152
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2153
|
+
method.postProgressMessage = jest.fn();
|
|
2154
|
+
|
|
2155
|
+
await expect(
|
|
2156
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
2157
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
2158
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
2159
|
+
])
|
|
2160
|
+
).resolves.toBeUndefined();
|
|
2161
|
+
|
|
2162
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2163
|
+
expect(updateProtocolV2Features).toHaveBeenCalledTimes(1);
|
|
2164
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
2165
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2166
|
+
'DeviceInfoGet',
|
|
2167
|
+
]);
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
2171
|
+
const method = new FirmwareUpdateV4({
|
|
2172
|
+
id: 1,
|
|
2173
|
+
payload: {
|
|
2174
|
+
method: 'firmwareUpdateV4',
|
|
2175
|
+
},
|
|
2176
|
+
});
|
|
2177
|
+
method.postProgressMessage = jest.fn();
|
|
2178
|
+
|
|
2179
|
+
const expectedTargetIds = new Set([4]);
|
|
2180
|
+
|
|
2181
|
+
expect(
|
|
2182
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 2 }], expectedTargetIds)
|
|
2183
|
+
).toBe(true);
|
|
2184
|
+
expect(
|
|
2185
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2186
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' }],
|
|
2187
|
+
expectedTargetIds
|
|
2188
|
+
)
|
|
2189
|
+
).toBe(true);
|
|
2190
|
+
expect(
|
|
2191
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2192
|
+
[
|
|
2193
|
+
{
|
|
2194
|
+
target_id: 'FW_MGMT_TARGET_CRATE',
|
|
2195
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
2196
|
+
},
|
|
2197
|
+
{ target_id: 'FW_MGMT_TARGET_SE04', status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
2198
|
+
],
|
|
2199
|
+
new Set([1, 10])
|
|
2200
|
+
)
|
|
2201
|
+
).toBe(true);
|
|
2202
|
+
|
|
2203
|
+
expect(
|
|
2204
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 1 }], expectedTargetIds)
|
|
2205
|
+
).toBe(false);
|
|
2206
|
+
expect(
|
|
2207
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2208
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS' }],
|
|
2209
|
+
expectedTargetIds
|
|
2210
|
+
)
|
|
2211
|
+
).toBe(false);
|
|
2212
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2213
|
+
|
|
2214
|
+
try {
|
|
2215
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2216
|
+
[{ target_id: 4, status: 3 }],
|
|
2217
|
+
expectedTargetIds
|
|
2218
|
+
);
|
|
2219
|
+
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
2220
|
+
} catch (error: any) {
|
|
2221
|
+
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
try {
|
|
2225
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2226
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY' }],
|
|
2227
|
+
expectedTargetIds
|
|
2228
|
+
);
|
|
2229
|
+
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
2230
|
+
} catch (error: any) {
|
|
2231
|
+
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
test('passes resource, bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
2236
|
+
const method = new FirmwareUpdateV4({
|
|
2237
|
+
id: 1,
|
|
2238
|
+
payload: {
|
|
2239
|
+
method: 'firmwareUpdateV4',
|
|
2240
|
+
},
|
|
2241
|
+
});
|
|
2242
|
+
|
|
2243
|
+
const writtenPaths: string[] = [];
|
|
2244
|
+
method.postTipMessage = jest.fn();
|
|
2245
|
+
method.postProgressMessage = jest.fn();
|
|
2246
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
2247
|
+
writtenPaths.push(params.filePath);
|
|
2248
|
+
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
2249
|
+
});
|
|
2250
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2251
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2252
|
+
.fn()
|
|
2253
|
+
.mockResolvedValue(undefined);
|
|
2254
|
+
|
|
2255
|
+
await (method as any).executeProtocolV2Update({
|
|
2256
|
+
resourceBinaryMap: [
|
|
2257
|
+
{
|
|
2258
|
+
fileName: 'resource.bin',
|
|
2259
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2260
|
+
targetId: 1,
|
|
2261
|
+
},
|
|
2262
|
+
],
|
|
2263
|
+
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2264
|
+
fwBinaryMap: [
|
|
2265
|
+
{
|
|
2266
|
+
fileName: 'coprocessor.bin',
|
|
2267
|
+
binary: new Uint8Array([6]).buffer,
|
|
2268
|
+
targetId: 6,
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
fileName: 'se01.bin',
|
|
2272
|
+
binary: new Uint8Array([7]).buffer,
|
|
2273
|
+
targetId: 7,
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
fileName: 'application_p1.bin',
|
|
2277
|
+
binary: new Uint8Array([8]).buffer,
|
|
2278
|
+
targetId: 4,
|
|
2279
|
+
},
|
|
2280
|
+
],
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
expect(writtenPaths).toEqual([
|
|
2284
|
+
'vol1:resource.bin',
|
|
2285
|
+
'vol1:bootloader.bin',
|
|
2286
|
+
'vol1:coprocessor.bin',
|
|
2287
|
+
'vol1:se01.bin',
|
|
2288
|
+
'vol1:application_p1.bin',
|
|
2289
|
+
]);
|
|
2290
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
2291
|
+
targets: [{ target_id: 1, path: 'vol1:resource.bin' }],
|
|
2292
|
+
});
|
|
2293
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
2294
|
+
targets: [
|
|
2295
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
2296
|
+
{ target_id: 6, path: 'vol1:coprocessor.bin' },
|
|
2297
|
+
{ target_id: 7, path: 'vol1:se01.bin' },
|
|
2298
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
2299
|
+
],
|
|
2300
|
+
});
|
|
2301
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2302
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2303
|
+
});
|
|
2304
|
+
|
|
2305
|
+
test('passes explicit per-target binaries through without file name heuristics', async () => {
|
|
2306
|
+
const method = new FirmwareUpdateV4({
|
|
2307
|
+
id: 1,
|
|
2308
|
+
payload: {
|
|
2309
|
+
method: 'firmwareUpdateV4',
|
|
2310
|
+
platform: 'web',
|
|
2311
|
+
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2312
|
+
se04Binary: new Uint8Array([3]).buffer,
|
|
2313
|
+
},
|
|
2314
|
+
});
|
|
2315
|
+
method.init();
|
|
2316
|
+
|
|
2317
|
+
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2318
|
+
expect(explicit).toEqual([
|
|
2319
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2320
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2321
|
+
]);
|
|
2322
|
+
|
|
2323
|
+
method.postTipMessage = jest.fn();
|
|
2324
|
+
method.postProgressMessage = jest.fn();
|
|
2325
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2326
|
+
.fn()
|
|
2327
|
+
.mockImplementation(params =>
|
|
2328
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2329
|
+
);
|
|
2330
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2331
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2332
|
+
.fn()
|
|
2333
|
+
.mockResolvedValue(undefined);
|
|
2334
|
+
|
|
2335
|
+
await (method as any).executeProtocolV2Update({
|
|
2336
|
+
resourceBinaryMap: [],
|
|
2337
|
+
bootloaderBinary: null,
|
|
2338
|
+
fwBinaryMap: explicit,
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2342
|
+
targets: [
|
|
2343
|
+
{ target_id: 5, path: 'vol1:application_p2.bin' },
|
|
2344
|
+
{ target_id: 10, path: 'vol1:se04.bin' },
|
|
2345
|
+
],
|
|
2346
|
+
});
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
test('maps explicit Pro2 resource crate binaries to separate CRATE targets', async () => {
|
|
2350
|
+
const firstResource = new Uint8Array([1]).buffer;
|
|
2351
|
+
const secondResource = new Uint8Array([2]).buffer;
|
|
2352
|
+
const method = new FirmwareUpdateV4({
|
|
2353
|
+
id: 1,
|
|
2354
|
+
payload: {
|
|
2355
|
+
method: 'firmwareUpdateV4',
|
|
2356
|
+
platform: 'web',
|
|
2357
|
+
resourceBinaries: [firstResource, secondResource],
|
|
2358
|
+
},
|
|
2359
|
+
});
|
|
2360
|
+
method.init();
|
|
2361
|
+
|
|
2362
|
+
const resourceTargets = await (method as any).prepareResourceBinaries('universal', {
|
|
2363
|
+
deviceType: 'pro2',
|
|
2364
|
+
firmwareVersion: '1.0.0',
|
|
2365
|
+
capabilities: [],
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
expect(resourceTargets).toEqual([
|
|
2369
|
+
{
|
|
2370
|
+
fileName: 'resource-1.bin',
|
|
2371
|
+
binary: firstResource,
|
|
2372
|
+
targetId: 1,
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
fileName: 'resource-2.bin',
|
|
2376
|
+
binary: secondResource,
|
|
2377
|
+
targetId: 1,
|
|
2378
|
+
},
|
|
2379
|
+
]);
|
|
2380
|
+
expect((method as any).hasExplicitProtocolV2Payload([])).toBe(true);
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2383
|
+
test('rejects romloaderBinary before sending a Protocol V2 update request', () => {
|
|
2384
|
+
const method = new FirmwareUpdateV4({
|
|
2385
|
+
id: 1,
|
|
2386
|
+
payload: {
|
|
2387
|
+
method: 'firmwareUpdateV4',
|
|
2388
|
+
platform: 'web',
|
|
2389
|
+
romloaderBinary: new Uint8Array([1]).buffer,
|
|
2390
|
+
},
|
|
2391
|
+
});
|
|
2392
|
+
method.init();
|
|
2393
|
+
|
|
2394
|
+
expect(() => (method as any).collectExplicitTargetBinaries()).toThrow(
|
|
2395
|
+
'FW_MGMT_TARGET_ROMLOADER is not accepted'
|
|
2396
|
+
);
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
test('maps Pro2 remote firmware-v1 components to Protocol V2 targets in installOrder', async () => {
|
|
2400
|
+
const method = new FirmwareUpdateV4({
|
|
2401
|
+
id: 1,
|
|
2402
|
+
payload: {
|
|
2403
|
+
method: 'firmwareUpdateV4',
|
|
2404
|
+
platform: 'web',
|
|
2405
|
+
},
|
|
2406
|
+
});
|
|
2407
|
+
method.init();
|
|
2408
|
+
|
|
2409
|
+
const binaries = new Map(
|
|
2410
|
+
[
|
|
2411
|
+
'bootloader',
|
|
2412
|
+
'applicationP1',
|
|
2413
|
+
'applicationP2',
|
|
2414
|
+
'coprocessor',
|
|
2415
|
+
'se01',
|
|
2416
|
+
'se02',
|
|
2417
|
+
'se03',
|
|
2418
|
+
'se04',
|
|
2419
|
+
].map((key, index) => [
|
|
2420
|
+
`https://example.com/${key}.pp.bin`,
|
|
2421
|
+
new Uint8Array([index + 1]).buffer,
|
|
2422
|
+
])
|
|
2423
|
+
);
|
|
2424
|
+
const getSysResourceBinarySpy = jest
|
|
2425
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2426
|
+
.mockImplementation(url =>
|
|
2427
|
+
Promise.resolve({
|
|
2428
|
+
binary: binaries.get(url) ?? new Uint8Array([0]).buffer,
|
|
2429
|
+
})
|
|
2430
|
+
);
|
|
2431
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2432
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2433
|
+
.mockReturnValue({
|
|
2434
|
+
required: false,
|
|
2435
|
+
version: [1, 0, 0],
|
|
2436
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2437
|
+
bootloaderResource: 'https://example.com/bootloader.pp.bin',
|
|
2438
|
+
bootloaderVersion: [1, 0, 0],
|
|
2439
|
+
displayBootloaderVersion: [1, 0, 0],
|
|
2440
|
+
upgradeType: 'payload-package-set',
|
|
2441
|
+
installOrder: [
|
|
2442
|
+
'bootloader',
|
|
2443
|
+
'applicationP1',
|
|
2444
|
+
'applicationP2',
|
|
2445
|
+
'coprocessor',
|
|
2446
|
+
'se01',
|
|
2447
|
+
'se02',
|
|
2448
|
+
'se03',
|
|
2449
|
+
'se04',
|
|
2450
|
+
],
|
|
2451
|
+
components: {
|
|
2452
|
+
bootloader: {
|
|
2453
|
+
target: 'BOOTLOADER',
|
|
2454
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
2455
|
+
},
|
|
2456
|
+
applicationP1: {
|
|
2457
|
+
target: 'APPLICATION_P1',
|
|
2458
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2459
|
+
},
|
|
2460
|
+
applicationP2: {
|
|
2461
|
+
target: 'APPLICATION_P2',
|
|
2462
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
2463
|
+
},
|
|
2464
|
+
coprocessor: {
|
|
2465
|
+
target: 'COPROCESSOR',
|
|
2466
|
+
url: 'https://example.com/coprocessor.pp.bin',
|
|
2467
|
+
},
|
|
2468
|
+
se01: { target: 'SE01', url: 'https://example.com/se01.pp.bin' },
|
|
2469
|
+
se02: { target: 'SE02', url: 'https://example.com/se02.pp.bin' },
|
|
2470
|
+
se03: { target: 'SE03', url: 'https://example.com/se03.pp.bin' },
|
|
2471
|
+
se04: { target: 'SE04', url: 'https://example.com/se04.pp.bin' },
|
|
2472
|
+
},
|
|
2473
|
+
fingerprint: '',
|
|
2474
|
+
changelog: {
|
|
2475
|
+
'zh-CN': '',
|
|
2476
|
+
'en-US': '',
|
|
2477
|
+
},
|
|
2478
|
+
});
|
|
2479
|
+
|
|
2480
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2481
|
+
deviceType: 'pro2',
|
|
2482
|
+
firmwareVersion: '0.0.0',
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
expect(remoteBinaries.bootloaderBinary).toBe(
|
|
2486
|
+
binaries.get('https://example.com/bootloader.pp.bin')
|
|
2487
|
+
);
|
|
2488
|
+
expect(remoteBinaries.fwBinaryMap).toEqual([
|
|
2489
|
+
{ fileName: 'application_p1.bin', binary: expect.anything(), targetId: 4 },
|
|
2490
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2491
|
+
{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 },
|
|
2492
|
+
{ fileName: 'se01.bin', binary: expect.anything(), targetId: 7 },
|
|
2493
|
+
{ fileName: 'se02.bin', binary: expect.anything(), targetId: 8 },
|
|
2494
|
+
{ fileName: 'se03.bin', binary: expect.anything(), targetId: 9 },
|
|
2495
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2496
|
+
]);
|
|
2497
|
+
expect(getSysResourceBinarySpy.mock.calls.map(call => call[0])).toEqual([
|
|
2498
|
+
'https://example.com/bootloader.pp.bin',
|
|
2499
|
+
'https://example.com/applicationP1.pp.bin',
|
|
2500
|
+
'https://example.com/applicationP2.pp.bin',
|
|
2501
|
+
'https://example.com/coprocessor.pp.bin',
|
|
2502
|
+
'https://example.com/se01.pp.bin',
|
|
2503
|
+
'https://example.com/se02.pp.bin',
|
|
2504
|
+
'https://example.com/se03.pp.bin',
|
|
2505
|
+
'https://example.com/se04.pp.bin',
|
|
2506
|
+
]);
|
|
2507
|
+
|
|
2508
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2509
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2510
|
+
});
|
|
2511
|
+
|
|
2512
|
+
test('skips Pro2 resource crate download when resourceManifest matches device OKPP headers', async () => {
|
|
2513
|
+
const method = new FirmwareUpdateV4({
|
|
2514
|
+
id: 1,
|
|
2515
|
+
payload: {
|
|
2516
|
+
method: 'firmwareUpdateV4',
|
|
2517
|
+
platform: 'web',
|
|
2518
|
+
},
|
|
2519
|
+
});
|
|
2520
|
+
method.init();
|
|
2521
|
+
|
|
2522
|
+
const payloadHash = 'ab'.repeat(64);
|
|
2523
|
+
const header = buildOkppHeader({ payloadHash });
|
|
2524
|
+
const typedCall = jest.fn(
|
|
2525
|
+
(name: string, _resType: string, params: { file: { offset: number }; chunk_len: number }) => {
|
|
2526
|
+
if (name === 'FilesystemPathInfoQuery') {
|
|
2527
|
+
return Promise.resolve({
|
|
2528
|
+
type: 'FilesystemPathInfo',
|
|
2529
|
+
message: { exist: true, directory: false, size: header.byteLength },
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
if (name === 'FilesystemFileRead') {
|
|
2533
|
+
const { offset } = params.file;
|
|
2534
|
+
const readLen = params.chunk_len;
|
|
2535
|
+
return Promise.resolve({
|
|
2536
|
+
type: 'FilesystemFile',
|
|
2537
|
+
message: { data: header.slice(offset, offset + readLen) },
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2541
|
+
}
|
|
2542
|
+
);
|
|
2543
|
+
(method as any).device = stubDevice({
|
|
2544
|
+
getCommands: () => ({ typedCall }),
|
|
2545
|
+
});
|
|
2546
|
+
|
|
2547
|
+
const getSysResourceBinarySpy = jest
|
|
2548
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2549
|
+
.mockResolvedValue({ binary: new Uint8Array([9]).buffer });
|
|
2550
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2551
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2552
|
+
.mockReturnValue({
|
|
2553
|
+
required: false,
|
|
2554
|
+
version: [1, 0, 0],
|
|
2555
|
+
url: '',
|
|
2556
|
+
upgradeType: 'payload-package-set',
|
|
2557
|
+
installOrder: ['resource'],
|
|
2558
|
+
components: {
|
|
2559
|
+
resource: {
|
|
2560
|
+
target: 'CRATE',
|
|
2561
|
+
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2562
|
+
},
|
|
2563
|
+
},
|
|
2564
|
+
resourceManifest: {
|
|
2565
|
+
format: 'okpkg-crate',
|
|
2566
|
+
target: 'CRATE',
|
|
2567
|
+
version: [1, 0, 0],
|
|
2568
|
+
packages: [
|
|
2569
|
+
{
|
|
2570
|
+
path: 'resource/images/images.okpkg',
|
|
2571
|
+
type: 'RESC',
|
|
2572
|
+
payloadHash,
|
|
2573
|
+
},
|
|
2574
|
+
],
|
|
2575
|
+
},
|
|
2576
|
+
fingerprint: '',
|
|
2577
|
+
changelog: {
|
|
2578
|
+
'zh-CN': '',
|
|
2579
|
+
'en-US': '',
|
|
2580
|
+
},
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2584
|
+
deviceType: 'pro2',
|
|
2585
|
+
firmwareVersion: '1.0.0',
|
|
2586
|
+
capabilities: [],
|
|
2587
|
+
});
|
|
2588
|
+
|
|
2589
|
+
expect(remoteBinaries).toEqual({
|
|
2590
|
+
resourceBinaryMap: [],
|
|
2591
|
+
bootloaderBinary: null,
|
|
2592
|
+
fwBinaryMap: [],
|
|
2593
|
+
});
|
|
2594
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
2595
|
+
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2596
|
+
path: 'vol0:/resource/images/images.okpkg',
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2600
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2601
|
+
});
|
|
2602
|
+
|
|
2603
|
+
test('downloads Pro2 resource crate when resourceManifest package is missing', async () => {
|
|
2604
|
+
const method = new FirmwareUpdateV4({
|
|
2605
|
+
id: 1,
|
|
2606
|
+
payload: {
|
|
2607
|
+
method: 'firmwareUpdateV4',
|
|
2608
|
+
platform: 'web',
|
|
2609
|
+
},
|
|
2610
|
+
});
|
|
2611
|
+
method.init();
|
|
2612
|
+
|
|
2613
|
+
const resourceBinary = new Uint8Array([9]).buffer;
|
|
2614
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
2615
|
+
type: 'FilesystemPathInfo',
|
|
2616
|
+
message: { exist: false, directory: false, size: 0 },
|
|
2617
|
+
});
|
|
2618
|
+
(method as any).device = stubDevice({
|
|
2619
|
+
getCommands: () => ({ typedCall }),
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2622
|
+
const getSysResourceBinarySpy = jest
|
|
2623
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2624
|
+
.mockResolvedValue({ binary: resourceBinary });
|
|
2625
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2626
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2627
|
+
.mockReturnValue({
|
|
2628
|
+
required: false,
|
|
2629
|
+
version: [1, 0, 0],
|
|
2630
|
+
url: '',
|
|
2631
|
+
upgradeType: 'payload-package-set',
|
|
2632
|
+
installOrder: ['resource'],
|
|
2633
|
+
components: {
|
|
2634
|
+
resource: {
|
|
2635
|
+
target: 'CRATE',
|
|
2636
|
+
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2637
|
+
resourceManifest: {
|
|
2638
|
+
format: 'okpkg-crate',
|
|
2639
|
+
target: 'CRATE',
|
|
2640
|
+
version: [1, 0, 0],
|
|
2641
|
+
packages: [{ path: 'resource/images/images.okpkg', type: 'RESC' }],
|
|
2642
|
+
},
|
|
2643
|
+
},
|
|
2644
|
+
},
|
|
2645
|
+
fingerprint: '',
|
|
2646
|
+
changelog: {
|
|
2647
|
+
'zh-CN': '',
|
|
2648
|
+
'en-US': '',
|
|
2649
|
+
},
|
|
2650
|
+
});
|
|
2651
|
+
|
|
2652
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2653
|
+
deviceType: 'pro2',
|
|
2654
|
+
firmwareVersion: '1.0.0',
|
|
2655
|
+
capabilities: [],
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
expect(remoteBinaries.resourceBinaryMap).toEqual([
|
|
2659
|
+
{
|
|
2660
|
+
fileName: 'resource-resource.bin',
|
|
2661
|
+
binary: resourceBinary,
|
|
2662
|
+
targetId: 1,
|
|
2663
|
+
},
|
|
2664
|
+
]);
|
|
2665
|
+
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2666
|
+
'https://example.com/pro2-resource.crate.okpkg'
|
|
2667
|
+
);
|
|
2668
|
+
|
|
2669
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2670
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2671
|
+
});
|
|
2672
|
+
|
|
2673
|
+
test('maps Pro2 CRATE remote component to resource binary target', async () => {
|
|
2674
|
+
const method = new FirmwareUpdateV4({
|
|
2675
|
+
id: 1,
|
|
2676
|
+
payload: {
|
|
2677
|
+
method: 'firmwareUpdateV4',
|
|
2678
|
+
platform: 'web',
|
|
2679
|
+
},
|
|
2680
|
+
});
|
|
2681
|
+
method.init();
|
|
2682
|
+
|
|
2683
|
+
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
2684
|
+
const getSysResourceBinarySpy = jest
|
|
2685
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2686
|
+
.mockResolvedValue({ binary: resourceBinary });
|
|
2687
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2688
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2689
|
+
.mockReturnValue({
|
|
2690
|
+
required: false,
|
|
2691
|
+
version: [1, 0, 0],
|
|
2692
|
+
url: '',
|
|
2693
|
+
upgradeType: 'payload-package-set',
|
|
2694
|
+
installOrder: ['resource'],
|
|
2695
|
+
components: {
|
|
2696
|
+
resource: {
|
|
2697
|
+
target: 'CRATE',
|
|
2698
|
+
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2699
|
+
},
|
|
2700
|
+
},
|
|
2701
|
+
fingerprint: '',
|
|
2702
|
+
changelog: {
|
|
2703
|
+
'zh-CN': '',
|
|
2704
|
+
'en-US': '',
|
|
2705
|
+
},
|
|
2706
|
+
});
|
|
2707
|
+
|
|
2708
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2709
|
+
deviceType: 'pro2',
|
|
2710
|
+
firmwareVersion: '0.0.0',
|
|
2711
|
+
capabilities: [],
|
|
2712
|
+
});
|
|
2713
|
+
|
|
2714
|
+
expect(remoteBinaries).toEqual({
|
|
2715
|
+
resourceBinaryMap: [
|
|
2716
|
+
{
|
|
2717
|
+
fileName: 'resource-resource.bin',
|
|
2718
|
+
binary: resourceBinary,
|
|
2719
|
+
targetId: 1,
|
|
2720
|
+
},
|
|
2721
|
+
],
|
|
2722
|
+
bootloaderBinary: null,
|
|
2723
|
+
fwBinaryMap: [],
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2727
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2728
|
+
});
|
|
2729
|
+
|
|
2730
|
+
test('downloads only unmatched Pro2 resource crate components by component manifests', async () => {
|
|
2731
|
+
const method = new FirmwareUpdateV4({
|
|
2732
|
+
id: 1,
|
|
2733
|
+
payload: {
|
|
2734
|
+
method: 'firmwareUpdateV4',
|
|
2735
|
+
platform: 'web',
|
|
2736
|
+
},
|
|
2737
|
+
});
|
|
2738
|
+
method.init();
|
|
2739
|
+
|
|
2740
|
+
const imagesPayloadHash = 'ab'.repeat(64);
|
|
2741
|
+
const imagesHeader = buildOkppHeader({ payloadHash: imagesPayloadHash });
|
|
2742
|
+
const fontsBinary = new Uint8Array([7, 8, 9]).buffer;
|
|
2743
|
+
const typedCall = jest.fn(
|
|
2744
|
+
(
|
|
2745
|
+
name: string,
|
|
2746
|
+
_resType: string,
|
|
2747
|
+
params: { path?: string; file?: { offset: number }; chunk_len?: number }
|
|
2748
|
+
) => {
|
|
2749
|
+
if (name === 'FilesystemPathInfoQuery') {
|
|
2750
|
+
const exist = params.path === 'vol0:/resource/images/images.okpkg';
|
|
2751
|
+
return Promise.resolve({
|
|
2752
|
+
type: 'FilesystemPathInfo',
|
|
2753
|
+
message: { exist, directory: false, size: exist ? imagesHeader.byteLength : 0 },
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
if (name === 'FilesystemFileRead') {
|
|
2757
|
+
const offset = params.file?.offset ?? 0;
|
|
2758
|
+
const readLen = params.chunk_len ?? 0;
|
|
2759
|
+
return Promise.resolve({
|
|
2760
|
+
type: 'FilesystemFile',
|
|
2761
|
+
message: { data: imagesHeader.slice(offset, offset + readLen) },
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
(method as any).device = stubDevice({
|
|
2768
|
+
getCommands: () => ({ typedCall }),
|
|
2769
|
+
});
|
|
2770
|
+
|
|
2771
|
+
const getSysResourceBinarySpy = jest
|
|
2772
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2773
|
+
.mockResolvedValue({ binary: fontsBinary });
|
|
2774
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2775
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2776
|
+
.mockReturnValue({
|
|
2777
|
+
required: false,
|
|
2778
|
+
version: [1, 0, 0],
|
|
2779
|
+
url: '',
|
|
2780
|
+
upgradeType: 'payload-package-set',
|
|
2781
|
+
installOrder: ['resourceImages', 'resourceFonts'],
|
|
2782
|
+
components: {
|
|
2783
|
+
resourceImages: {
|
|
2784
|
+
target: 'CRATE',
|
|
2785
|
+
url: 'https://example.com/pro2-resource-images.crate.okpkg',
|
|
2786
|
+
resourceManifest: {
|
|
2787
|
+
format: 'okpkg-crate',
|
|
2788
|
+
target: 'CRATE',
|
|
2789
|
+
version: [1, 0, 0],
|
|
2790
|
+
packages: [
|
|
2791
|
+
{
|
|
2792
|
+
path: 'resource/images/images.okpkg',
|
|
2793
|
+
type: 'RESC',
|
|
2794
|
+
payloadHash: imagesPayloadHash,
|
|
2795
|
+
},
|
|
2796
|
+
],
|
|
2797
|
+
},
|
|
2798
|
+
},
|
|
2799
|
+
resourceFonts: {
|
|
2800
|
+
target: 'CRATE',
|
|
2801
|
+
url: 'https://example.com/pro2-resource-fonts.crate.okpkg',
|
|
2802
|
+
resourceManifest: {
|
|
2803
|
+
format: 'okpkg-crate',
|
|
2804
|
+
target: 'CRATE',
|
|
2805
|
+
version: [1, 0, 0],
|
|
2806
|
+
packages: [{ path: 'resource/font/noto.okpkg', type: 'RESC' }],
|
|
2807
|
+
},
|
|
2808
|
+
},
|
|
2809
|
+
},
|
|
2810
|
+
fingerprint: '',
|
|
2811
|
+
changelog: {
|
|
2812
|
+
'zh-CN': '',
|
|
2813
|
+
'en-US': '',
|
|
2814
|
+
},
|
|
2815
|
+
});
|
|
2816
|
+
|
|
2817
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2818
|
+
deviceType: 'pro2',
|
|
2819
|
+
firmwareVersion: '1.0.0',
|
|
2820
|
+
capabilities: [],
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
expect(remoteBinaries.resourceBinaryMap).toEqual([
|
|
2824
|
+
{
|
|
2825
|
+
fileName: 'resource-resourceFonts.bin',
|
|
2826
|
+
binary: fontsBinary,
|
|
2827
|
+
targetId: 1,
|
|
2828
|
+
},
|
|
2829
|
+
]);
|
|
2830
|
+
expect(getSysResourceBinarySpy).toHaveBeenCalledTimes(1);
|
|
2831
|
+
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2832
|
+
'https://example.com/pro2-resource-fonts.crate.okpkg'
|
|
2833
|
+
);
|
|
2834
|
+
|
|
2835
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2836
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2837
|
+
});
|
|
2838
|
+
|
|
2839
|
+
test('installs multiple Pro2 resource crates as separate update requests', async () => {
|
|
2840
|
+
const method = new FirmwareUpdateV4({
|
|
2841
|
+
id: 1,
|
|
2842
|
+
payload: {
|
|
2843
|
+
method: 'firmwareUpdateV4',
|
|
2844
|
+
},
|
|
2061
2845
|
});
|
|
2846
|
+
|
|
2847
|
+
method.postTipMessage = jest.fn();
|
|
2848
|
+
method.postProgressMessage = jest.fn();
|
|
2849
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2850
|
+
.fn()
|
|
2851
|
+
.mockImplementation(params =>
|
|
2852
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2853
|
+
);
|
|
2062
2854
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2063
2855
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2064
2856
|
.fn()
|
|
2065
2857
|
.mockResolvedValue(undefined);
|
|
2066
2858
|
|
|
2067
2859
|
await (method as any).executeProtocolV2Update({
|
|
2068
|
-
|
|
2069
|
-
resourceBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
2070
|
-
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2071
|
-
fwBinaryMap: [
|
|
2860
|
+
resourceBinaryMap: [
|
|
2072
2861
|
{
|
|
2073
|
-
fileName: '
|
|
2074
|
-
binary: new Uint8Array([
|
|
2862
|
+
fileName: 'resource-images.bin',
|
|
2863
|
+
binary: new Uint8Array([1]).buffer,
|
|
2864
|
+
targetId: 1,
|
|
2075
2865
|
},
|
|
2076
2866
|
{
|
|
2077
|
-
fileName: '
|
|
2078
|
-
binary: new Uint8Array([
|
|
2867
|
+
fileName: 'resource-fonts.bin',
|
|
2868
|
+
binary: new Uint8Array([2]).buffer,
|
|
2869
|
+
targetId: 1,
|
|
2079
2870
|
},
|
|
2871
|
+
],
|
|
2872
|
+
bootloaderBinary: null,
|
|
2873
|
+
fwBinaryMap: [
|
|
2080
2874
|
{
|
|
2081
|
-
fileName: '
|
|
2082
|
-
binary: new Uint8Array([
|
|
2875
|
+
fileName: 'application_p1.bin',
|
|
2876
|
+
binary: new Uint8Array([3]).buffer,
|
|
2877
|
+
targetId: 4,
|
|
2083
2878
|
},
|
|
2084
2879
|
],
|
|
2085
2880
|
});
|
|
2086
2881
|
|
|
2087
|
-
expect(
|
|
2088
|
-
'vol1:resource.bin',
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
'vol1:
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2096
|
-
targets: [
|
|
2097
|
-
{ target_id: 10, path: 'vol1:resource.bin' },
|
|
2098
|
-
{ target_id: 1, path: 'vol1:bootloader.bin' },
|
|
2099
|
-
{ target_id: 2, path: 'vol1:ble-firmware.bin' },
|
|
2100
|
-
{ target_id: 3, path: 'vol1:se1-firmware.bin' },
|
|
2101
|
-
{ target_id: 0, path: 'vol1:firmware.bin' },
|
|
2102
|
-
],
|
|
2882
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
2883
|
+
targets: [{ target_id: 1, path: 'vol1:resource-images.bin' }],
|
|
2884
|
+
});
|
|
2885
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
2886
|
+
targets: [{ target_id: 1, path: 'vol1:resource-fonts.bin' }],
|
|
2887
|
+
});
|
|
2888
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(3, {
|
|
2889
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2103
2890
|
});
|
|
2104
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2105
|
-
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2106
2891
|
});
|
|
2107
2892
|
|
|
2108
|
-
test('
|
|
2893
|
+
test('skips Pro2 firmware components when configured versions are already installed', async () => {
|
|
2109
2894
|
const method = new FirmwareUpdateV4({
|
|
2110
2895
|
id: 1,
|
|
2111
2896
|
payload: {
|
|
2112
2897
|
method: 'firmwareUpdateV4',
|
|
2113
2898
|
platform: 'web',
|
|
2114
|
-
romloaderBinary: new Uint8Array([1]).buffer,
|
|
2115
|
-
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2116
|
-
se04Binary: new Uint8Array([3]).buffer,
|
|
2117
2899
|
},
|
|
2118
2900
|
});
|
|
2119
2901
|
method.init();
|
|
2120
2902
|
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2123
|
-
{
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2903
|
+
const getSysResourceBinarySpy = jest
|
|
2904
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2905
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
2906
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2907
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2908
|
+
.mockReturnValue({
|
|
2909
|
+
required: false,
|
|
2910
|
+
version: [1, 0, 0],
|
|
2911
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2912
|
+
bootloaderVersion: [1, 0, 0],
|
|
2913
|
+
upgradeType: 'payload-package-set',
|
|
2914
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
2915
|
+
components: {
|
|
2916
|
+
bootloader: {
|
|
2917
|
+
target: 'BOOTLOADER',
|
|
2918
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
2919
|
+
},
|
|
2920
|
+
applicationP1: {
|
|
2921
|
+
target: 'APPLICATION_P1',
|
|
2922
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2923
|
+
},
|
|
2924
|
+
applicationP2: {
|
|
2925
|
+
target: 'APPLICATION_P2',
|
|
2926
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
2927
|
+
},
|
|
2928
|
+
},
|
|
2929
|
+
fingerprint: '',
|
|
2930
|
+
changelog: {
|
|
2931
|
+
'zh-CN': '',
|
|
2932
|
+
'en-US': '',
|
|
2933
|
+
},
|
|
2934
|
+
});
|
|
2127
2935
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
.
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
);
|
|
2135
|
-
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2136
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2137
|
-
.fn()
|
|
2138
|
-
.mockResolvedValue(undefined);
|
|
2936
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2937
|
+
deviceType: 'pro2',
|
|
2938
|
+
firmwareVersion: '1.0.0',
|
|
2939
|
+
bootloaderVersion: '1.0.0',
|
|
2940
|
+
capabilities: [],
|
|
2941
|
+
});
|
|
2139
2942
|
|
|
2140
|
-
|
|
2141
|
-
|
|
2943
|
+
expect(remoteBinaries).toEqual({
|
|
2944
|
+
resourceBinaryMap: [],
|
|
2142
2945
|
bootloaderBinary: null,
|
|
2143
|
-
fwBinaryMap:
|
|
2946
|
+
fwBinaryMap: [],
|
|
2144
2947
|
});
|
|
2948
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
2145
2949
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2950
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2951
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2952
|
+
});
|
|
2953
|
+
|
|
2954
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
2955
|
+
const method = new FirmwareUpdateV4({
|
|
2956
|
+
id: 1,
|
|
2957
|
+
payload: {
|
|
2958
|
+
method: 'firmwareUpdateV4',
|
|
2959
|
+
platform: 'web',
|
|
2960
|
+
},
|
|
2961
|
+
});
|
|
2962
|
+
method.init();
|
|
2963
|
+
|
|
2964
|
+
const remoteBinaries = {
|
|
2965
|
+
resourceBinaryMap: [],
|
|
2966
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
2967
|
+
fwBinaryMap: [
|
|
2968
|
+
{
|
|
2969
|
+
fileName: 'application_p1.bin',
|
|
2970
|
+
binary: new Uint8Array([2]).buffer,
|
|
2971
|
+
targetId: 4,
|
|
2972
|
+
},
|
|
2151
2973
|
],
|
|
2974
|
+
};
|
|
2975
|
+
|
|
2976
|
+
(method as any).device = stubDevice({
|
|
2977
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
2978
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
2979
|
+
});
|
|
2980
|
+
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
2981
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
2982
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
2983
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
2984
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
2985
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
2986
|
+
bootloaderVersion: '1.0.0',
|
|
2987
|
+
bleVersion: '0.0.0',
|
|
2988
|
+
firmwareVersion: '1.0.0',
|
|
2989
|
+
});
|
|
2990
|
+
method.postTipMessage = jest.fn();
|
|
2991
|
+
|
|
2992
|
+
await method.run();
|
|
2993
|
+
|
|
2994
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
2995
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
2996
|
+
resourceBinaryMap: remoteBinaries.resourceBinaryMap,
|
|
2997
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
2998
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
2999
|
+
});
|
|
3000
|
+
});
|
|
3001
|
+
|
|
3002
|
+
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
3003
|
+
const method = new FirmwareUpdateV4({
|
|
3004
|
+
id: 1,
|
|
3005
|
+
payload: {
|
|
3006
|
+
method: 'firmwareUpdateV4',
|
|
3007
|
+
platform: 'web',
|
|
3008
|
+
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
3009
|
+
},
|
|
3010
|
+
});
|
|
3011
|
+
method.init();
|
|
3012
|
+
|
|
3013
|
+
(method as any).device = stubDevice({
|
|
3014
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3015
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3016
|
+
});
|
|
3017
|
+
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
3018
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3019
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3020
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3021
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3022
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3023
|
+
bootloaderVersion: '1.0.0',
|
|
3024
|
+
bleVersion: '0.0.0',
|
|
3025
|
+
firmwareVersion: '1.0.0',
|
|
3026
|
+
});
|
|
3027
|
+
method.postTipMessage = jest.fn();
|
|
3028
|
+
|
|
3029
|
+
await method.run();
|
|
3030
|
+
|
|
3031
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3032
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3033
|
+
resourceBinaryMap: [],
|
|
3034
|
+
bootloaderBinary: null,
|
|
3035
|
+
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
2152
3036
|
});
|
|
2153
3037
|
});
|
|
2154
3038
|
|
|
@@ -2186,10 +3070,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2186
3070
|
});
|
|
2187
3071
|
|
|
2188
3072
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2189
|
-
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0,
|
|
2190
|
-
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([
|
|
3073
|
+
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
3074
|
+
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([4000, 97]);
|
|
2191
3075
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
2192
3076
|
expect(writePayloads.every(payload => payload.append === false)).toBe(true);
|
|
3077
|
+
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
2193
3078
|
});
|
|
2194
3079
|
|
|
2195
3080
|
test('continues to DeviceFirmwareUpdate when FilesystemFileWrite returns processed chunk length', async () => {
|
|
@@ -2208,9 +3093,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2208
3093
|
},
|
|
2209
3094
|
});
|
|
2210
3095
|
}
|
|
2211
|
-
if (name === '
|
|
3096
|
+
if (name === 'DeviceFirmwareUpdateRequest') {
|
|
2212
3097
|
return Promise.resolve({ type: 'Success', message: { message: 'ok' } });
|
|
2213
3098
|
}
|
|
3099
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
3100
|
+
return Promise.resolve({
|
|
3101
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3102
|
+
message: {
|
|
3103
|
+
records: [{ target_id: 4, status: 2 }],
|
|
3104
|
+
},
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
2214
3107
|
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2215
3108
|
});
|
|
2216
3109
|
|
|
@@ -2221,21 +3114,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2221
3114
|
method.postTipMessage = jest.fn();
|
|
2222
3115
|
|
|
2223
3116
|
await (method as any).executeProtocolV2Update({
|
|
2224
|
-
|
|
3117
|
+
resourceBinaryMap: [],
|
|
2225
3118
|
bootloaderBinary: null,
|
|
2226
3119
|
fwBinaryMap: [
|
|
2227
3120
|
{
|
|
2228
3121
|
fileName: 'firmware.bin',
|
|
2229
3122
|
binary: new Uint8Array(4097).buffer,
|
|
3123
|
+
targetId: 4,
|
|
2230
3124
|
},
|
|
2231
3125
|
],
|
|
2232
3126
|
});
|
|
2233
3127
|
|
|
2234
3128
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2235
|
-
'
|
|
2236
|
-
['Success', '
|
|
3129
|
+
'DeviceFirmwareUpdateRequest',
|
|
3130
|
+
['Success', 'DeviceFirmwareUpdateStatus'],
|
|
2237
3131
|
{
|
|
2238
|
-
targets: [{ target_id:
|
|
3132
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2239
3133
|
},
|
|
2240
3134
|
expect.any(Object)
|
|
2241
3135
|
);
|
|
@@ -2281,6 +3175,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2281
3175
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2282
3176
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
2283
3177
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
3178
|
+
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
2284
3179
|
});
|
|
2285
3180
|
|
|
2286
3181
|
test('consumes Protocol V2 install progress before final update success', async () => {
|
|
@@ -2299,18 +3194,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2299
3194
|
method.postTipMessage = jest.fn();
|
|
2300
3195
|
|
|
2301
3196
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2302
|
-
targets: [{ target_id:
|
|
3197
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2303
3198
|
});
|
|
2304
3199
|
|
|
2305
3200
|
const callOptions = typedCall.mock.calls[0][3];
|
|
2306
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
2307
|
-
expect(callOptions.intermediateTypes).toEqual(['
|
|
3201
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
3202
|
+
expect(callOptions.intermediateTypes).toEqual(['DeviceFirmwareUpdateStatus']);
|
|
2308
3203
|
callOptions.onIntermediateResponse({
|
|
2309
|
-
type: '
|
|
2310
|
-
message: { target_id:
|
|
3204
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3205
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2311
3206
|
});
|
|
2312
3207
|
|
|
2313
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
3208
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2314
3209
|
});
|
|
2315
3210
|
|
|
2316
3211
|
test('accepts Protocol V2 firmware update status as start response', async () => {
|
|
@@ -2321,8 +3216,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2321
3216
|
},
|
|
2322
3217
|
});
|
|
2323
3218
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2324
|
-
type: '
|
|
2325
|
-
message: {
|
|
3219
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3220
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2326
3221
|
});
|
|
2327
3222
|
|
|
2328
3223
|
(method as any).device = stubDevice({
|
|
@@ -2332,29 +3227,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2332
3227
|
method.postTipMessage = jest.fn();
|
|
2333
3228
|
|
|
2334
3229
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2335
|
-
targets: [{ target_id:
|
|
3230
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2336
3231
|
});
|
|
2337
3232
|
|
|
2338
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3233
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2339
3234
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2340
3235
|
});
|
|
2341
3236
|
});
|
|
2342
3237
|
|
|
2343
3238
|
describe('Protocol V2 firmware update method', () => {
|
|
2344
|
-
test('returns
|
|
3239
|
+
test('returns DeviceFirmwareUpdateStatus from low-level update trigger', async () => {
|
|
2345
3240
|
const method = new DeviceFirmwareUpdate({
|
|
2346
3241
|
id: 1,
|
|
2347
3242
|
payload: {
|
|
2348
3243
|
method: 'deviceFirmwareUpdate',
|
|
2349
|
-
targetId:
|
|
3244
|
+
targetId: 4,
|
|
2350
3245
|
path: 'vol0:firmware.bin',
|
|
2351
3246
|
},
|
|
2352
3247
|
});
|
|
2353
3248
|
method.init();
|
|
2354
3249
|
|
|
2355
3250
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2356
|
-
type: '
|
|
2357
|
-
message: {
|
|
3251
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3252
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2358
3253
|
});
|
|
2359
3254
|
|
|
2360
3255
|
(method as any).device = stubDevice({
|
|
@@ -2362,11 +3257,11 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2362
3257
|
});
|
|
2363
3258
|
|
|
2364
3259
|
await expect(method.run()).resolves.toEqual({
|
|
2365
|
-
|
|
3260
|
+
records: [{ target_id: 4, status: 1 }],
|
|
2366
3261
|
});
|
|
2367
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3262
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2368
3263
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2369
|
-
targets: [{ target_id:
|
|
3264
|
+
targets: [{ target_id: 4, path: 'vol0:firmware.bin' }],
|
|
2370
3265
|
});
|
|
2371
3266
|
});
|
|
2372
3267
|
|
|
@@ -2375,7 +3270,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2375
3270
|
id: 1,
|
|
2376
3271
|
payload: {
|
|
2377
3272
|
method: 'deviceFirmwareUpdate',
|
|
2378
|
-
targetId:
|
|
3273
|
+
targetId: 4,
|
|
2379
3274
|
path: 'vol0:firmware.bin',
|
|
2380
3275
|
},
|
|
2381
3276
|
});
|
|
@@ -2429,7 +3324,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2429
3324
|
targets: [
|
|
2430
3325
|
{
|
|
2431
3326
|
target_id: undefined,
|
|
2432
|
-
targetId:
|
|
3327
|
+
targetId: 4,
|
|
2433
3328
|
path: 'vol0:firmware.bin',
|
|
2434
3329
|
},
|
|
2435
3330
|
],
|
|
@@ -2442,31 +3337,33 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2442
3337
|
|
|
2443
3338
|
await method.run();
|
|
2444
3339
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2445
|
-
targets: [{ target_id:
|
|
3340
|
+
targets: [{ target_id: 4, path: 'vol0:firmware.bin' }],
|
|
2446
3341
|
});
|
|
2447
3342
|
});
|
|
2448
|
-
});
|
|
2449
3343
|
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
const
|
|
2453
|
-
const method = new DevReboot({
|
|
3344
|
+
test('accepts firmware target enum names from low-level update params', async () => {
|
|
3345
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3346
|
+
const method = new DeviceFirmwareUpdate({
|
|
2454
3347
|
id: 1,
|
|
2455
3348
|
payload: {
|
|
2456
|
-
method: '
|
|
2457
|
-
|
|
3349
|
+
method: 'deviceFirmwareUpdate',
|
|
3350
|
+
targetId: 'FW_MGMT_TARGET_CRATE',
|
|
3351
|
+
path: 'vol0:resource.bin',
|
|
2458
3352
|
},
|
|
2459
3353
|
});
|
|
2460
3354
|
method.init();
|
|
2461
|
-
(method as any).device = stubDevice({
|
|
3355
|
+
(method as any).device = stubDevice({
|
|
3356
|
+
commands: { typedCall },
|
|
3357
|
+
});
|
|
2462
3358
|
|
|
2463
3359
|
await method.run();
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
reboot_type: 2,
|
|
3360
|
+
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3361
|
+
targets: [{ target_id: 1, path: 'vol0:resource.bin' }],
|
|
2467
3362
|
});
|
|
2468
3363
|
});
|
|
3364
|
+
});
|
|
2469
3365
|
|
|
3366
|
+
describe('Protocol V2 reboot methods', () => {
|
|
2470
3367
|
test('sends DeviceReboot from deviceReboot', async () => {
|
|
2471
3368
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
2472
3369
|
const method = new DeviceReboot({
|
|
@@ -2487,12 +3384,80 @@ describe('Protocol V2 reboot methods', () => {
|
|
|
2487
3384
|
});
|
|
2488
3385
|
});
|
|
2489
3386
|
|
|
3387
|
+
describe('Protocol V2 current low-level methods', () => {
|
|
3388
|
+
test('sends ProtocolInfoRequest from protocolInfoRequest', async () => {
|
|
3389
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { version: 1 } });
|
|
3390
|
+
const method = new ProtocolInfoRequest({
|
|
3391
|
+
id: 1,
|
|
3392
|
+
payload: { method: 'protocolInfoRequest' },
|
|
3393
|
+
});
|
|
3394
|
+
method.init();
|
|
3395
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3396
|
+
|
|
3397
|
+
await method.run();
|
|
3398
|
+
|
|
3399
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
3400
|
+
});
|
|
3401
|
+
|
|
3402
|
+
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
3403
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3404
|
+
const setMethod = new DeviceFactoryInfoSet({
|
|
3405
|
+
id: 1,
|
|
3406
|
+
payload: {
|
|
3407
|
+
method: 'deviceFactoryInfoSet',
|
|
3408
|
+
serial_number: 'PR2SERIAL',
|
|
3409
|
+
burn_in_completed: true,
|
|
3410
|
+
},
|
|
3411
|
+
});
|
|
3412
|
+
setMethod.init();
|
|
3413
|
+
(setMethod as any).device = stubDevice({ commands: { typedCall } });
|
|
3414
|
+
|
|
3415
|
+
await setMethod.run();
|
|
3416
|
+
|
|
3417
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceFactoryInfoSet', 'Success', {
|
|
3418
|
+
info: {
|
|
3419
|
+
version: undefined,
|
|
3420
|
+
serial_number: 'PR2SERIAL',
|
|
3421
|
+
burn_in_completed: true,
|
|
3422
|
+
factory_test_completed: undefined,
|
|
3423
|
+
manufacture_time: undefined,
|
|
3424
|
+
},
|
|
3425
|
+
});
|
|
3426
|
+
|
|
3427
|
+
typedCall.mockResolvedValueOnce({ message: { serial_number: 'PR2SERIAL' } });
|
|
3428
|
+
const getMethod = new DeviceFactoryInfoGet({
|
|
3429
|
+
id: 2,
|
|
3430
|
+
payload: { method: 'deviceFactoryInfoGet' },
|
|
3431
|
+
});
|
|
3432
|
+
getMethod.init();
|
|
3433
|
+
(getMethod as any).device = stubDevice({ commands: { typedCall } });
|
|
3434
|
+
|
|
3435
|
+
await getMethod.run();
|
|
3436
|
+
|
|
3437
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceFactoryInfoGet', 'DeviceFactoryInfo', {});
|
|
3438
|
+
});
|
|
3439
|
+
|
|
3440
|
+
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|
|
3441
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3442
|
+
const method = new FilesystemPermissionFix({
|
|
3443
|
+
id: 1,
|
|
3444
|
+
payload: { method: 'filesystemPermissionFix' },
|
|
3445
|
+
});
|
|
3446
|
+
method.init();
|
|
3447
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3448
|
+
|
|
3449
|
+
await method.run();
|
|
3450
|
+
|
|
3451
|
+
expect(typedCall).toHaveBeenCalledWith('FilesystemPermissionFix', 'Success', {});
|
|
3452
|
+
});
|
|
3453
|
+
});
|
|
3454
|
+
|
|
2490
3455
|
describe('Protocol V2 raw device info method', () => {
|
|
2491
3456
|
const buildMethod = (payload: Record<string, unknown> = {}) => {
|
|
2492
|
-
const method = new
|
|
3457
|
+
const method = new DeviceInfoGet({
|
|
2493
3458
|
id: 1,
|
|
2494
3459
|
payload: {
|
|
2495
|
-
method: '
|
|
3460
|
+
method: 'deviceInfoGet',
|
|
2496
3461
|
...payload,
|
|
2497
3462
|
},
|
|
2498
3463
|
});
|
|
@@ -2502,7 +3467,7 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2502
3467
|
|
|
2503
3468
|
test('passes requested targets/types through and returns the raw DeviceInfo message', async () => {
|
|
2504
3469
|
const method = buildMethod({
|
|
2505
|
-
targets: { hw: true, se1: true
|
|
3470
|
+
targets: { hw: true, se1: true },
|
|
2506
3471
|
types: { version: true, hash: true },
|
|
2507
3472
|
});
|
|
2508
3473
|
const typedCall = jest.fn().mockResolvedValue({
|
|
@@ -2518,9 +3483,8 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2518
3483
|
protocol_version: 1,
|
|
2519
3484
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2520
3485
|
});
|
|
2521
|
-
// 未知 key(junk)被过滤,已选 key 原样透传,不构建 DeviceProfile
|
|
2522
3486
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2523
|
-
'
|
|
3487
|
+
'DeviceInfoGet',
|
|
2524
3488
|
'DeviceInfo',
|
|
2525
3489
|
{
|
|
2526
3490
|
targets: { hw: true, se1: true },
|
|
@@ -2530,6 +3494,20 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2530
3494
|
);
|
|
2531
3495
|
});
|
|
2532
3496
|
|
|
3497
|
+
test('rejects removed target/type names before transport call', () => {
|
|
3498
|
+
expect(() =>
|
|
3499
|
+
buildMethod({
|
|
3500
|
+
targets: { hw: true, bt: true },
|
|
3501
|
+
})
|
|
3502
|
+
).toThrow('targets');
|
|
3503
|
+
|
|
3504
|
+
expect(() =>
|
|
3505
|
+
buildMethod({
|
|
3506
|
+
types: { version: true, digest: true },
|
|
3507
|
+
})
|
|
3508
|
+
).toThrow('types');
|
|
3509
|
+
});
|
|
3510
|
+
|
|
2533
3511
|
test('defaults to the basic targets/types when none are given', async () => {
|
|
2534
3512
|
const method = buildMethod();
|
|
2535
3513
|
const typedCall = jest.fn().mockResolvedValue({ type: 'DeviceInfo', message: {} });
|
|
@@ -2541,10 +3519,10 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2541
3519
|
await method.run();
|
|
2542
3520
|
|
|
2543
3521
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2544
|
-
'
|
|
3522
|
+
'DeviceInfoGet',
|
|
2545
3523
|
'DeviceInfo',
|
|
2546
3524
|
{
|
|
2547
|
-
targets: { hw: true, fw: true,
|
|
3525
|
+
targets: { hw: true, fw: true, coprocessor: true, status: true },
|
|
2548
3526
|
types: { version: true, specific: true },
|
|
2549
3527
|
},
|
|
2550
3528
|
{ timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }
|
|
@@ -2564,48 +3542,6 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2564
3542
|
});
|
|
2565
3543
|
});
|
|
2566
3544
|
|
|
2567
|
-
describe('Protocol V2 onboarding status method', () => {
|
|
2568
|
-
test('returns OnboardingStatus from low-level status query', async () => {
|
|
2569
|
-
const method = new DeviceGetOnboardingStatus({
|
|
2570
|
-
id: 1,
|
|
2571
|
-
payload: {
|
|
2572
|
-
method: 'deviceGetOnboardingStatus',
|
|
2573
|
-
},
|
|
2574
|
-
});
|
|
2575
|
-
method.init();
|
|
2576
|
-
|
|
2577
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
2578
|
-
type: 'OnboardingStatus',
|
|
2579
|
-
message: {
|
|
2580
|
-
step: 3,
|
|
2581
|
-
setup: {
|
|
2582
|
-
restore: {
|
|
2583
|
-
mnemonic: true,
|
|
2584
|
-
},
|
|
2585
|
-
},
|
|
2586
|
-
detail_code: 7,
|
|
2587
|
-
detail_str: 'Recovery Phrase',
|
|
2588
|
-
},
|
|
2589
|
-
});
|
|
2590
|
-
|
|
2591
|
-
(method as any).device = stubDevice({
|
|
2592
|
-
commands: { typedCall },
|
|
2593
|
-
});
|
|
2594
|
-
|
|
2595
|
-
await expect(method.run()).resolves.toEqual({
|
|
2596
|
-
step: 3,
|
|
2597
|
-
setup: {
|
|
2598
|
-
restore: {
|
|
2599
|
-
mnemonic: true,
|
|
2600
|
-
},
|
|
2601
|
-
},
|
|
2602
|
-
detail_code: 7,
|
|
2603
|
-
detail_str: 'Recovery Phrase',
|
|
2604
|
-
});
|
|
2605
|
-
expect(typedCall).toHaveBeenCalledWith('GetOnboardingStatus', 'OnboardingStatus', {});
|
|
2606
|
-
});
|
|
2607
|
-
});
|
|
2608
|
-
|
|
2609
3545
|
describe('Protocol V2 file write method', () => {
|
|
2610
3546
|
test('rejects invalid write parameters before transport call', () => {
|
|
2611
3547
|
expect(() => {
|
|
@@ -2655,17 +3591,22 @@ describe('Protocol V2 file write method', () => {
|
|
|
2655
3591
|
method.init();
|
|
2656
3592
|
await method.run();
|
|
2657
3593
|
|
|
2658
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
3594
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
3595
|
+
'FilesystemFileWrite',
|
|
3596
|
+
'FilesystemFile',
|
|
3597
|
+
{
|
|
3598
|
+
file: {
|
|
3599
|
+
path: 'vol1:test.bin',
|
|
3600
|
+
offset: 1,
|
|
3601
|
+
total_size: 2,
|
|
3602
|
+
data: new Uint8Array([1]),
|
|
3603
|
+
},
|
|
3604
|
+
overwrite: false,
|
|
3605
|
+
append: false,
|
|
3606
|
+
ui_percentage: 100,
|
|
2664
3607
|
},
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
ui_percentage: 99,
|
|
2668
|
-
}, { timeoutMs: undefined });
|
|
3608
|
+
{ timeoutMs: undefined }
|
|
3609
|
+
);
|
|
2669
3610
|
expect(method.postMessage).toHaveBeenCalledWith({
|
|
2670
3611
|
event: 'UI_EVENT',
|
|
2671
3612
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
@@ -2698,28 +3639,40 @@ describe('Protocol V2 file write method', () => {
|
|
|
2698
3639
|
const result = await method.run();
|
|
2699
3640
|
|
|
2700
3641
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2701
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
3642
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3643
|
+
1,
|
|
3644
|
+
'FilesystemFileWrite',
|
|
3645
|
+
'FilesystemFile',
|
|
3646
|
+
{
|
|
3647
|
+
file: {
|
|
3648
|
+
path: 'vol1:test.bin',
|
|
3649
|
+
offset: 0,
|
|
3650
|
+
total_size: 4097,
|
|
3651
|
+
data: data.slice(0, 4000),
|
|
3652
|
+
},
|
|
3653
|
+
overwrite: true,
|
|
3654
|
+
append: false,
|
|
3655
|
+
ui_percentage: 0,
|
|
3656
|
+
},
|
|
3657
|
+
{ timeoutMs: undefined }
|
|
3658
|
+
);
|
|
3659
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3660
|
+
2,
|
|
3661
|
+
'FilesystemFileWrite',
|
|
3662
|
+
'FilesystemFile',
|
|
3663
|
+
{
|
|
3664
|
+
file: {
|
|
3665
|
+
path: 'vol1:test.bin',
|
|
3666
|
+
offset: 4000,
|
|
3667
|
+
total_size: 4097,
|
|
3668
|
+
data: data.slice(4000),
|
|
3669
|
+
},
|
|
3670
|
+
overwrite: false,
|
|
3671
|
+
append: false,
|
|
3672
|
+
ui_percentage: 100,
|
|
3673
|
+
},
|
|
3674
|
+
{ timeoutMs: undefined }
|
|
3675
|
+
);
|
|
2723
3676
|
expect(result).toMatchObject({
|
|
2724
3677
|
path: 'vol1:test.bin',
|
|
2725
3678
|
processed_byte: 4097,
|
|
@@ -2729,8 +3682,8 @@ describe('Protocol V2 file write method', () => {
|
|
|
2729
3682
|
event: 'UI_EVENT',
|
|
2730
3683
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
2731
3684
|
payload: expect.objectContaining({
|
|
2732
|
-
progress:
|
|
2733
|
-
transferredBytes:
|
|
3685
|
+
progress: 97,
|
|
3686
|
+
transferredBytes: 4000,
|
|
2734
3687
|
totalBytes: 4097,
|
|
2735
3688
|
elapsedMs: expect.any(Number),
|
|
2736
3689
|
}),
|
|
@@ -2855,7 +3808,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2855
3808
|
total_size: 0,
|
|
2856
3809
|
},
|
|
2857
3810
|
chunk_len: 64,
|
|
2858
|
-
ui_percentage:
|
|
3811
|
+
ui_percentage: 0,
|
|
2859
3812
|
});
|
|
2860
3813
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'FilesystemFileRead', 'FilesystemFile', {
|
|
2861
3814
|
file: {
|
|
@@ -2864,7 +3817,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2864
3817
|
total_size: 0,
|
|
2865
3818
|
},
|
|
2866
3819
|
chunk_len: 1,
|
|
2867
|
-
ui_percentage:
|
|
3820
|
+
ui_percentage: 100,
|
|
2868
3821
|
});
|
|
2869
3822
|
expect(result.data.byteLength).toBe(65);
|
|
2870
3823
|
expect(result.data[0]).toBe(1);
|