@onekeyfe/hd-core 1.2.0-alpha.1 → 1.2.0-alpha.10
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__/connectSettings.test.ts +19 -0
- package/__tests__/device-wallet-session-store.test.ts +131 -0
- package/__tests__/preInitialize.test.ts +33 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
- package/__tests__/protocol-v2.test.ts +1784 -502
- package/dist/api/ClearSessionCache.d.ts +9 -0
- package/dist/api/ClearSessionCache.d.ts.map +1 -0
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +24 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +1 -0
- 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/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/index.d.ts +8 -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 +3 -2
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +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/DeviceSessionGet.d.ts +9 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
- 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 +27 -18
- 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/api/tron/TronSignMessage.d.ts +0 -1
- package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
- package/dist/api/tron/TronSignTransaction.d.ts +0 -1
- package/dist/api/tron/TronSignTransaction.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/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +6 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/index.d.ts +96 -55
- package/dist/index.js +2077 -1609
- 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 +11 -9
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +1 -0
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdate.d.ts +4 -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/index.d.ts +11 -11
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +16 -17
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +10 -0
- package/dist/types/api/sessionCache.d.ts.map +1 -0
- 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 +21 -2
- 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/ClearSessionCache.ts +28 -0
- package/src/api/FileRead.ts +16 -3
- package/src/api/FileWrite.ts +14 -1
- package/src/api/FirmwareUpdateV4.ts +1075 -264
- package/src/api/GetOnekeyFeatures.ts +1 -1
- package/src/api/GetPassphraseState.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
- package/src/api/device/DeviceRebootToBootloader.ts +4 -4
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +18 -4
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/index.ts +8 -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 +39 -5
- package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
- package/src/api/protocol-v2/DeviceReboot.ts +1 -1
- package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
- package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
- package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
- 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 +71 -47
- package/src/api/ton/TonSignData.ts +1 -5
- package/src/api/tron/TronSignMessage.ts +0 -1
- package/src/api/tron/TronSignTransaction.ts +0 -1
- package/src/core/index.ts +19 -5
- package/src/data/messages/messages-protocol-v2.json +599 -1033
- package/src/data/messages/messages.json +8 -0
- package/src/data-manager/DataManager.ts +7 -0
- package/src/data-manager/connectSettings.ts +2 -4
- package/src/device/Device.ts +89 -70
- package/src/device/DeviceWalletSessionStore.ts +78 -0
- package/src/deviceProfile/buildDeviceFeatures.ts +60 -63
- package/src/deviceProfile/buildDeviceProfile.ts +77 -58
- package/src/inject.ts +13 -16
- package/src/protocols/protocol-v2/features.ts +26 -63
- package/src/protocols/protocol-v2/firmware.ts +12 -36
- package/src/protocols/protocol-v2/index.ts +1 -0
- package/src/protocols/protocol-v2/walletSession.ts +83 -0
- package/src/types/api/firmwareUpdate.ts +16 -20
- package/src/types/api/getDeviceInfo.ts +2 -0
- package/src/types/api/index.ts +17 -20
- package/src/types/api/protocolV2.ts +36 -40
- package/src/types/api/sessionCache.ts +14 -0
- package/src/types/device.ts +1 -0
- package/src/types/params.ts +4 -0
- package/src/types/settings.ts +42 -35
- package/src/utils/deviceFeaturesUtils.ts +44 -13
- 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,23 @@
|
|
|
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
|
|
13
|
+
import DeviceGetFirmwareUpdateStatus from '../src/api/protocol-v2/DeviceGetFirmwareUpdateStatus';
|
|
14
|
+
import DeviceInfoGet from '../src/api/protocol-v2/DeviceInfoGet';
|
|
14
15
|
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
16
|
+
import DeviceSessionGet from '../src/api/protocol-v2/DeviceSessionGet';
|
|
17
|
+
import DeviceStatusGet from '../src/api/protocol-v2/DeviceStatusGet';
|
|
18
|
+
import FilesystemFormat from '../src/api/protocol-v2/FilesystemFormat';
|
|
19
|
+
import FilesystemPermissionFix from '../src/api/protocol-v2/FilesystemPermissionFix';
|
|
20
|
+
import ProtocolInfoRequest from '../src/api/protocol-v2/ProtocolInfoRequest';
|
|
15
21
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
16
22
|
import EVMSignMessageEIP712 from '../src/api/evm/EVMSignMessageEIP712';
|
|
17
23
|
import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
|
|
@@ -35,27 +41,27 @@ import TonSignData from '../src/api/ton/TonSignData';
|
|
|
35
41
|
import TonSignMessage from '../src/api/ton/TonSignMessage';
|
|
36
42
|
import TonSignProof from '../src/api/ton/TonSignProof';
|
|
37
43
|
import TronGetAddress from '../src/api/tron/TronGetAddress';
|
|
38
|
-
import TronSignMessage from '../src/api/tron/TronSignMessage';
|
|
39
|
-
import XrpSignTransaction from '../src/api/xrp/XrpSignTransaction';
|
|
40
44
|
import StellarGetAddress from '../src/api/stellar/StellarGetAddress';
|
|
41
45
|
import BenfenSignMessage from '../src/api/benfen/BenfenSignMessage';
|
|
42
46
|
import { getBitcoinForkVersionRange } from '../src/api/btc/helpers/versionLimit';
|
|
43
47
|
import { DataManager } from '../src/data-manager';
|
|
44
|
-
import {
|
|
48
|
+
import { createCoreApi } from '../src/inject';
|
|
49
|
+
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
45
50
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
46
51
|
import {
|
|
47
52
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
48
|
-
buildMockProtocolV2DeviceInfo,
|
|
49
53
|
requestProtocolV2DeviceInfo,
|
|
50
|
-
setProtocolV2DeviceInfoMock,
|
|
51
54
|
} from '../src/protocols/protocol-v2/features';
|
|
52
55
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} from '../src/
|
|
56
|
+
getProtocolV2WalletSession,
|
|
57
|
+
refreshProtocolV2DeviceStatus,
|
|
58
|
+
} from '../src/protocols/protocol-v2/walletSession';
|
|
59
|
+
import { buildProfileFromProtocolV2, buildProtocolV2FeaturesPayload } from '../src/deviceProfile';
|
|
56
60
|
import {
|
|
57
61
|
getDeviceType,
|
|
58
62
|
getFirmwareType,
|
|
63
|
+
getFirmwareUpdateField,
|
|
64
|
+
getFirmwareUpdateFieldArray,
|
|
59
65
|
getMethodVersionRange,
|
|
60
66
|
isMethodVersionRangeUnsupported,
|
|
61
67
|
} from '../src/utils';
|
|
@@ -74,12 +80,6 @@ jest.mock('../src/data/config', () => ({
|
|
|
74
80
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
75
81
|
}));
|
|
76
82
|
|
|
77
|
-
// DeviceGetDeviceInfo mock 开关默认开启(早期工程板固件没有该消息)。
|
|
78
|
-
// 本测试文件验证的是真实 wire 调用行为,统一关闭;mock 行为单独有用例覆盖。
|
|
79
|
-
beforeAll(() => {
|
|
80
|
-
setProtocolV2DeviceInfoMock(false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
83
|
const descriptor = {
|
|
84
84
|
id: 'ble-id',
|
|
85
85
|
path: 'usb-path',
|
|
@@ -101,8 +101,7 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
101
101
|
d.getCurrentSafetyChecks ??= () => d.features?.safetyChecks;
|
|
102
102
|
d.getCurrentDeviceId ??= () => d.features?.deviceId || undefined;
|
|
103
103
|
d.getCurrentSerialNo ??= () => d.features?.serialNo || '';
|
|
104
|
-
d.getCurrentPassphraseProtection ??= () =>
|
|
105
|
-
d.features?.passphraseProtection;
|
|
104
|
+
d.getCurrentPassphraseProtection ??= () => d.features?.passphraseProtection;
|
|
106
105
|
d.getCurrentMethodVersionRange ??= (fn: (model: any) => any) => {
|
|
107
106
|
const deviceType = d.getCurrentDeviceType();
|
|
108
107
|
const range = fn(deviceType);
|
|
@@ -121,7 +120,19 @@ function normalizeProtocolV2Features(_descriptor: unknown, deviceInfo?: Protocol
|
|
|
121
120
|
return buildProtocolV2FeaturesPayload(deviceInfo);
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
|
|
124
|
+
protocol_version: 1,
|
|
125
|
+
hw: {
|
|
126
|
+
serial_no: 'PR9999999999',
|
|
127
|
+
},
|
|
128
|
+
fw: {
|
|
129
|
+
bootloader: {
|
|
130
|
+
version: '0.0.1',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
async function requestProtocolV2Features({
|
|
125
136
|
commands,
|
|
126
137
|
descriptor: inputDescriptor,
|
|
127
138
|
}: {
|
|
@@ -133,63 +144,38 @@ async function requestProtocolV2LegacyFeatures({
|
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
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
147
|
test('normalizes Protocol V2 DeviceInfo into existing Features fields', () => {
|
|
161
148
|
const features = normalizeProtocolV2Features(descriptor as any, {
|
|
162
149
|
protocol_version: 1,
|
|
163
150
|
hw: {
|
|
164
151
|
serial_no: 'PR2SERIAL',
|
|
165
|
-
device_id: 'PRO2-DEVICE-ID',
|
|
166
152
|
},
|
|
167
153
|
fw: {
|
|
168
|
-
|
|
154
|
+
application_data: {
|
|
169
155
|
version: '0.1.0',
|
|
170
156
|
hash: [1, 2, 255],
|
|
171
157
|
},
|
|
172
|
-
|
|
158
|
+
bootloader: {
|
|
173
159
|
version: '0.2.0',
|
|
174
160
|
build_id: 'boot-build',
|
|
175
161
|
hash: new Uint8Array([10, 11]),
|
|
176
162
|
},
|
|
177
|
-
|
|
163
|
+
application: {
|
|
178
164
|
version: '1.2.3',
|
|
179
165
|
build_id: 'app-build',
|
|
180
166
|
hash: 'abc123',
|
|
181
167
|
},
|
|
182
168
|
},
|
|
183
|
-
|
|
184
|
-
|
|
169
|
+
coprocessor: {
|
|
170
|
+
application: {
|
|
185
171
|
version: '4.5.6',
|
|
186
172
|
build_id: 'bt-build',
|
|
187
173
|
hash: [12, 13],
|
|
188
174
|
},
|
|
189
|
-
|
|
175
|
+
bt_adv_name: 'Pro2 BLE',
|
|
190
176
|
},
|
|
191
177
|
se1: {
|
|
192
|
-
|
|
178
|
+
application: {
|
|
193
179
|
version: '7.8.9',
|
|
194
180
|
build_id: 'se-build',
|
|
195
181
|
hash: [14, 15],
|
|
@@ -197,18 +183,19 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
197
183
|
state: 85,
|
|
198
184
|
},
|
|
199
185
|
se2: {
|
|
200
|
-
|
|
186
|
+
application: {
|
|
201
187
|
version: '8.0.0',
|
|
202
188
|
},
|
|
203
189
|
state: 0,
|
|
204
190
|
},
|
|
205
191
|
status: {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
bt_enable: true,
|
|
192
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
193
|
+
unlocked: true,
|
|
209
194
|
init_states: false,
|
|
210
195
|
backup_required: true,
|
|
211
|
-
|
|
196
|
+
passphrase_enabled: true,
|
|
197
|
+
attach_to_pin_enabled: true,
|
|
198
|
+
unlocked_by_attach_to_pin: true,
|
|
212
199
|
},
|
|
213
200
|
});
|
|
214
201
|
|
|
@@ -228,24 +215,48 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
228
215
|
expect(features.verify?.bleHash).toBe('0c0d');
|
|
229
216
|
expect(features.se01Version).toBe('7.8.9');
|
|
230
217
|
expect(features.verify?.se01Hash).toBe('0e0f');
|
|
231
|
-
expect(features.label).
|
|
232
|
-
expect(features.language).
|
|
218
|
+
expect(features.label).toBeNull();
|
|
219
|
+
expect(features.language).toBeNull();
|
|
233
220
|
expect(features.initialized).toBe(false);
|
|
221
|
+
expect(features.unlocked).toBe(true);
|
|
234
222
|
expect(features.backupRequired).toBe(true);
|
|
235
223
|
expect(features.passphraseProtection).toBe(true);
|
|
236
|
-
expect(features.
|
|
224
|
+
expect(features.attachToPinEnabled).toBe(true);
|
|
225
|
+
expect(features.unlockedAttachPin).toBe(true);
|
|
226
|
+
expect(features.bleEnabled).toBeNull();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('marks Protocol V2 DeviceInfo without status as bootloader mode', () => {
|
|
230
|
+
const deviceInfo = {
|
|
231
|
+
protocol_version: 1,
|
|
232
|
+
hw: {
|
|
233
|
+
serial_no: 'PR2BOOT',
|
|
234
|
+
},
|
|
235
|
+
fw: {
|
|
236
|
+
bootloader: {
|
|
237
|
+
version: '0.2.0',
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
const features = normalizeProtocolV2Features(descriptor as any, deviceInfo);
|
|
242
|
+
const profile = buildProfileFromProtocolV2({ deviceInfo });
|
|
243
|
+
|
|
244
|
+
expect(features.mode).toBe('bootloader');
|
|
245
|
+
expect(features.bootloaderMode).toBe(true);
|
|
246
|
+
expect(features.initialized).toBeNull();
|
|
247
|
+
expect(profile.status.mode).toBe('bootloader');
|
|
248
|
+
expect(profile.status.bootloaderMode).toBe(true);
|
|
237
249
|
});
|
|
238
250
|
|
|
239
|
-
test('uses
|
|
251
|
+
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
240
252
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
241
|
-
// Pro2 版本线独立于 Pro 系列:V2 设备无论固件版本都支持 GetPassphraseState
|
|
242
253
|
features.firmwareVersion = '1.2.3';
|
|
254
|
+
features.passphraseProtection = true;
|
|
243
255
|
const typedCall = jest.fn().mockResolvedValue({
|
|
244
|
-
type: '
|
|
256
|
+
type: 'DeviceSession',
|
|
245
257
|
message: {
|
|
246
|
-
|
|
258
|
+
btc_test_address: 'state-1',
|
|
247
259
|
session_id: 'session-1',
|
|
248
|
-
unlocked_attach_pin: false,
|
|
249
260
|
},
|
|
250
261
|
});
|
|
251
262
|
const commands = { typedCall } as unknown as DeviceCommands;
|
|
@@ -256,25 +267,284 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
256
267
|
await getPassphraseState(device, {
|
|
257
268
|
expectPassphraseState: 'state-1',
|
|
258
269
|
});
|
|
259
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
271
|
+
|
|
272
|
+
await expect(
|
|
273
|
+
getPassphraseState(device, {
|
|
274
|
+
expectPassphraseState: 'state-2',
|
|
275
|
+
allowCreateAttachPin: true,
|
|
276
|
+
})
|
|
277
|
+
).rejects.toEqual(
|
|
278
|
+
expect.objectContaining({
|
|
279
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
262
283
|
|
|
263
284
|
await getPassphraseState(device, {
|
|
264
|
-
|
|
265
|
-
allowCreateAttachPin: true,
|
|
285
|
+
onlyMainPin: true,
|
|
266
286
|
});
|
|
267
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
268
|
-
|
|
269
|
-
|
|
287
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('deviceStatusGet returns raw DeviceStatus without updating features', async () => {
|
|
291
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
292
|
+
type: 'DeviceStatus',
|
|
293
|
+
message: { device_id: 'device-1', unlocked: true },
|
|
294
|
+
});
|
|
295
|
+
const updateProtocolV2Features = jest.fn();
|
|
296
|
+
const method = new DeviceStatusGet({
|
|
297
|
+
payload: { method: 'deviceStatusGet', connectId: 'connect-id' },
|
|
270
298
|
});
|
|
299
|
+
method.init();
|
|
300
|
+
method.device = stubDevice({
|
|
301
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
302
|
+
commands: { typedCall },
|
|
303
|
+
updateProtocolV2Features,
|
|
304
|
+
}) as any;
|
|
271
305
|
|
|
272
|
-
await
|
|
273
|
-
|
|
306
|
+
await expect(method.run()).resolves.toEqual({ device_id: 'device-1', unlocked: true });
|
|
307
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
308
|
+
expect(updateProtocolV2Features).not.toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('deviceSessionGet maps sessionId and does not mutate wallet cache', async () => {
|
|
312
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
313
|
+
type: 'DeviceSession',
|
|
314
|
+
message: { session_id: 'new-session', btc_test_address: 'state-a' },
|
|
315
|
+
});
|
|
316
|
+
const updateInternalState = jest.fn();
|
|
317
|
+
const method = new DeviceSessionGet({
|
|
318
|
+
payload: {
|
|
319
|
+
method: 'deviceSessionGet',
|
|
320
|
+
connectId: 'connect-id',
|
|
321
|
+
sessionId: 'cached-session',
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
method.init();
|
|
325
|
+
method.device = stubDevice({
|
|
326
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
327
|
+
commands: { typedCall },
|
|
328
|
+
updateInternalState,
|
|
329
|
+
}) as any;
|
|
330
|
+
|
|
331
|
+
await expect(method.run()).resolves.toEqual({
|
|
332
|
+
session_id: 'new-session',
|
|
333
|
+
btc_test_address: 'state-a',
|
|
334
|
+
});
|
|
335
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
336
|
+
session_id: 'cached-session',
|
|
337
|
+
});
|
|
338
|
+
expect(updateInternalState).not.toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('routes raw status and session methods through CoreApi', async () => {
|
|
342
|
+
const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
|
|
343
|
+
const api = createCoreApi(call as any);
|
|
344
|
+
|
|
345
|
+
await api.deviceStatusGet('connect-id', { retryCount: 1 });
|
|
346
|
+
await api.deviceSessionGet('connect-id', { sessionId: 'cached-session' });
|
|
347
|
+
|
|
348
|
+
expect(call).toHaveBeenNthCalledWith(1, {
|
|
349
|
+
method: 'deviceStatusGet',
|
|
350
|
+
connectId: 'connect-id',
|
|
351
|
+
retryCount: 1,
|
|
352
|
+
});
|
|
353
|
+
expect(call).toHaveBeenNthCalledWith(2, {
|
|
354
|
+
method: 'deviceSessionGet',
|
|
355
|
+
connectId: 'connect-id',
|
|
356
|
+
sessionId: 'cached-session',
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('reuses the cached session id for the selected Pro2 wallet', async () => {
|
|
361
|
+
const device = Device.fromDescriptor({
|
|
362
|
+
id: 'cache-device-1',
|
|
363
|
+
path: 'cache-path-1',
|
|
364
|
+
protocolType: 'V2',
|
|
365
|
+
} as any);
|
|
366
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
367
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
368
|
+
{
|
|
369
|
+
status: {
|
|
370
|
+
device_id: 'stable-device-1',
|
|
371
|
+
unlocked: true,
|
|
372
|
+
passphrase_enabled: true,
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
device.passphraseState = 'state-a';
|
|
377
|
+
preloadSessionCache('stable-device-1', 'state-a', 'session-a');
|
|
378
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
379
|
+
type: 'DeviceSession',
|
|
380
|
+
message: { session_id: 'session-b', btc_test_address: 'state-a' },
|
|
381
|
+
});
|
|
382
|
+
(device as any).commands = { typedCall };
|
|
383
|
+
|
|
384
|
+
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
385
|
+
passphraseState: 'state-a',
|
|
386
|
+
newSession: 'session-b',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
390
|
+
session_id: 'session-a',
|
|
391
|
+
});
|
|
392
|
+
expect(device.getInternalState()).toBe('session-b');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('does not reuse another Pro2 wallet session without passphraseState', async () => {
|
|
396
|
+
const device = Device.fromDescriptor({
|
|
397
|
+
id: 'cache-device-2',
|
|
398
|
+
path: 'cache-path-2',
|
|
399
|
+
protocolType: 'V2',
|
|
400
|
+
} as any);
|
|
401
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
402
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
403
|
+
{ status: { device_id: 'stable-device-2', unlocked: true } }
|
|
404
|
+
);
|
|
405
|
+
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
406
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
407
|
+
type: 'DeviceSession',
|
|
408
|
+
message: { session_id: 'main-session' },
|
|
409
|
+
});
|
|
410
|
+
(device as any).commands = { typedCall };
|
|
411
|
+
|
|
412
|
+
await getProtocolV2WalletSession(device);
|
|
413
|
+
|
|
414
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
415
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('clears the selected Pro2 cache entry after invalid session rejection', async () => {
|
|
419
|
+
const device = Device.fromDescriptor({
|
|
420
|
+
id: 'cache-device-3',
|
|
421
|
+
path: 'cache-path-3',
|
|
422
|
+
protocolType: 'V2',
|
|
423
|
+
} as any);
|
|
424
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
425
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
426
|
+
{
|
|
427
|
+
status: {
|
|
428
|
+
device_id: 'stable-device-3',
|
|
429
|
+
unlocked: true,
|
|
430
|
+
passphrase_enabled: true,
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
device.passphraseState = 'state-a';
|
|
435
|
+
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
436
|
+
(device as any).commands = {
|
|
437
|
+
typedCall: jest.fn().mockRejectedValue(new Error('Failure_InvalidSession,no error message')),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Failure_InvalidSession');
|
|
441
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
445
|
+
const device = Device.fromDescriptor({
|
|
446
|
+
id: 'cache-device-mismatch',
|
|
447
|
+
path: 'cache-path-mismatch',
|
|
448
|
+
protocolType: 'V2',
|
|
449
|
+
} as any);
|
|
450
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
451
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
452
|
+
{
|
|
453
|
+
status: {
|
|
454
|
+
device_id: 'stable-device-mismatch',
|
|
455
|
+
unlocked: true,
|
|
456
|
+
passphrase_enabled: true,
|
|
457
|
+
},
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
device.passphraseState = 'state-a';
|
|
461
|
+
preloadSessionCache('stable-device-mismatch', 'state-a', 'session-a');
|
|
462
|
+
(device as any).commands = {
|
|
463
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
464
|
+
type: 'DeviceSession',
|
|
465
|
+
message: { session_id: 'session-b', btc_test_address: 'state-b' },
|
|
466
|
+
}),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
await expect(
|
|
470
|
+
getPassphraseStateWithRefreshDeviceInfo(device, { expectPassphraseState: 'state-a' })
|
|
471
|
+
).rejects.toEqual(
|
|
472
|
+
expect.objectContaining({
|
|
473
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('rejects DeviceSessionGet while cached Pro2 status is locked', async () => {
|
|
480
|
+
const device = Device.fromDescriptor({
|
|
481
|
+
id: 'cache-device-4',
|
|
482
|
+
path: 'cache-path-4',
|
|
483
|
+
protocolType: 'V2',
|
|
484
|
+
} as any);
|
|
485
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
486
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
487
|
+
{ status: { device_id: 'stable-device-4', unlocked: false } }
|
|
488
|
+
);
|
|
489
|
+
const typedCall = jest.fn();
|
|
490
|
+
(device as any).commands = { typedCall };
|
|
491
|
+
|
|
492
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Device is locked');
|
|
493
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
497
|
+
const device = Device.fromDescriptor({
|
|
498
|
+
id: 'cache-device-5',
|
|
499
|
+
path: 'cache-path-5',
|
|
500
|
+
protocolType: 'V2',
|
|
501
|
+
} as any);
|
|
502
|
+
const typedCall = jest.fn();
|
|
503
|
+
(device as any).commands = { typedCall };
|
|
504
|
+
|
|
505
|
+
await expect(getPassphraseStateWithRefreshDeviceInfo(device)).resolves.toEqual({
|
|
506
|
+
passphraseState: undefined,
|
|
507
|
+
newSession: undefined,
|
|
508
|
+
unlockedAttachPin: undefined,
|
|
509
|
+
});
|
|
510
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('merges DeviceStatus into existing Protocol V2 features', async () => {
|
|
514
|
+
const device = Device.fromDescriptor({
|
|
515
|
+
id: 'status-device',
|
|
516
|
+
path: 'status-path',
|
|
517
|
+
protocolType: 'V2',
|
|
518
|
+
} as any);
|
|
519
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
520
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
521
|
+
{
|
|
522
|
+
protocol_version: 2,
|
|
523
|
+
fw: { application: { version: '1.2.3' } },
|
|
524
|
+
se1: { application: { version: '4.5.6' } },
|
|
525
|
+
status: { unlocked: false, passphrase_enabled: false },
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
529
|
+
type: 'DeviceStatus',
|
|
530
|
+
message: {
|
|
531
|
+
device_id: 'stable-status-device',
|
|
532
|
+
unlocked: true,
|
|
533
|
+
passphrase_enabled: true,
|
|
534
|
+
},
|
|
274
535
|
});
|
|
275
|
-
|
|
276
|
-
|
|
536
|
+
(device as any).commands = { typedCall };
|
|
537
|
+
|
|
538
|
+
const features = await refreshProtocolV2DeviceStatus(device);
|
|
539
|
+
|
|
540
|
+
expect(features).toMatchObject({
|
|
541
|
+
deviceId: 'stable-status-device',
|
|
542
|
+
unlocked: true,
|
|
543
|
+
passphraseProtection: true,
|
|
544
|
+
firmwareVersion: '1.2.3',
|
|
545
|
+
se01Version: '4.5.6',
|
|
277
546
|
});
|
|
547
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
278
548
|
});
|
|
279
549
|
|
|
280
550
|
test('returns unified GetPassphraseState object payload for existing Pro devices', async () => {
|
|
@@ -302,7 +572,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
302
572
|
},
|
|
303
573
|
});
|
|
304
574
|
method.device = stubDevice({
|
|
305
|
-
originalDescriptor: { ...descriptor, protocolType: '
|
|
575
|
+
originalDescriptor: { ...descriptor, protocolType: 'V1' },
|
|
306
576
|
features,
|
|
307
577
|
commands: { typedCall },
|
|
308
578
|
updateInternalState,
|
|
@@ -335,11 +605,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
335
605
|
unlockedAttachPin: true,
|
|
336
606
|
};
|
|
337
607
|
const typedCall = jest.fn().mockResolvedValue({
|
|
338
|
-
type: '
|
|
608
|
+
type: 'DeviceSession',
|
|
339
609
|
message: {
|
|
340
|
-
|
|
610
|
+
btc_test_address: 'state-pro2',
|
|
341
611
|
session_id: 'session-pro2',
|
|
342
|
-
unlocked_attach_pin: false,
|
|
343
612
|
},
|
|
344
613
|
});
|
|
345
614
|
const updateInternalState = jest.fn();
|
|
@@ -364,28 +633,77 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
364
633
|
await expect(method.run()).resolves.toEqual({
|
|
365
634
|
passphraseState: 'state-pro2',
|
|
366
635
|
sessionId: 'session-pro2',
|
|
367
|
-
unlockedAttachPin:
|
|
636
|
+
unlockedAttachPin: true,
|
|
368
637
|
passphraseProtection: true,
|
|
369
638
|
});
|
|
370
639
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
371
640
|
});
|
|
372
641
|
|
|
642
|
+
test('honors initSession when getting Pro2 passphrase state', async () => {
|
|
643
|
+
const features = {
|
|
644
|
+
deviceId: 'pro2-device-id',
|
|
645
|
+
deviceType: 'pro2',
|
|
646
|
+
firmwareVersion: '9.9.9',
|
|
647
|
+
passphraseProtection: true,
|
|
648
|
+
sessionId: 'old-feature-session',
|
|
649
|
+
unlockedAttachPin: false,
|
|
650
|
+
};
|
|
651
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
652
|
+
type: 'DeviceSession',
|
|
653
|
+
message: {
|
|
654
|
+
btc_test_address: 'state-pro2-new',
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
const clearInternalState = jest.fn();
|
|
658
|
+
const updateInternalState = jest.fn();
|
|
659
|
+
const method = new GetPassphraseState({
|
|
660
|
+
payload: {
|
|
661
|
+
method: 'getPassphraseState',
|
|
662
|
+
connectId: 'connect-id',
|
|
663
|
+
initSession: true,
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
method.device = stubDevice({
|
|
667
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
668
|
+
features,
|
|
669
|
+
commands: { typedCall },
|
|
670
|
+
clearInternalState,
|
|
671
|
+
updateInternalState,
|
|
672
|
+
getCurrentDeviceId: () => 'pro2-device-id',
|
|
673
|
+
getCurrentPassphraseProtection: () => true,
|
|
674
|
+
}) as any;
|
|
675
|
+
|
|
676
|
+
await expect(method.run()).resolves.toEqual({
|
|
677
|
+
passphraseState: 'state-pro2-new',
|
|
678
|
+
sessionId: undefined,
|
|
679
|
+
unlockedAttachPin: false,
|
|
680
|
+
passphraseProtection: true,
|
|
681
|
+
});
|
|
682
|
+
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
683
|
+
expect(updateInternalState).toHaveBeenCalledWith(
|
|
684
|
+
true,
|
|
685
|
+
'state-pro2-new',
|
|
686
|
+
'pro2-device-id',
|
|
687
|
+
undefined,
|
|
688
|
+
null
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
|
|
373
692
|
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
374
693
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
375
694
|
const typedCall = jest.fn().mockResolvedValue({
|
|
376
|
-
type: '
|
|
695
|
+
type: 'DeviceSession',
|
|
377
696
|
message: {
|
|
378
|
-
|
|
697
|
+
btc_test_address: 'state-profile',
|
|
379
698
|
session_id: 'session-profile',
|
|
380
|
-
unlocked_attach_pin: false,
|
|
381
699
|
},
|
|
382
700
|
});
|
|
383
701
|
|
|
384
702
|
(device as any).features = {
|
|
385
703
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
386
704
|
hw: { serial_no: 'PR2SERIAL' },
|
|
387
|
-
fw: {
|
|
388
|
-
status: {
|
|
705
|
+
fw: { application: { version: '4.15.0' } },
|
|
706
|
+
status: { passphrase_enabled: true },
|
|
389
707
|
}),
|
|
390
708
|
unlocked: true,
|
|
391
709
|
};
|
|
@@ -395,17 +713,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
395
713
|
|
|
396
714
|
device.passphraseState = 'state-profile';
|
|
397
715
|
expect(device.getInternalState()).toBe('session-profile');
|
|
398
|
-
expect(device.getInternalState('
|
|
716
|
+
expect(device.getInternalState('CACHED-ID')).toBeUndefined();
|
|
399
717
|
});
|
|
400
718
|
|
|
401
719
|
test('stores Pro2 passphrase sessions without selecting them implicitly', async () => {
|
|
402
720
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
403
721
|
const typedCall = jest.fn().mockResolvedValue({
|
|
404
|
-
type: '
|
|
722
|
+
type: 'DeviceSession',
|
|
405
723
|
message: {
|
|
406
|
-
|
|
724
|
+
btc_test_address: 'state-auto',
|
|
407
725
|
session_id: 'session-auto',
|
|
408
|
-
unlocked_attach_pin: false,
|
|
409
726
|
},
|
|
410
727
|
});
|
|
411
728
|
|
|
@@ -434,18 +751,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
434
751
|
expect(device.getInternalState()).toBeUndefined();
|
|
435
752
|
device.passphraseState = 'state-auto';
|
|
436
753
|
expect(device.getInternalState()).toBe('session-auto');
|
|
437
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
438
|
-
passphrase_state: undefined,
|
|
439
|
-
});
|
|
754
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
440
755
|
});
|
|
441
756
|
|
|
442
757
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
443
758
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
444
759
|
const typedCall = jest.fn().mockResolvedValue({
|
|
445
|
-
type: '
|
|
760
|
+
type: 'DeviceSession',
|
|
446
761
|
message: {
|
|
447
762
|
session_id: 'main-pin-session',
|
|
448
|
-
unlocked_attach_pin: false,
|
|
449
763
|
},
|
|
450
764
|
});
|
|
451
765
|
|
|
@@ -456,7 +770,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
456
770
|
} as any,
|
|
457
771
|
{
|
|
458
772
|
status: {
|
|
459
|
-
|
|
773
|
+
passphrase_enabled: false,
|
|
460
774
|
},
|
|
461
775
|
}
|
|
462
776
|
);
|
|
@@ -474,19 +788,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
474
788
|
expect(device.features?.passphraseProtection).toBe(false);
|
|
475
789
|
expect(device.features?.sessionId).toBeNull();
|
|
476
790
|
expect(device.getInternalState()).toBeUndefined();
|
|
477
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
478
|
-
_only_main_pin: true,
|
|
479
|
-
});
|
|
791
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
480
792
|
});
|
|
481
793
|
|
|
482
794
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
483
795
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
484
796
|
const typedCall = jest.fn().mockResolvedValue({
|
|
485
|
-
type: '
|
|
797
|
+
type: 'DeviceSession',
|
|
486
798
|
message: {
|
|
487
|
-
|
|
799
|
+
btc_test_address: 'wrong-state',
|
|
488
800
|
session_id: 'wrong-session',
|
|
489
|
-
unlocked_attach_pin: false,
|
|
490
801
|
},
|
|
491
802
|
});
|
|
492
803
|
|
|
@@ -498,14 +809,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
498
809
|
(device as any).features.passphraseProtection = true;
|
|
499
810
|
(device as any).commands = { typedCall };
|
|
500
811
|
|
|
501
|
-
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).
|
|
502
|
-
|
|
812
|
+
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).rejects.toEqual(
|
|
813
|
+
expect.objectContaining({
|
|
814
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
815
|
+
})
|
|
503
816
|
);
|
|
504
817
|
|
|
505
818
|
expect(device.getInternalState()).toBeUndefined();
|
|
506
|
-
expect(typedCall).toHaveBeenNthCalledWith(1, '
|
|
507
|
-
passphrase_state: 'expected-state',
|
|
508
|
-
});
|
|
819
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'DeviceSessionGet', 'DeviceSession', {});
|
|
509
820
|
});
|
|
510
821
|
|
|
511
822
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -518,7 +829,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
518
829
|
expect(features.firmwarePresent).toBeNull();
|
|
519
830
|
});
|
|
520
831
|
|
|
521
|
-
test('uses Protocol V2
|
|
832
|
+
test('uses Protocol V2 status.device_id and does not fall back to serial_no', () => {
|
|
522
833
|
const features = normalizeProtocolV2Features(
|
|
523
834
|
{
|
|
524
835
|
id: 'PR2000000000',
|
|
@@ -528,6 +839,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
528
839
|
{
|
|
529
840
|
hw: {
|
|
530
841
|
serial_no: 'PR9999999999',
|
|
842
|
+
},
|
|
843
|
+
status: {
|
|
531
844
|
device_id: 'DEVICE-ID-9999',
|
|
532
845
|
},
|
|
533
846
|
}
|
|
@@ -559,17 +872,17 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
559
872
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
560
873
|
(device as any).features = {
|
|
561
874
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
562
|
-
serialNo: '
|
|
563
|
-
label: '
|
|
564
|
-
bleName: '
|
|
875
|
+
serialNo: 'CACHED-SERIAL',
|
|
876
|
+
label: 'Cached Label',
|
|
877
|
+
bleName: 'Cached BLE',
|
|
565
878
|
passphraseProtection: true,
|
|
566
879
|
};
|
|
567
880
|
|
|
568
881
|
expect(device.toMessageObject()).toMatchObject({
|
|
569
|
-
uuid: '
|
|
882
|
+
uuid: 'CACHED-SERIAL',
|
|
570
883
|
deviceId: null,
|
|
571
|
-
bleName: '
|
|
572
|
-
label: '
|
|
884
|
+
bleName: 'Cached BLE',
|
|
885
|
+
label: 'Cached Label',
|
|
573
886
|
deviceType: 'pro2',
|
|
574
887
|
});
|
|
575
888
|
expect(device.getCurrentPassphraseProtection()).toBe(true);
|
|
@@ -581,52 +894,53 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
581
894
|
(cached as any).features = {
|
|
582
895
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
583
896
|
deviceId: null,
|
|
584
|
-
serialNo: '
|
|
897
|
+
serialNo: 'CACHED-SERIAL',
|
|
585
898
|
};
|
|
586
899
|
|
|
587
900
|
const current = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
588
901
|
current.updateFromCache(cached);
|
|
589
902
|
|
|
590
903
|
expect(current.getCurrentDeviceId()).toBeUndefined();
|
|
591
|
-
expect(current.getCurrentSerialNo()).toBe('
|
|
904
|
+
expect(current.getCurrentSerialNo()).toBe('CACHED-SERIAL');
|
|
592
905
|
expect(current.toMessageObject()).toMatchObject({
|
|
593
|
-
uuid: '
|
|
906
|
+
uuid: 'CACHED-SERIAL',
|
|
594
907
|
deviceId: null,
|
|
595
908
|
});
|
|
596
909
|
});
|
|
597
910
|
|
|
598
|
-
test('initializes Protocol V2 features from lightweight
|
|
911
|
+
test('initializes Protocol V2 features from lightweight DeviceInfoGet', async () => {
|
|
599
912
|
const commands = {
|
|
600
913
|
typedCall: jest.fn().mockResolvedValueOnce({
|
|
601
914
|
type: 'DeviceInfo',
|
|
602
915
|
message: {
|
|
603
916
|
hw: { serial_no: 'PR2SERIAL' },
|
|
604
917
|
status: {
|
|
918
|
+
device_id: 'PRO2-STATUS-ID',
|
|
605
919
|
init_states: true,
|
|
606
|
-
|
|
920
|
+
passphrase_enabled: true,
|
|
607
921
|
},
|
|
608
922
|
},
|
|
609
923
|
}),
|
|
610
924
|
};
|
|
611
925
|
|
|
612
|
-
const features = await
|
|
926
|
+
const features = await requestProtocolV2Features({
|
|
613
927
|
commands: commands as unknown as DeviceCommands,
|
|
614
928
|
descriptor: descriptor as any,
|
|
615
929
|
});
|
|
616
930
|
|
|
617
|
-
expect(features.deviceId).
|
|
931
|
+
expect(features.deviceId).toBe('PRO2-STATUS-ID');
|
|
618
932
|
expect(features.initialized).toBe(true);
|
|
619
933
|
expect(features.passphraseProtection).toBe(true);
|
|
620
934
|
expect(commands.typedCall).toHaveBeenCalledTimes(1);
|
|
621
935
|
expect(commands.typedCall).toHaveBeenNthCalledWith(
|
|
622
936
|
1,
|
|
623
|
-
'
|
|
937
|
+
'DeviceInfoGet',
|
|
624
938
|
'DeviceInfo',
|
|
625
939
|
{
|
|
626
940
|
targets: {
|
|
627
941
|
hw: true,
|
|
628
942
|
fw: true,
|
|
629
|
-
|
|
943
|
+
coprocessor: true,
|
|
630
944
|
status: true,
|
|
631
945
|
},
|
|
632
946
|
types: {
|
|
@@ -638,13 +952,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
638
952
|
);
|
|
639
953
|
});
|
|
640
954
|
|
|
641
|
-
test('fails initialization when Protocol V2
|
|
955
|
+
test('fails initialization when Protocol V2 DeviceInfoGet fails', async () => {
|
|
642
956
|
const commands = {
|
|
643
957
|
typedCall: jest.fn().mockRejectedValueOnce(new Error('DeviceInfo not supported')),
|
|
644
958
|
};
|
|
645
959
|
|
|
646
960
|
await expect(
|
|
647
|
-
|
|
961
|
+
requestProtocolV2Features({
|
|
648
962
|
commands: commands as unknown as DeviceCommands,
|
|
649
963
|
descriptor: descriptor as any,
|
|
650
964
|
})
|
|
@@ -657,20 +971,21 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
657
971
|
message: {
|
|
658
972
|
hw: { serial_no: 'PR2SERIAL' },
|
|
659
973
|
fw: {
|
|
660
|
-
|
|
974
|
+
application: {
|
|
661
975
|
version: '5.6.7',
|
|
662
976
|
},
|
|
663
977
|
},
|
|
664
|
-
|
|
665
|
-
|
|
978
|
+
coprocessor: {
|
|
979
|
+
application: {
|
|
666
980
|
version: '8.9.10',
|
|
667
981
|
},
|
|
668
|
-
|
|
982
|
+
bt_adv_name: 'Raw Pro2 BLE',
|
|
669
983
|
},
|
|
670
984
|
status: {
|
|
985
|
+
device_id: 'PRO2-BASIC-ID',
|
|
986
|
+
unlocked_by_attach_to_pin: true,
|
|
671
987
|
init_states: true,
|
|
672
|
-
|
|
673
|
-
passphrase_protection: true,
|
|
988
|
+
passphrase_enabled: true,
|
|
674
989
|
},
|
|
675
990
|
},
|
|
676
991
|
});
|
|
@@ -687,9 +1002,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
687
1002
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
688
1003
|
features: {
|
|
689
1004
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
690
|
-
device_id: '
|
|
691
|
-
serial_no: '
|
|
692
|
-
label: '
|
|
1005
|
+
device_id: 'CACHED-ID',
|
|
1006
|
+
serial_no: 'CACHED-SERIAL',
|
|
1007
|
+
label: 'Cached Pro2',
|
|
693
1008
|
onekey_firmware_version: '1.2.3',
|
|
694
1009
|
onekey_ble_version: '2.3.4',
|
|
695
1010
|
},
|
|
@@ -700,13 +1015,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
700
1015
|
const result = await method.run();
|
|
701
1016
|
|
|
702
1017
|
expect(typedCall).toHaveBeenCalledWith(
|
|
703
|
-
'
|
|
1018
|
+
'DeviceInfoGet',
|
|
704
1019
|
'DeviceInfo',
|
|
705
1020
|
{
|
|
706
1021
|
targets: {
|
|
707
1022
|
hw: true,
|
|
708
1023
|
fw: true,
|
|
709
|
-
|
|
1024
|
+
coprocessor: true,
|
|
710
1025
|
status: true,
|
|
711
1026
|
},
|
|
712
1027
|
types: {
|
|
@@ -719,12 +1034,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
719
1034
|
expect(result).toMatchObject({
|
|
720
1035
|
protocol: 'V2',
|
|
721
1036
|
deviceType: 'pro2',
|
|
722
|
-
deviceId: '',
|
|
1037
|
+
deviceId: 'PRO2-BASIC-ID',
|
|
723
1038
|
serialNo: 'PR2SERIAL',
|
|
724
|
-
label:
|
|
1039
|
+
label: null,
|
|
725
1040
|
bleName: 'Raw Pro2 BLE',
|
|
726
1041
|
status: {
|
|
727
1042
|
initialized: true,
|
|
1043
|
+
unlockedAttachPin: true,
|
|
728
1044
|
passphraseProtection: true,
|
|
729
1045
|
},
|
|
730
1046
|
versions: {
|
|
@@ -734,7 +1050,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
734
1050
|
});
|
|
735
1051
|
});
|
|
736
1052
|
|
|
737
|
-
test('does not fill Protocol V2 DeviceProfile from
|
|
1053
|
+
test('does not fill Protocol V2 DeviceProfile from cached V1-shaped fields', async () => {
|
|
738
1054
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
739
1055
|
type: 'DeviceInfo',
|
|
740
1056
|
message: {
|
|
@@ -755,10 +1071,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
755
1071
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
756
1072
|
features: {
|
|
757
1073
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
758
|
-
device_id: '
|
|
759
|
-
serial_no: '
|
|
760
|
-
label: '
|
|
761
|
-
ble_name: '
|
|
1074
|
+
device_id: 'STALE-ID',
|
|
1075
|
+
serial_no: 'STALE-SERIAL',
|
|
1076
|
+
label: 'Stale Pro2',
|
|
1077
|
+
ble_name: 'Stale BLE',
|
|
762
1078
|
passphrase_protection: true,
|
|
763
1079
|
onekey_firmware_version: '1.2.3',
|
|
764
1080
|
onekey_ble_version: '2.3.4',
|
|
@@ -770,7 +1086,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
770
1086
|
|
|
771
1087
|
expect(result).toMatchObject({
|
|
772
1088
|
protocol: 'V2',
|
|
773
|
-
//
|
|
1089
|
+
// 身份字段不得取自缓存的 V1-shaped features(STALE-ID / STALE-SERIAL),
|
|
774
1090
|
// hw.serial_no 缺失时也不回退 descriptor.path。
|
|
775
1091
|
deviceId: '',
|
|
776
1092
|
serialNo: '',
|
|
@@ -813,13 +1129,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
813
1129
|
await method.run();
|
|
814
1130
|
|
|
815
1131
|
expect(typedCall).toHaveBeenCalledWith(
|
|
816
|
-
'
|
|
1132
|
+
'DeviceInfoGet',
|
|
817
1133
|
'DeviceInfo',
|
|
818
1134
|
{
|
|
819
1135
|
targets: {
|
|
820
1136
|
hw: true,
|
|
821
1137
|
fw: true,
|
|
822
|
-
|
|
1138
|
+
coprocessor: true,
|
|
823
1139
|
se1: true,
|
|
824
1140
|
se2: true,
|
|
825
1141
|
se3: true,
|
|
@@ -843,12 +1159,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
843
1159
|
message: {
|
|
844
1160
|
hw: { serial_no: 'PR2SERIAL' },
|
|
845
1161
|
se1: {
|
|
846
|
-
|
|
847
|
-
|
|
1162
|
+
application: { version: '1.0.1' },
|
|
1163
|
+
bootloader: { version: '1.0.0' },
|
|
848
1164
|
},
|
|
849
1165
|
se2: {
|
|
850
|
-
|
|
851
|
-
|
|
1166
|
+
application: { version: '2.0.1' },
|
|
1167
|
+
bootloader: { version: '2.0.0' },
|
|
852
1168
|
},
|
|
853
1169
|
status: { init_states: true },
|
|
854
1170
|
},
|
|
@@ -870,13 +1186,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
870
1186
|
const result = await method.run();
|
|
871
1187
|
|
|
872
1188
|
expect(typedCall).toHaveBeenCalledWith(
|
|
873
|
-
'
|
|
1189
|
+
'DeviceInfoGet',
|
|
874
1190
|
'DeviceInfo',
|
|
875
1191
|
{
|
|
876
1192
|
targets: {
|
|
877
1193
|
hw: true,
|
|
878
1194
|
fw: true,
|
|
879
|
-
|
|
1195
|
+
coprocessor: true,
|
|
880
1196
|
se1: true,
|
|
881
1197
|
se2: true,
|
|
882
1198
|
se3: true,
|
|
@@ -915,10 +1231,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
915
1231
|
expect(checkedTypes).toEqual(['pro2']);
|
|
916
1232
|
});
|
|
917
1233
|
|
|
1234
|
+
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
1235
|
+
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any);
|
|
1236
|
+
|
|
1237
|
+
expect(
|
|
1238
|
+
getFirmwareUpdateField({
|
|
1239
|
+
features,
|
|
1240
|
+
updateType: 'firmware',
|
|
1241
|
+
firmwareType: 'universal',
|
|
1242
|
+
})
|
|
1243
|
+
).toBe('firmware-v1');
|
|
1244
|
+
expect(getFirmwareUpdateFieldArray(features, 'firmware')).toEqual(['firmware-v1']);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
918
1247
|
test('marks known unsupported public-chain methods as unsupported on Protocol V2', () => {
|
|
919
1248
|
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
920
1249
|
fw: {
|
|
921
|
-
|
|
1250
|
+
application: {
|
|
922
1251
|
version: '9.9.9',
|
|
923
1252
|
},
|
|
924
1253
|
},
|
|
@@ -968,7 +1297,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
968
1297
|
expect(isMethodVersionRangeUnsupported(neuraiRange)).toBe(true);
|
|
969
1298
|
});
|
|
970
1299
|
|
|
971
|
-
test('does not block
|
|
1300
|
+
test('does not block batch public key support checks on Protocol V2', async () => {
|
|
972
1301
|
const paths = [{ address_n: [0x8000002c, 0x80000000, 0x80000000] }] as any;
|
|
973
1302
|
const typedCall = jest.fn().mockResolvedValue({
|
|
974
1303
|
type: 'EcdsaPublicKeys',
|
|
@@ -982,7 +1311,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
982
1311
|
originalDescriptor: { protocolType: 'V2' },
|
|
983
1312
|
features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
984
1313
|
fw: {
|
|
985
|
-
|
|
1314
|
+
application: {
|
|
986
1315
|
version: '4.14.0',
|
|
987
1316
|
},
|
|
988
1317
|
},
|
|
@@ -1004,7 +1333,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1004
1333
|
});
|
|
1005
1334
|
});
|
|
1006
1335
|
|
|
1007
|
-
test('does not call
|
|
1336
|
+
test('does not call V1 OnekeyGetFeatures for Protocol V2 devices', async () => {
|
|
1008
1337
|
const method = new GetOnekeyFeatures({
|
|
1009
1338
|
id: 1,
|
|
1010
1339
|
payload: {
|
|
@@ -1016,9 +1345,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1016
1345
|
message: {
|
|
1017
1346
|
protocol_version: 1,
|
|
1018
1347
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1019
|
-
fw: {
|
|
1020
|
-
|
|
1021
|
-
status: {
|
|
1348
|
+
fw: { application: { version: '1.2.3', build_id: 'app-build' } },
|
|
1349
|
+
coprocessor: { bt_adv_name: 'Pro2 BLE' },
|
|
1350
|
+
status: { init_states: true },
|
|
1022
1351
|
},
|
|
1023
1352
|
});
|
|
1024
1353
|
|
|
@@ -1029,10 +1358,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1029
1358
|
|
|
1030
1359
|
const message = await method.run();
|
|
1031
1360
|
|
|
1032
|
-
// V2 走
|
|
1361
|
+
// V2 走 DeviceInfoGet 完整请求(含 SE 与 hash/build_id),而不是 V1 OnekeyGetFeatures
|
|
1033
1362
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1034
1363
|
expect(typedCall).toHaveBeenCalledWith(
|
|
1035
|
-
'
|
|
1364
|
+
'DeviceInfoGet',
|
|
1036
1365
|
'DeviceInfo',
|
|
1037
1366
|
expect.objectContaining({
|
|
1038
1367
|
targets: expect.objectContaining({ se1: true, se2: true, se3: true, se4: true }),
|
|
@@ -1055,9 +1384,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1055
1384
|
type: 'DeviceInfo',
|
|
1056
1385
|
message: {
|
|
1057
1386
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1058
|
-
fw: {
|
|
1387
|
+
fw: { application: { version: '1.2.3' } },
|
|
1059
1388
|
status: {
|
|
1060
|
-
|
|
1389
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1390
|
+
passphrase_enabled: true,
|
|
1061
1391
|
},
|
|
1062
1392
|
},
|
|
1063
1393
|
})
|
|
@@ -1067,8 +1397,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1067
1397
|
message: {
|
|
1068
1398
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1069
1399
|
status: {
|
|
1070
|
-
|
|
1071
|
-
|
|
1400
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1401
|
+
passphrase_enabled: false,
|
|
1072
1402
|
},
|
|
1073
1403
|
},
|
|
1074
1404
|
});
|
|
@@ -1079,27 +1409,27 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1079
1409
|
await device.initialize();
|
|
1080
1410
|
|
|
1081
1411
|
expect(device.features).toMatchObject({
|
|
1082
|
-
deviceId:
|
|
1412
|
+
deviceId: 'PRO2-REFRESH-ID',
|
|
1083
1413
|
firmwareVersion: '1.2.3',
|
|
1084
1414
|
passphraseProtection: false,
|
|
1085
|
-
label:
|
|
1415
|
+
label: null,
|
|
1086
1416
|
});
|
|
1087
1417
|
expect((device as any).profile).toBeUndefined();
|
|
1088
1418
|
// status 字段被第二次刷新更新
|
|
1089
1419
|
expect(device.features?.passphraseProtection).toBe(false);
|
|
1090
|
-
expect(device.features?.label).
|
|
1420
|
+
expect(device.features?.label).toBeNull();
|
|
1091
1421
|
// 轻量刷新不含 fw target,已有版本信息按字段级合并保留
|
|
1092
1422
|
expect(device.features?.firmwareVersion).toBe('1.2.3');
|
|
1093
1423
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1094
1424
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1095
1425
|
1,
|
|
1096
|
-
'
|
|
1426
|
+
'DeviceInfoGet',
|
|
1097
1427
|
'DeviceInfo',
|
|
1098
1428
|
{
|
|
1099
1429
|
targets: {
|
|
1100
1430
|
hw: true,
|
|
1101
1431
|
fw: true,
|
|
1102
|
-
|
|
1432
|
+
coprocessor: true,
|
|
1103
1433
|
status: true,
|
|
1104
1434
|
},
|
|
1105
1435
|
types: {
|
|
@@ -1111,15 +1441,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1111
1441
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
1112
1442
|
}
|
|
1113
1443
|
);
|
|
1114
|
-
// 第二次为 status-only 轻量请求(hw/
|
|
1444
|
+
// 第二次为 status-only 轻量请求(hw/coprocessor 仅用于身份字段,避免顶层覆盖清空)
|
|
1115
1445
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1116
1446
|
2,
|
|
1117
|
-
'
|
|
1447
|
+
'DeviceInfoGet',
|
|
1118
1448
|
'DeviceInfo',
|
|
1119
1449
|
{
|
|
1120
1450
|
targets: {
|
|
1121
1451
|
hw: true,
|
|
1122
|
-
|
|
1452
|
+
coprocessor: true,
|
|
1123
1453
|
status: true,
|
|
1124
1454
|
},
|
|
1125
1455
|
types: {
|
|
@@ -1133,7 +1463,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1133
1463
|
);
|
|
1134
1464
|
});
|
|
1135
1465
|
|
|
1136
|
-
test('refreshes Protocol V2 features without falling back to
|
|
1466
|
+
test('refreshes Protocol V2 features without falling back to V1 GetFeatures', async () => {
|
|
1137
1467
|
const device = Device.fromDescriptor({
|
|
1138
1468
|
path: 'usb-path',
|
|
1139
1469
|
protocolType: 'V2',
|
|
@@ -1144,7 +1474,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1144
1474
|
type: 'DeviceInfo',
|
|
1145
1475
|
message: {
|
|
1146
1476
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1147
|
-
fw: {
|
|
1477
|
+
fw: { application: { version: '1.2.3' } },
|
|
1148
1478
|
status: { init_states: true },
|
|
1149
1479
|
},
|
|
1150
1480
|
})
|
|
@@ -1152,8 +1482,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1152
1482
|
type: 'DeviceInfo',
|
|
1153
1483
|
message: {
|
|
1154
1484
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1155
|
-
fw: {
|
|
1156
|
-
status: { init_states: true,
|
|
1485
|
+
fw: { application: { version: '1.2.4' } },
|
|
1486
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1157
1487
|
},
|
|
1158
1488
|
});
|
|
1159
1489
|
|
|
@@ -1176,13 +1506,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1176
1506
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1177
1507
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1178
1508
|
2,
|
|
1179
|
-
'
|
|
1509
|
+
'DeviceInfoGet',
|
|
1180
1510
|
'DeviceInfo',
|
|
1181
1511
|
{
|
|
1182
1512
|
targets: {
|
|
1183
1513
|
hw: true,
|
|
1184
1514
|
fw: true,
|
|
1185
|
-
|
|
1515
|
+
coprocessor: true,
|
|
1186
1516
|
status: true,
|
|
1187
1517
|
},
|
|
1188
1518
|
types: {
|
|
@@ -1195,14 +1525,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1195
1525
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1196
1526
|
});
|
|
1197
1527
|
|
|
1198
|
-
test('keeps Protocol V2 features available for
|
|
1528
|
+
test('keeps Protocol V2 features available for method internals such as evmSignTypedData', async () => {
|
|
1199
1529
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1200
1530
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
1201
1531
|
type: 'DeviceInfo',
|
|
1202
1532
|
message: {
|
|
1203
1533
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1204
|
-
fw: {
|
|
1205
|
-
status: { init_states: true,
|
|
1534
|
+
fw: { application: { version: '1.2.4' } },
|
|
1535
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1206
1536
|
},
|
|
1207
1537
|
});
|
|
1208
1538
|
(device as any).commands = { typedCall };
|
|
@@ -1239,64 +1569,155 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1239
1569
|
await expect(method.run()).resolves.toEqual({ address: '0x0', signature: '0x1' });
|
|
1240
1570
|
});
|
|
1241
1571
|
|
|
1242
|
-
test('unlocks Protocol V2 devices via
|
|
1572
|
+
test('unlocks Protocol V2 devices via DeviceSessionAskPin regardless of Pro-series version gates', async () => {
|
|
1243
1573
|
const device = Device.fromDescriptor({
|
|
1244
1574
|
path: 'usb-path',
|
|
1245
1575
|
protocolType: 'V2',
|
|
1246
1576
|
} as any);
|
|
1247
|
-
const typedCall = jest.fn().
|
|
1248
|
-
|
|
1249
|
-
|
|
1577
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1578
|
+
if (requestType === 'DeviceSessionAskPin') {
|
|
1579
|
+
return {
|
|
1580
|
+
type: 'DeviceSessionPinResult',
|
|
1581
|
+
message: { unlocked: false, passphrase_protection: false },
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1585
|
+
return {
|
|
1586
|
+
type: 'DeviceStatus',
|
|
1587
|
+
message: {
|
|
1588
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
1589
|
+
unlocked: true,
|
|
1590
|
+
passphrase_enabled: true,
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1250
1595
|
});
|
|
1251
1596
|
|
|
1252
1597
|
(device as any).commands = { typedCall };
|
|
1253
1598
|
// Pro2 版本线独立于 Pro(这里是 1.2.3,不满足 Pro 系列 4.15.0 门槛),
|
|
1254
|
-
// Protocol V2
|
|
1599
|
+
// Protocol V2 走独立的设备端 PIN 解锁流程,不走 Pro 系列版本门槛或 GetAddress 探测回退。
|
|
1255
1600
|
(device as any).features = normalizeProtocolV2Features(
|
|
1256
1601
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1257
1602
|
{
|
|
1258
1603
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1259
|
-
fw: {
|
|
1604
|
+
fw: { application: { version: '1.2.3' } },
|
|
1260
1605
|
status: { init_states: true },
|
|
1261
1606
|
}
|
|
1262
1607
|
);
|
|
1263
1608
|
|
|
1264
1609
|
const features = await device.unlockDevice();
|
|
1265
1610
|
|
|
1266
|
-
expect(typedCall).
|
|
1611
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1612
|
+
['DeviceSessionAskPin', 'DeviceSessionPinResult'],
|
|
1613
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1614
|
+
]);
|
|
1267
1615
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1268
1616
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1269
1617
|
expect(features).toMatchObject({
|
|
1270
1618
|
deviceType: 'pro2',
|
|
1271
|
-
deviceId:
|
|
1619
|
+
deviceId: 'PRO2-DEVICE-ID',
|
|
1272
1620
|
firmwareVersion: '1.2.3',
|
|
1273
1621
|
unlocked: true,
|
|
1622
|
+
passphraseProtection: true,
|
|
1274
1623
|
});
|
|
1275
1624
|
});
|
|
1276
1625
|
|
|
1277
|
-
test('syncs Protocol V2 features passphrase state after unlock
|
|
1626
|
+
test('syncs Protocol V2 features passphrase state from DeviceStatusGet after unlock', async () => {
|
|
1278
1627
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1279
1628
|
(device as any).features = normalizeProtocolV2Features(
|
|
1280
1629
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1281
1630
|
{
|
|
1282
1631
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1283
|
-
fw: {
|
|
1284
|
-
status: {
|
|
1632
|
+
fw: { application: { version: '4.15.0' } },
|
|
1633
|
+
status: { passphrase_enabled: false },
|
|
1634
|
+
}
|
|
1635
|
+
);
|
|
1636
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1637
|
+
if (requestType === 'DeviceSessionAskPin') {
|
|
1638
|
+
return {
|
|
1639
|
+
type: 'DeviceSessionPinResult',
|
|
1640
|
+
message: {
|
|
1641
|
+
unlocked: false,
|
|
1642
|
+
unlocked_attach_pin: false,
|
|
1643
|
+
passphrase_protection: false,
|
|
1644
|
+
},
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1648
|
+
return {
|
|
1649
|
+
type: 'DeviceStatus',
|
|
1650
|
+
message: {
|
|
1651
|
+
unlocked: true,
|
|
1652
|
+
unlocked_by_attach_to_pin: true,
|
|
1653
|
+
passphrase_enabled: true,
|
|
1654
|
+
},
|
|
1655
|
+
};
|
|
1285
1656
|
}
|
|
1657
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1658
|
+
});
|
|
1659
|
+
(device as any).commands = { typedCall };
|
|
1660
|
+
|
|
1661
|
+
await device.unlockDevice();
|
|
1662
|
+
|
|
1663
|
+
expect((device as any).profile).toBeUndefined();
|
|
1664
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
1665
|
+
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
test('maps unsupported Protocol V2 DeviceSessionAskPin to DeviceNotSupportMethod', async () => {
|
|
1669
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1670
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1671
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1672
|
+
{
|
|
1673
|
+
fw: { application: { version: '1.2.3' } },
|
|
1674
|
+
status: { unlocked: false },
|
|
1675
|
+
}
|
|
1676
|
+
);
|
|
1677
|
+
const typedCall = jest
|
|
1678
|
+
.fn()
|
|
1679
|
+
.mockRejectedValue(new Error('Failure_UnexpectedMessage,Unknown message'));
|
|
1680
|
+
(device as any).commands = { typedCall };
|
|
1681
|
+
|
|
1682
|
+
await expect(device.unlockDevice()).rejects.toEqual(
|
|
1683
|
+
expect.objectContaining({
|
|
1684
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1685
|
+
})
|
|
1686
|
+
);
|
|
1687
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1688
|
+
expect(typedCall.mock.calls).toEqual([['DeviceSessionAskPin', 'DeviceSessionPinResult']]);
|
|
1689
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
1690
|
+
'DeviceSessionGet',
|
|
1691
|
+
'DeviceSession',
|
|
1692
|
+
expect.anything()
|
|
1286
1693
|
);
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
test('keeps Protocol V1 unlock response handling unchanged', async () => {
|
|
1697
|
+
const device = Device.fromDescriptor({ path: 'v1-path', protocolType: 'V1' } as any);
|
|
1698
|
+
(device as any).features = {
|
|
1699
|
+
deviceType: 'pro',
|
|
1700
|
+
firmwareVersion: '4.15.0',
|
|
1701
|
+
capabilities: [],
|
|
1702
|
+
unlocked: false,
|
|
1703
|
+
passphraseProtection: false,
|
|
1704
|
+
} as Features;
|
|
1287
1705
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1288
1706
|
type: 'UnLockDeviceResponse',
|
|
1289
1707
|
message: {
|
|
1290
1708
|
unlocked: true,
|
|
1709
|
+
unlocked_attach_pin: true,
|
|
1291
1710
|
passphrase_protection: true,
|
|
1292
1711
|
},
|
|
1293
1712
|
});
|
|
1294
1713
|
(device as any).commands = { typedCall };
|
|
1295
1714
|
|
|
1296
|
-
await device.unlockDevice()
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1715
|
+
await expect(device.unlockDevice()).resolves.toMatchObject({
|
|
1716
|
+
unlocked: true,
|
|
1717
|
+
unlockedAttachPin: true,
|
|
1718
|
+
passphraseProtection: true,
|
|
1719
|
+
});
|
|
1720
|
+
expect(typedCall.mock.calls).toEqual([['UnLockDevice', 'UnLockDeviceResponse']]);
|
|
1300
1721
|
});
|
|
1301
1722
|
});
|
|
1302
1723
|
|
|
@@ -1333,25 +1754,7 @@ describe('API compatibility handling', () => {
|
|
|
1333
1754
|
);
|
|
1334
1755
|
});
|
|
1335
1756
|
|
|
1336
|
-
test('
|
|
1337
|
-
const method = new TronSignMessage({
|
|
1338
|
-
id: 1,
|
|
1339
|
-
payload: {
|
|
1340
|
-
method: 'tronSignMessage',
|
|
1341
|
-
path: "m/44'/195'/0'/0/0",
|
|
1342
|
-
messageHex: '0x1234',
|
|
1343
|
-
messageType: 'V1',
|
|
1344
|
-
},
|
|
1345
|
-
});
|
|
1346
|
-
|
|
1347
|
-
expect(() => method.init()).toThrow(
|
|
1348
|
-
expect.objectContaining({
|
|
1349
|
-
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1350
|
-
})
|
|
1351
|
-
);
|
|
1352
|
-
});
|
|
1353
|
-
|
|
1354
|
-
test('does not mark Pro2 Tron, Solana, TON, SUI and Polkadot methods as unsupported', () => {
|
|
1757
|
+
test('does not mark supported Pro2 Tron, Solana, TON, SUI and Polkadot methods as unsupported', () => {
|
|
1355
1758
|
const features = {
|
|
1356
1759
|
deviceType: 'pro2',
|
|
1357
1760
|
} as Features;
|
|
@@ -1472,9 +1875,7 @@ describe('API compatibility handling', () => {
|
|
|
1472
1875
|
});
|
|
1473
1876
|
expect(
|
|
1474
1877
|
getMethodVersionRange(features, type => tonSignDataMethod.getVersionRange()[type])
|
|
1475
|
-
).
|
|
1476
|
-
min: '0.0.0',
|
|
1477
|
-
});
|
|
1878
|
+
).toBeUndefined();
|
|
1478
1879
|
expect(
|
|
1479
1880
|
getMethodVersionRange(features, type => suiGetAddressMethod.getVersionRange()[type])
|
|
1480
1881
|
).toEqual({
|
|
@@ -1568,15 +1969,6 @@ describe('API compatibility handling', () => {
|
|
|
1568
1969
|
});
|
|
1569
1970
|
});
|
|
1570
1971
|
|
|
1571
|
-
test('includes TON signData in the Protocol V2 protobuf schema', () => {
|
|
1572
|
-
const protocolV2Messages = DataManager.getProtobufMessages('v2Schema') as any;
|
|
1573
|
-
|
|
1574
|
-
expect(protocolV2Messages.nested.TonSignData).toBeDefined();
|
|
1575
|
-
expect(protocolV2Messages.nested.TonSignedData).toBeDefined();
|
|
1576
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignData).toBe(11908);
|
|
1577
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignedData).toBe(11909);
|
|
1578
|
-
});
|
|
1579
|
-
|
|
1580
1972
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
1581
1973
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
1582
1974
|
const typedCall = jest.fn(() => ({
|
|
@@ -1621,54 +2013,6 @@ describe('API compatibility handling', () => {
|
|
|
1621
2013
|
expect(params.data_initial_chunk).toHaveLength(2048);
|
|
1622
2014
|
});
|
|
1623
2015
|
|
|
1624
|
-
test('accepts string XRP payment amount values', () => {
|
|
1625
|
-
const method = new XrpSignTransaction({
|
|
1626
|
-
id: 1,
|
|
1627
|
-
payload: {
|
|
1628
|
-
method: 'xrpSignTransaction',
|
|
1629
|
-
path: "m/44'/144'/0'/0/0",
|
|
1630
|
-
transaction: {
|
|
1631
|
-
fee: '100000',
|
|
1632
|
-
flags: 2147483648,
|
|
1633
|
-
sequence: 25,
|
|
1634
|
-
maxLedgerVersion: 8820051,
|
|
1635
|
-
payment: {
|
|
1636
|
-
amount: '100000000',
|
|
1637
|
-
destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws',
|
|
1638
|
-
},
|
|
1639
|
-
},
|
|
1640
|
-
},
|
|
1641
|
-
});
|
|
1642
|
-
|
|
1643
|
-
expect(() => method.init()).not.toThrow();
|
|
1644
|
-
expect(method.params.payment?.amount).toBe('100000000');
|
|
1645
|
-
});
|
|
1646
|
-
|
|
1647
|
-
test('accepts Conflux base32 recipient addresses without hex formatting them', () => {
|
|
1648
|
-
const to = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91';
|
|
1649
|
-
const method = new ConfluxSignTransaction({
|
|
1650
|
-
id: 1,
|
|
1651
|
-
payload: {
|
|
1652
|
-
method: 'confluxSignTransaction',
|
|
1653
|
-
path: "m/44'/503'/0'/0/0",
|
|
1654
|
-
transaction: {
|
|
1655
|
-
to,
|
|
1656
|
-
value: '0x0',
|
|
1657
|
-
data: '0x',
|
|
1658
|
-
chainId: 1,
|
|
1659
|
-
nonce: '0x00',
|
|
1660
|
-
epochHeight: '0x00',
|
|
1661
|
-
gasLimit: '0x5208',
|
|
1662
|
-
storageLimit: '0x5208',
|
|
1663
|
-
gasPrice: '0xbebc200',
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
});
|
|
1667
|
-
|
|
1668
|
-
expect(() => method.init()).not.toThrow();
|
|
1669
|
-
expect(method.formattedTx?.to).toBe(to);
|
|
1670
|
-
});
|
|
1671
|
-
|
|
1672
2016
|
test('returns a typed unsupported error for Dynex signing on Protocol V2', async () => {
|
|
1673
2017
|
const method = new DnxSignTransaction({
|
|
1674
2018
|
id: 1,
|
|
@@ -1724,7 +2068,41 @@ describe('API compatibility handling', () => {
|
|
|
1724
2068
|
});
|
|
1725
2069
|
|
|
1726
2070
|
describe('Protocol V2 firmware update targets', () => {
|
|
1727
|
-
|
|
2071
|
+
const OKPP_HEADER_SIZE = 0x52a0;
|
|
2072
|
+
|
|
2073
|
+
const buildOkppHeader = ({
|
|
2074
|
+
type = 'RESC',
|
|
2075
|
+
version = [1, 0, 0],
|
|
2076
|
+
payloadHash = 'ab'.repeat(64),
|
|
2077
|
+
headerHash = 'cd'.repeat(64),
|
|
2078
|
+
}: {
|
|
2079
|
+
type?: string;
|
|
2080
|
+
version?: [number, number, number];
|
|
2081
|
+
payloadHash?: string;
|
|
2082
|
+
headerHash?: string;
|
|
2083
|
+
} = {}) => {
|
|
2084
|
+
const header = new Uint8Array(OKPP_HEADER_SIZE);
|
|
2085
|
+
const view = new DataView(header.buffer);
|
|
2086
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
2087
|
+
header[index] = char.charCodeAt(0);
|
|
2088
|
+
});
|
|
2089
|
+
view.setUint32(0x04, 1, true);
|
|
2090
|
+
type.split('').forEach((char, index) => {
|
|
2091
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
2092
|
+
});
|
|
2093
|
+
view.setUint32(0x0c, OKPP_HEADER_SIZE, true);
|
|
2094
|
+
view.setUint32(0x10, version[0] * 0x10000 + version[1] * 0x100 + version[2], true);
|
|
2095
|
+
const writeHex = (offset: number, hex: string) => {
|
|
2096
|
+
for (let i = 0; i < hex.length / 2; i++) {
|
|
2097
|
+
header[offset + i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
writeHex(0x200, payloadHash);
|
|
2101
|
+
writeHex(0x240, headerHash);
|
|
2102
|
+
return header;
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
test('keeps Protocol V2 firmware updates off the firmwareUpdateV3 path', async () => {
|
|
1728
2106
|
const method = new FirmwareUpdateV3({
|
|
1729
2107
|
id: 1,
|
|
1730
2108
|
payload: {
|
|
@@ -1738,25 +2116,26 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1738
2116
|
await expect(method.run()).rejects.toThrow('firmwareUpdateV4');
|
|
1739
2117
|
});
|
|
1740
2118
|
|
|
1741
|
-
test('uses Protocol V2 features after BLE final reconnect without
|
|
2119
|
+
test('uses Protocol V2 features after BLE final reconnect without V1 Initialize', async () => {
|
|
1742
2120
|
const method = new FirmwareUpdateV4({
|
|
1743
2121
|
id: 1,
|
|
1744
2122
|
payload: {
|
|
1745
2123
|
method: 'firmwareUpdateV4',
|
|
2124
|
+
coprocessorBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
1746
2125
|
},
|
|
1747
2126
|
});
|
|
1748
2127
|
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-session' });
|
|
1749
2128
|
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
1750
|
-
if (name === '
|
|
2129
|
+
if (name === 'DeviceInfoGet') {
|
|
1751
2130
|
return Promise.resolve({
|
|
1752
2131
|
type: 'DeviceInfo',
|
|
1753
2132
|
message: {
|
|
1754
2133
|
fw: {
|
|
1755
|
-
|
|
1756
|
-
|
|
2134
|
+
bootloader: { version: '0.0.0' },
|
|
2135
|
+
application: { version: '0.0.0' },
|
|
1757
2136
|
},
|
|
1758
|
-
|
|
1759
|
-
|
|
2137
|
+
coprocessor: {
|
|
2138
|
+
application: { version: '0.0.0' },
|
|
1760
2139
|
},
|
|
1761
2140
|
},
|
|
1762
2141
|
});
|
|
@@ -1770,7 +2149,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1770
2149
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1771
2150
|
deviceConnector: { acquire },
|
|
1772
2151
|
getCommands: () => commands,
|
|
1773
|
-
|
|
2152
|
+
updateProtocolV2Features: jest.fn(() => ({
|
|
2153
|
+
bootloaderMode: false,
|
|
2154
|
+
mode: 'normal',
|
|
2155
|
+
firmwareVersion: '0.0.0',
|
|
2156
|
+
bootloaderVersion: '0.0.0',
|
|
2157
|
+
bleVersion: '0.0.0',
|
|
2158
|
+
})),
|
|
1774
2159
|
});
|
|
1775
2160
|
|
|
1776
2161
|
const versions = await (method as any).waitForProtocolV2FinalFeatures();
|
|
@@ -1779,13 +2164,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1779
2164
|
// 更新完成判定使用 VERSIONS 请求(含 SE targets),scope 与请求内容一致
|
|
1780
2165
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1781
2166
|
1,
|
|
1782
|
-
'
|
|
2167
|
+
'DeviceInfoGet',
|
|
1783
2168
|
'DeviceInfo',
|
|
1784
2169
|
{
|
|
1785
2170
|
targets: {
|
|
1786
2171
|
hw: true,
|
|
1787
2172
|
fw: true,
|
|
1788
|
-
|
|
2173
|
+
coprocessor: true,
|
|
1789
2174
|
se1: true,
|
|
1790
2175
|
se2: true,
|
|
1791
2176
|
se3: true,
|
|
@@ -1808,7 +2193,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1808
2193
|
});
|
|
1809
2194
|
});
|
|
1810
2195
|
|
|
1811
|
-
test('
|
|
2196
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
1812
2197
|
const method = new FirmwareUpdateV4({
|
|
1813
2198
|
id: 1,
|
|
1814
2199
|
payload: {
|
|
@@ -1821,14 +2206,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1821
2206
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1822
2207
|
features: { capabilities: [] },
|
|
1823
2208
|
});
|
|
1824
|
-
(method as any).
|
|
1825
|
-
(method as any).
|
|
2209
|
+
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
2210
|
+
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
1826
2211
|
{
|
|
1827
|
-
fileName: '
|
|
2212
|
+
fileName: 'coprocessor.bin',
|
|
1828
2213
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2214
|
+
targetId: 5,
|
|
1829
2215
|
},
|
|
1830
2216
|
]);
|
|
1831
|
-
(method as any).prepareBootloaderBinary = jest.fn().mockResolvedValue(null);
|
|
1832
2217
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
1833
2218
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
1834
2219
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
@@ -1842,17 +2227,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1842
2227
|
|
|
1843
2228
|
await method.run();
|
|
1844
2229
|
|
|
2230
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
1845
2231
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
1846
|
-
resourceBinary: null,
|
|
1847
2232
|
fwBinaryMap: [
|
|
1848
2233
|
{
|
|
1849
|
-
fileName: '
|
|
2234
|
+
fileName: 'coprocessor.bin',
|
|
1850
2235
|
binary: expect.any(ArrayBuffer),
|
|
2236
|
+
targetId: 5,
|
|
1851
2237
|
},
|
|
1852
2238
|
],
|
|
1853
2239
|
bootloaderBinary: null,
|
|
1854
2240
|
});
|
|
1855
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
1856
2241
|
});
|
|
1857
2242
|
|
|
1858
2243
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -1862,31 +2247,70 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1862
2247
|
method: 'firmwareUpdateV4',
|
|
1863
2248
|
},
|
|
1864
2249
|
});
|
|
1865
|
-
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
1866
2250
|
(method as any).device = stubDevice({
|
|
1867
2251
|
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
1868
2252
|
features: { bootloader_mode: false, capabilities: [] },
|
|
1869
2253
|
isBootloader: () => false,
|
|
1870
|
-
acquire,
|
|
1871
2254
|
});
|
|
1872
2255
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
1873
2256
|
message: 'Device rebooted successfully',
|
|
1874
2257
|
});
|
|
1875
|
-
(method as any).checkDeviceToBootloader = jest.fn(
|
|
1876
|
-
|
|
2258
|
+
(method as any).checkDeviceToBootloader = jest.fn();
|
|
2259
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
2260
|
+
type: 'DeviceInfo',
|
|
2261
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
2262
|
+
});
|
|
2263
|
+
const reconnectProtocolV2Device = jest.fn().mockImplementation(() => {
|
|
2264
|
+
(method as any).device.isBootloader = () => true;
|
|
2265
|
+
return Promise.resolve();
|
|
1877
2266
|
});
|
|
2267
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2268
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
1878
2269
|
method.postTipMessage = jest.fn();
|
|
1879
2270
|
|
|
1880
2271
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
1881
2272
|
|
|
1882
2273
|
expect(method.postTipMessage).toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
1883
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
2274
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Bootloader);
|
|
1884
2275
|
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
1885
|
-
expect((method as any).checkDeviceToBootloader).
|
|
1886
|
-
expect(
|
|
2276
|
+
expect((method as any).checkDeviceToBootloader).not.toHaveBeenCalled();
|
|
2277
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
test('polls until Protocol V2 bootloader descriptor is ready after reboot', async () => {
|
|
2281
|
+
const method = new FirmwareUpdateV4({
|
|
2282
|
+
id: 1,
|
|
2283
|
+
payload: {
|
|
2284
|
+
method: 'firmwareUpdateV4',
|
|
2285
|
+
},
|
|
2286
|
+
});
|
|
2287
|
+
(method as any).device = stubDevice({
|
|
2288
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
2289
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
2290
|
+
isBootloader: () => false,
|
|
2291
|
+
});
|
|
2292
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
2293
|
+
type: 'DeviceInfo',
|
|
2294
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
2295
|
+
});
|
|
2296
|
+
const reconnectProtocolV2Device = jest
|
|
2297
|
+
.fn()
|
|
2298
|
+
.mockRejectedValueOnce(new Error('Device not found'))
|
|
2299
|
+
.mockRejectedValueOnce(new Error('Device not found'))
|
|
2300
|
+
.mockImplementation(() => {
|
|
2301
|
+
(method as any).device.isBootloader = () => true;
|
|
2302
|
+
return Promise.resolve();
|
|
2303
|
+
});
|
|
2304
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2305
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
2306
|
+
|
|
2307
|
+
await (method as any).waitForProtocolV2BootloaderMode(60 * 1000, 0);
|
|
2308
|
+
|
|
2309
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(3);
|
|
2310
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1887
2311
|
});
|
|
1888
2312
|
|
|
1889
|
-
test('reboots Protocol V2 firmware flow back to normal
|
|
2313
|
+
test('reboots Protocol V2 firmware flow back to normal after install status is finished', async () => {
|
|
1890
2314
|
const method = new FirmwareUpdateV4({
|
|
1891
2315
|
id: 1,
|
|
1892
2316
|
payload: {
|
|
@@ -1901,7 +2325,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1901
2325
|
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
1902
2326
|
|
|
1903
2327
|
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
1904
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
2328
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
1905
2329
|
});
|
|
1906
2330
|
|
|
1907
2331
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
@@ -1921,7 +2345,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1921
2345
|
getCommands: () => ({ typedCall }),
|
|
1922
2346
|
});
|
|
1923
2347
|
|
|
1924
|
-
await expect((method as any).protocolV2Reboot(
|
|
2348
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
1925
2349
|
message: 'Device rebooted successfully',
|
|
1926
2350
|
});
|
|
1927
2351
|
});
|
|
@@ -1941,7 +2365,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1941
2365
|
getCommands: () => ({ typedCall }),
|
|
1942
2366
|
});
|
|
1943
2367
|
|
|
1944
|
-
await expect((method as any).protocolV2Reboot(
|
|
2368
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
1945
2369
|
message: 'Device rebooted successfully',
|
|
1946
2370
|
});
|
|
1947
2371
|
});
|
|
@@ -1969,7 +2393,30 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1969
2393
|
|
|
1970
2394
|
await expect(
|
|
1971
2395
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
1972
|
-
targets: [{ target_id:
|
|
2396
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2397
|
+
})
|
|
2398
|
+
).resolves.toBeUndefined();
|
|
2399
|
+
|
|
2400
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
test('continues Protocol V2 install polling when update request transfer times out', async () => {
|
|
2404
|
+
const method = new FirmwareUpdateV4({
|
|
2405
|
+
id: 1,
|
|
2406
|
+
payload: {
|
|
2407
|
+
method: 'firmwareUpdateV4',
|
|
2408
|
+
},
|
|
2409
|
+
});
|
|
2410
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
2411
|
+
|
|
2412
|
+
(method as any).device = stubDevice({
|
|
2413
|
+
getCommands: () => ({ typedCall }),
|
|
2414
|
+
});
|
|
2415
|
+
method.postTipMessage = jest.fn();
|
|
2416
|
+
|
|
2417
|
+
await expect(
|
|
2418
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
2419
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
1973
2420
|
})
|
|
1974
2421
|
).resolves.toBeUndefined();
|
|
1975
2422
|
|
|
@@ -1983,19 +2430,31 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1983
2430
|
method: 'firmwareUpdateV4',
|
|
1984
2431
|
},
|
|
1985
2432
|
});
|
|
1986
|
-
const typedCall = jest
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2433
|
+
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
2434
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
2435
|
+
const callCount = typedCall.mock.calls.filter(call => call[0] === name).length;
|
|
2436
|
+
if (callCount === 1) {
|
|
2437
|
+
return Promise.reject(
|
|
2438
|
+
new Error(
|
|
2439
|
+
'Device protocol mismatch: expected V2, but device did not respond to expected protocol'
|
|
2440
|
+
)
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
return Promise.resolve({
|
|
2444
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
2445
|
+
message: {
|
|
2446
|
+
records: [{ target_id: 6, status: 2 }],
|
|
2447
|
+
},
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
if (name === 'DeviceInfoGet') {
|
|
2451
|
+
return Promise.reject(new Error('DeviceInfo not ready'));
|
|
2452
|
+
}
|
|
2453
|
+
if (name === 'Ping') {
|
|
2454
|
+
return Promise.resolve({ type: 'Success', message: { message: 'ready' } });
|
|
2455
|
+
}
|
|
2456
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2457
|
+
});
|
|
1999
2458
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2000
2459
|
|
|
2001
2460
|
(method as any).device = stubDevice({
|
|
@@ -2005,14 +2464,75 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2005
2464
|
method.postProgressMessage = jest.fn();
|
|
2006
2465
|
|
|
2007
2466
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
2008
|
-
{ target_id:
|
|
2467
|
+
{ target_id: 6, path: 'vol1:ble-firmware.bin' },
|
|
2009
2468
|
]);
|
|
2010
2469
|
|
|
2011
2470
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2012
|
-
expect(typedCall).
|
|
2471
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
2472
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2473
|
+
'DeviceInfoGet',
|
|
2474
|
+
'Ping',
|
|
2475
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2476
|
+
]);
|
|
2477
|
+
});
|
|
2478
|
+
|
|
2479
|
+
test('treats Protocol V2 normal mode after reconnect as firmware update complete', async () => {
|
|
2480
|
+
const method = new FirmwareUpdateV4({
|
|
2481
|
+
id: 1,
|
|
2482
|
+
payload: {
|
|
2483
|
+
method: 'firmwareUpdateV4',
|
|
2484
|
+
},
|
|
2485
|
+
});
|
|
2486
|
+
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
2487
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
2488
|
+
return Promise.reject(new Error('Device disconnected'));
|
|
2489
|
+
}
|
|
2490
|
+
if (name === 'DeviceInfoGet') {
|
|
2491
|
+
return Promise.resolve({
|
|
2492
|
+
type: 'DeviceInfo',
|
|
2493
|
+
message: {
|
|
2494
|
+
hw: { serial_no: 'PR9999999999' },
|
|
2495
|
+
fw: {
|
|
2496
|
+
bootloader: { version: '9.9.9' },
|
|
2497
|
+
application: { version: '9.9.9' },
|
|
2498
|
+
},
|
|
2499
|
+
status: {
|
|
2500
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
2501
|
+
init_states: true,
|
|
2502
|
+
},
|
|
2503
|
+
},
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2507
|
+
});
|
|
2508
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2509
|
+
const updateProtocolV2Features = jest.fn((deviceInfo: ProtocolV2DeviceInfo) =>
|
|
2510
|
+
normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, deviceInfo)
|
|
2511
|
+
);
|
|
2512
|
+
|
|
2513
|
+
(method as any).device = stubDevice({
|
|
2514
|
+
getCommands: () => ({ typedCall }),
|
|
2515
|
+
updateProtocolV2Features,
|
|
2516
|
+
});
|
|
2517
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2518
|
+
method.postProgressMessage = jest.fn();
|
|
2519
|
+
|
|
2520
|
+
await expect(
|
|
2521
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
2522
|
+
{ target_id: 3, path: 'vol1:bootloader.bin' },
|
|
2523
|
+
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
2524
|
+
])
|
|
2525
|
+
).resolves.toBeUndefined();
|
|
2526
|
+
|
|
2527
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2528
|
+
expect(updateProtocolV2Features).toHaveBeenCalledTimes(1);
|
|
2529
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
2530
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2531
|
+
'DeviceInfoGet',
|
|
2532
|
+
]);
|
|
2013
2533
|
});
|
|
2014
2534
|
|
|
2015
|
-
test('uses
|
|
2535
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
2016
2536
|
const method = new FirmwareUpdateV4({
|
|
2017
2537
|
id: 1,
|
|
2018
2538
|
payload: {
|
|
@@ -2021,18 +2541,36 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2021
2541
|
});
|
|
2022
2542
|
method.postProgressMessage = jest.fn();
|
|
2023
2543
|
|
|
2024
|
-
const expectedTargetIds = new Set([
|
|
2544
|
+
const expectedTargetIds = new Set([4]);
|
|
2025
2545
|
|
|
2546
|
+
expect(
|
|
2547
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 2 }], expectedTargetIds)
|
|
2548
|
+
).toBe(true);
|
|
2026
2549
|
expect(
|
|
2027
2550
|
(method as any).assertProtocolV2TargetStatus(
|
|
2028
|
-
[{ target_id:
|
|
2551
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' }],
|
|
2029
2552
|
expectedTargetIds
|
|
2030
2553
|
)
|
|
2031
2554
|
).toBe(true);
|
|
2555
|
+
expect(
|
|
2556
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2557
|
+
[
|
|
2558
|
+
{
|
|
2559
|
+
target_id: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
2560
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
2561
|
+
},
|
|
2562
|
+
{ target_id: 'FW_MGMT_TARGET_SE04', status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
2563
|
+
],
|
|
2564
|
+
new Set([4, 10])
|
|
2565
|
+
)
|
|
2566
|
+
).toBe(true);
|
|
2032
2567
|
|
|
2568
|
+
expect(
|
|
2569
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 1 }], expectedTargetIds)
|
|
2570
|
+
).toBe(false);
|
|
2033
2571
|
expect(
|
|
2034
2572
|
(method as any).assertProtocolV2TargetStatus(
|
|
2035
|
-
[{ target_id:
|
|
2573
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS' }],
|
|
2036
2574
|
expectedTargetIds
|
|
2037
2575
|
)
|
|
2038
2576
|
).toBe(false);
|
|
@@ -2040,7 +2578,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2040
2578
|
|
|
2041
2579
|
try {
|
|
2042
2580
|
(method as any).assertProtocolV2TargetStatus(
|
|
2043
|
-
[{ target_id:
|
|
2581
|
+
[{ target_id: 4, status: 3 }],
|
|
2582
|
+
expectedTargetIds
|
|
2583
|
+
);
|
|
2584
|
+
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
2585
|
+
} catch (error: any) {
|
|
2586
|
+
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
try {
|
|
2590
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2591
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY' }],
|
|
2044
2592
|
expectedTargetIds
|
|
2045
2593
|
);
|
|
2046
2594
|
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
@@ -2049,7 +2597,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2049
2597
|
}
|
|
2050
2598
|
});
|
|
2051
2599
|
|
|
2052
|
-
test('passes
|
|
2600
|
+
test('passes bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
2053
2601
|
const method = new FirmwareUpdateV4({
|
|
2054
2602
|
id: 1,
|
|
2055
2603
|
payload: {
|
|
@@ -2064,71 +2612,65 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2064
2612
|
writtenPaths.push(params.filePath);
|
|
2065
2613
|
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
2066
2614
|
});
|
|
2615
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2067
2616
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2068
2617
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2069
2618
|
.fn()
|
|
2070
2619
|
.mockResolvedValue(undefined);
|
|
2071
2620
|
|
|
2072
2621
|
await (method as any).executeProtocolV2Update({
|
|
2073
|
-
// resource 只支持单文件 .bin,整文件一次上传
|
|
2074
|
-
resourceBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
2075
2622
|
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2076
2623
|
fwBinaryMap: [
|
|
2077
2624
|
{
|
|
2078
|
-
fileName: '
|
|
2625
|
+
fileName: 'coprocessor.bin',
|
|
2079
2626
|
binary: new Uint8Array([6]).buffer,
|
|
2627
|
+
targetId: 6,
|
|
2080
2628
|
},
|
|
2081
2629
|
{
|
|
2082
|
-
fileName: '
|
|
2630
|
+
fileName: 'se01.bin',
|
|
2083
2631
|
binary: new Uint8Array([7]).buffer,
|
|
2632
|
+
targetId: 7,
|
|
2084
2633
|
},
|
|
2085
2634
|
{
|
|
2086
|
-
fileName: '
|
|
2635
|
+
fileName: 'application_p1.bin',
|
|
2087
2636
|
binary: new Uint8Array([8]).buffer,
|
|
2637
|
+
targetId: 4,
|
|
2088
2638
|
},
|
|
2089
2639
|
],
|
|
2090
2640
|
});
|
|
2091
2641
|
|
|
2092
2642
|
expect(writtenPaths).toEqual([
|
|
2093
|
-
'
|
|
2094
|
-
'
|
|
2095
|
-
'
|
|
2096
|
-
'
|
|
2097
|
-
'vol1:firmware.bin',
|
|
2643
|
+
'vol0:/bootloader.bin',
|
|
2644
|
+
'vol0:/coprocessor.bin',
|
|
2645
|
+
'vol0:/se01.bin',
|
|
2646
|
+
'vol0:/application_p1.bin',
|
|
2098
2647
|
]);
|
|
2099
|
-
|
|
2648
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledTimes(4);
|
|
2649
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenNthCalledWith(
|
|
2650
|
+
2,
|
|
2651
|
+
'vol0:/coprocessor.bin',
|
|
2652
|
+
1
|
|
2653
|
+
);
|
|
2654
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
2100
2655
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2101
2656
|
targets: [
|
|
2102
|
-
{ target_id:
|
|
2103
|
-
{ target_id:
|
|
2104
|
-
{ target_id:
|
|
2105
|
-
{ target_id:
|
|
2106
|
-
{ target_id: 0, path: 'vol1:firmware.bin' },
|
|
2657
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
2658
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
2659
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
2660
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
2107
2661
|
],
|
|
2108
2662
|
});
|
|
2109
2663
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2110
2664
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2111
2665
|
});
|
|
2112
2666
|
|
|
2113
|
-
test('
|
|
2667
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
2114
2668
|
const method = new FirmwareUpdateV4({
|
|
2115
2669
|
id: 1,
|
|
2116
2670
|
payload: {
|
|
2117
2671
|
method: 'firmwareUpdateV4',
|
|
2118
|
-
platform: 'web',
|
|
2119
|
-
romloaderBinary: new Uint8Array([1]).buffer,
|
|
2120
|
-
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2121
|
-
se04Binary: new Uint8Array([3]).buffer,
|
|
2122
2672
|
},
|
|
2123
2673
|
});
|
|
2124
|
-
method.init();
|
|
2125
|
-
|
|
2126
|
-
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2127
|
-
expect(explicit).toEqual([
|
|
2128
|
-
{ fileName: 'romloader.bin', binary: expect.anything(), targetId: 1 },
|
|
2129
|
-
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 0 },
|
|
2130
|
-
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 6 },
|
|
2131
|
-
]);
|
|
2132
2674
|
|
|
2133
2675
|
method.postTipMessage = jest.fn();
|
|
2134
2676
|
method.postProgressMessage = jest.fn();
|
|
@@ -2137,41 +2679,542 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2137
2679
|
.mockImplementation(params =>
|
|
2138
2680
|
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2139
2681
|
);
|
|
2682
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2140
2683
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2141
2684
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2142
2685
|
.fn()
|
|
2143
2686
|
.mockResolvedValue(undefined);
|
|
2144
2687
|
|
|
2145
2688
|
await (method as any).executeProtocolV2Update({
|
|
2146
|
-
|
|
2689
|
+
resourceBundles: [
|
|
2690
|
+
{
|
|
2691
|
+
name: 'images.okpkg',
|
|
2692
|
+
binary: new Uint8Array([1, 2]).buffer,
|
|
2693
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
2694
|
+
},
|
|
2695
|
+
],
|
|
2147
2696
|
bootloaderBinary: null,
|
|
2148
|
-
fwBinaryMap:
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
{ target_id: 0, path: 'vol1:application_p2.bin' },
|
|
2155
|
-
{ target_id: 6, path: 'vol1:se04.bin' },
|
|
2697
|
+
fwBinaryMap: [
|
|
2698
|
+
{
|
|
2699
|
+
fileName: 'application_p1.bin',
|
|
2700
|
+
binary: new Uint8Array([3]).buffer,
|
|
2701
|
+
targetId: 3,
|
|
2702
|
+
},
|
|
2156
2703
|
],
|
|
2157
2704
|
});
|
|
2705
|
+
|
|
2706
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
2707
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
2708
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
2709
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2710
|
+
1,
|
|
2711
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
2712
|
+
);
|
|
2713
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2714
|
+
2,
|
|
2715
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
2716
|
+
);
|
|
2158
2717
|
});
|
|
2159
2718
|
|
|
2160
|
-
test('
|
|
2719
|
+
test('does not request installation when the staged file size does not match', async () => {
|
|
2161
2720
|
const method = new FirmwareUpdateV4({
|
|
2162
2721
|
id: 1,
|
|
2163
2722
|
payload: {
|
|
2164
2723
|
method: 'firmwareUpdateV4',
|
|
2165
2724
|
},
|
|
2166
2725
|
});
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
) =>
|
|
2173
|
-
|
|
2174
|
-
type: '
|
|
2726
|
+
|
|
2727
|
+
method.postTipMessage = jest.fn();
|
|
2728
|
+
method.postProgressMessage = jest.fn();
|
|
2729
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
2730
|
+
(method as any).device = stubDevice({
|
|
2731
|
+
getCommands: () => ({
|
|
2732
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
2733
|
+
type: 'FilesystemPathInfo',
|
|
2734
|
+
message: { exist: true, directory: false, size: 2 },
|
|
2735
|
+
}),
|
|
2736
|
+
}),
|
|
2737
|
+
});
|
|
2738
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
2739
|
+
|
|
2740
|
+
await expect(
|
|
2741
|
+
(method as any).executeProtocolV2Update({
|
|
2742
|
+
bootloaderBinary: null,
|
|
2743
|
+
fwBinaryMap: [
|
|
2744
|
+
{
|
|
2745
|
+
fileName: 'coprocessor.bin',
|
|
2746
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2747
|
+
targetId: 5,
|
|
2748
|
+
},
|
|
2749
|
+
],
|
|
2750
|
+
})
|
|
2751
|
+
).rejects.toThrow(/staged file verification failed/);
|
|
2752
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
test('passes explicit per-target binaries through without file name heuristics', async () => {
|
|
2756
|
+
const method = new FirmwareUpdateV4({
|
|
2757
|
+
id: 1,
|
|
2758
|
+
payload: {
|
|
2759
|
+
method: 'firmwareUpdateV4',
|
|
2760
|
+
platform: 'web',
|
|
2761
|
+
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2762
|
+
se04Binary: new Uint8Array([3]).buffer,
|
|
2763
|
+
},
|
|
2764
|
+
});
|
|
2765
|
+
method.init();
|
|
2766
|
+
|
|
2767
|
+
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2768
|
+
expect(explicit).toEqual([
|
|
2769
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2770
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2771
|
+
]);
|
|
2772
|
+
|
|
2773
|
+
method.postTipMessage = jest.fn();
|
|
2774
|
+
method.postProgressMessage = jest.fn();
|
|
2775
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2776
|
+
.fn()
|
|
2777
|
+
.mockImplementation(params =>
|
|
2778
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2779
|
+
);
|
|
2780
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2781
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2782
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2783
|
+
.fn()
|
|
2784
|
+
.mockResolvedValue(undefined);
|
|
2785
|
+
|
|
2786
|
+
await (method as any).executeProtocolV2Update({
|
|
2787
|
+
bootloaderBinary: null,
|
|
2788
|
+
fwBinaryMap: explicit,
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2791
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2792
|
+
targets: [
|
|
2793
|
+
{ target_id: 5, path: 'vol0:/application_p2.bin' },
|
|
2794
|
+
{ target_id: 10, path: 'vol0:/se04.bin' },
|
|
2795
|
+
],
|
|
2796
|
+
});
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
test('rejects romloaderBinary before sending a Protocol V2 update request', () => {
|
|
2800
|
+
const method = new FirmwareUpdateV4({
|
|
2801
|
+
id: 1,
|
|
2802
|
+
payload: {
|
|
2803
|
+
method: 'firmwareUpdateV4',
|
|
2804
|
+
platform: 'web',
|
|
2805
|
+
romloaderBinary: new Uint8Array([1]).buffer,
|
|
2806
|
+
},
|
|
2807
|
+
});
|
|
2808
|
+
method.init();
|
|
2809
|
+
|
|
2810
|
+
expect(() => (method as any).collectExplicitTargetBinaries()).toThrow(
|
|
2811
|
+
'FW_MGMT_TARGET_ROMLOADER is not accepted'
|
|
2812
|
+
);
|
|
2813
|
+
});
|
|
2814
|
+
|
|
2815
|
+
test('maps Pro2 remote firmware-v1 components to Protocol V2 targets in installOrder', async () => {
|
|
2816
|
+
const method = new FirmwareUpdateV4({
|
|
2817
|
+
id: 1,
|
|
2818
|
+
payload: {
|
|
2819
|
+
method: 'firmwareUpdateV4',
|
|
2820
|
+
platform: 'web',
|
|
2821
|
+
},
|
|
2822
|
+
});
|
|
2823
|
+
method.init();
|
|
2824
|
+
|
|
2825
|
+
const binaries = new Map(
|
|
2826
|
+
[
|
|
2827
|
+
'bootloader',
|
|
2828
|
+
'applicationP1',
|
|
2829
|
+
'applicationP2',
|
|
2830
|
+
'coprocessor',
|
|
2831
|
+
'se01',
|
|
2832
|
+
'se02',
|
|
2833
|
+
'se03',
|
|
2834
|
+
'se04',
|
|
2835
|
+
].map((key, index) => [
|
|
2836
|
+
`https://example.com/${key}.pp.bin`,
|
|
2837
|
+
new Uint8Array([index + 1]).buffer,
|
|
2838
|
+
])
|
|
2839
|
+
);
|
|
2840
|
+
const getSysResourceBinarySpy = jest
|
|
2841
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2842
|
+
.mockImplementation(url =>
|
|
2843
|
+
Promise.resolve({
|
|
2844
|
+
binary: binaries.get(url) ?? new Uint8Array([0]).buffer,
|
|
2845
|
+
})
|
|
2846
|
+
);
|
|
2847
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2848
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2849
|
+
.mockReturnValue({
|
|
2850
|
+
required: false,
|
|
2851
|
+
version: [1, 0, 0],
|
|
2852
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2853
|
+
bootloaderResource: 'https://example.com/bootloader.pp.bin',
|
|
2854
|
+
bootloaderVersion: [1, 0, 0],
|
|
2855
|
+
displayBootloaderVersion: [1, 0, 0],
|
|
2856
|
+
upgradeType: 'payload-package-set',
|
|
2857
|
+
installOrder: [
|
|
2858
|
+
'bootloader',
|
|
2859
|
+
'applicationP1',
|
|
2860
|
+
'applicationP2',
|
|
2861
|
+
'coprocessor',
|
|
2862
|
+
'se01',
|
|
2863
|
+
'se02',
|
|
2864
|
+
'se03',
|
|
2865
|
+
'se04',
|
|
2866
|
+
],
|
|
2867
|
+
components: {
|
|
2868
|
+
bootloader: {
|
|
2869
|
+
target: 'BOOTLOADER',
|
|
2870
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
2871
|
+
},
|
|
2872
|
+
applicationP1: {
|
|
2873
|
+
target: 'APPLICATION_P1',
|
|
2874
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
2875
|
+
},
|
|
2876
|
+
applicationP2: {
|
|
2877
|
+
target: 'APPLICATION_P2',
|
|
2878
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
2879
|
+
},
|
|
2880
|
+
coprocessor: {
|
|
2881
|
+
target: 'COPROCESSOR',
|
|
2882
|
+
url: 'https://example.com/coprocessor.pp.bin',
|
|
2883
|
+
},
|
|
2884
|
+
se01: { target: 'SE01', url: 'https://example.com/se01.pp.bin' },
|
|
2885
|
+
se02: { target: 'SE02', url: 'https://example.com/se02.pp.bin' },
|
|
2886
|
+
se03: { target: 'SE03', url: 'https://example.com/se03.pp.bin' },
|
|
2887
|
+
se04: { target: 'SE04', url: 'https://example.com/se04.pp.bin' },
|
|
2888
|
+
},
|
|
2889
|
+
fingerprint: '',
|
|
2890
|
+
changelog: {
|
|
2891
|
+
'zh-CN': '',
|
|
2892
|
+
'en-US': '',
|
|
2893
|
+
},
|
|
2894
|
+
});
|
|
2895
|
+
|
|
2896
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2897
|
+
deviceType: 'pro2',
|
|
2898
|
+
firmwareVersion: '0.0.0',
|
|
2899
|
+
});
|
|
2900
|
+
|
|
2901
|
+
expect(remoteBinaries.bootloaderBinary).toBe(
|
|
2902
|
+
binaries.get('https://example.com/bootloader.pp.bin')
|
|
2903
|
+
);
|
|
2904
|
+
expect(remoteBinaries.fwBinaryMap).toEqual([
|
|
2905
|
+
{ fileName: 'application_p1.bin', binary: expect.anything(), targetId: 4 },
|
|
2906
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2907
|
+
{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 },
|
|
2908
|
+
{ fileName: 'se01.bin', binary: expect.anything(), targetId: 7 },
|
|
2909
|
+
{ fileName: 'se02.bin', binary: expect.anything(), targetId: 8 },
|
|
2910
|
+
{ fileName: 'se03.bin', binary: expect.anything(), targetId: 9 },
|
|
2911
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2912
|
+
]);
|
|
2913
|
+
expect(getSysResourceBinarySpy.mock.calls.map(call => call[0])).toEqual([
|
|
2914
|
+
'https://example.com/bootloader.pp.bin',
|
|
2915
|
+
'https://example.com/applicationP1.pp.bin',
|
|
2916
|
+
'https://example.com/applicationP2.pp.bin',
|
|
2917
|
+
'https://example.com/coprocessor.pp.bin',
|
|
2918
|
+
'https://example.com/se01.pp.bin',
|
|
2919
|
+
'https://example.com/se02.pp.bin',
|
|
2920
|
+
'https://example.com/se03.pp.bin',
|
|
2921
|
+
'https://example.com/se04.pp.bin',
|
|
2922
|
+
]);
|
|
2923
|
+
|
|
2924
|
+
getSysResourceBinarySpy.mockRestore();
|
|
2925
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
2929
|
+
const method = new FirmwareUpdateV4({
|
|
2930
|
+
id: 1,
|
|
2931
|
+
payload: {
|
|
2932
|
+
method: 'firmwareUpdateV4',
|
|
2933
|
+
platform: 'web',
|
|
2934
|
+
},
|
|
2935
|
+
});
|
|
2936
|
+
method.init();
|
|
2937
|
+
|
|
2938
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2939
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2940
|
+
.mockReturnValue({
|
|
2941
|
+
required: false,
|
|
2942
|
+
version: [1, 0, 0],
|
|
2943
|
+
url: '',
|
|
2944
|
+
resourceBundles: [
|
|
2945
|
+
{
|
|
2946
|
+
name: 'images',
|
|
2947
|
+
url: 'https://example.com/images.okpkg',
|
|
2948
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2949
|
+
version: [2, 0, 0],
|
|
2950
|
+
payloadHash: 'ab'.repeat(64),
|
|
2951
|
+
headerHash: 'cd'.repeat(64),
|
|
2952
|
+
},
|
|
2953
|
+
],
|
|
2954
|
+
fingerprint: '',
|
|
2955
|
+
changelog: {
|
|
2956
|
+
'zh-CN': '',
|
|
2957
|
+
'en-US': '',
|
|
2958
|
+
},
|
|
2959
|
+
});
|
|
2960
|
+
|
|
2961
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
2962
|
+
deviceType: 'pro2',
|
|
2963
|
+
firmwareVersion: '1.0.0',
|
|
2964
|
+
capabilities: [],
|
|
2965
|
+
});
|
|
2966
|
+
|
|
2967
|
+
expect(bundles).toEqual([
|
|
2968
|
+
{
|
|
2969
|
+
name: 'images',
|
|
2970
|
+
binary: expect.any(ArrayBuffer),
|
|
2971
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2972
|
+
url: 'https://example.com/images.okpkg',
|
|
2973
|
+
version: [2, 0, 0],
|
|
2974
|
+
payloadHash: 'ab'.repeat(64),
|
|
2975
|
+
headerHash: 'cd'.repeat(64),
|
|
2976
|
+
},
|
|
2977
|
+
]);
|
|
2978
|
+
expect(bundles[0].binary.byteLength).toBe(0);
|
|
2979
|
+
|
|
2980
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2981
|
+
});
|
|
2982
|
+
|
|
2983
|
+
test('skips Pro2 firmware components when configured versions are already installed', async () => {
|
|
2984
|
+
const method = new FirmwareUpdateV4({
|
|
2985
|
+
id: 1,
|
|
2986
|
+
payload: {
|
|
2987
|
+
method: 'firmwareUpdateV4',
|
|
2988
|
+
platform: 'web',
|
|
2989
|
+
},
|
|
2990
|
+
});
|
|
2991
|
+
method.init();
|
|
2992
|
+
|
|
2993
|
+
const getSysResourceBinarySpy = jest
|
|
2994
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2995
|
+
.mockResolvedValue({ binary: new Uint8Array([1]).buffer });
|
|
2996
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
2997
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2998
|
+
.mockReturnValue({
|
|
2999
|
+
required: false,
|
|
3000
|
+
version: [1, 0, 0],
|
|
3001
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
3002
|
+
bootloaderVersion: [1, 0, 0],
|
|
3003
|
+
upgradeType: 'payload-package-set',
|
|
3004
|
+
installOrder: ['bootloader', 'applicationP1', 'applicationP2'],
|
|
3005
|
+
components: {
|
|
3006
|
+
bootloader: {
|
|
3007
|
+
target: 'BOOTLOADER',
|
|
3008
|
+
url: 'https://example.com/bootloader.pp.bin',
|
|
3009
|
+
},
|
|
3010
|
+
applicationP1: {
|
|
3011
|
+
target: 'APPLICATION_P1',
|
|
3012
|
+
url: 'https://example.com/applicationP1.pp.bin',
|
|
3013
|
+
},
|
|
3014
|
+
applicationP2: {
|
|
3015
|
+
target: 'APPLICATION_P2',
|
|
3016
|
+
url: 'https://example.com/applicationP2.pp.bin',
|
|
3017
|
+
},
|
|
3018
|
+
},
|
|
3019
|
+
fingerprint: '',
|
|
3020
|
+
changelog: {
|
|
3021
|
+
'zh-CN': '',
|
|
3022
|
+
'en-US': '',
|
|
3023
|
+
},
|
|
3024
|
+
});
|
|
3025
|
+
|
|
3026
|
+
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
3027
|
+
deviceType: 'pro2',
|
|
3028
|
+
firmwareVersion: '1.0.0',
|
|
3029
|
+
bootloaderVersion: '1.0.0',
|
|
3030
|
+
capabilities: [],
|
|
3031
|
+
});
|
|
3032
|
+
|
|
3033
|
+
expect(remoteBinaries).toEqual({
|
|
3034
|
+
bootloaderBinary: null,
|
|
3035
|
+
fwBinaryMap: [],
|
|
3036
|
+
installItems: [],
|
|
3037
|
+
});
|
|
3038
|
+
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
3039
|
+
|
|
3040
|
+
getSysResourceBinarySpy.mockRestore();
|
|
3041
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3044
|
+
test('uses Pro2 remote components when firmwareUpdateV4 has no explicit binaries', async () => {
|
|
3045
|
+
const method = new FirmwareUpdateV4({
|
|
3046
|
+
id: 1,
|
|
3047
|
+
payload: {
|
|
3048
|
+
method: 'firmwareUpdateV4',
|
|
3049
|
+
platform: 'web',
|
|
3050
|
+
},
|
|
3051
|
+
});
|
|
3052
|
+
method.init();
|
|
3053
|
+
|
|
3054
|
+
const remoteBinaries = {
|
|
3055
|
+
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
3056
|
+
fwBinaryMap: [
|
|
3057
|
+
{
|
|
3058
|
+
fileName: 'application_p1.bin',
|
|
3059
|
+
binary: new Uint8Array([2]).buffer,
|
|
3060
|
+
targetId: 3,
|
|
3061
|
+
},
|
|
3062
|
+
],
|
|
3063
|
+
};
|
|
3064
|
+
|
|
3065
|
+
(method as any).device = stubDevice({
|
|
3066
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3067
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3068
|
+
});
|
|
3069
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
3070
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3071
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3072
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3073
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3074
|
+
bootloaderVersion: '1.0.0',
|
|
3075
|
+
bleVersion: '0.0.0',
|
|
3076
|
+
firmwareVersion: '1.0.0',
|
|
3077
|
+
});
|
|
3078
|
+
method.postTipMessage = jest.fn();
|
|
3079
|
+
|
|
3080
|
+
await method.run();
|
|
3081
|
+
|
|
3082
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
3083
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3084
|
+
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
3085
|
+
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
3086
|
+
});
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
3090
|
+
const method = new FirmwareUpdateV4({
|
|
3091
|
+
id: 1,
|
|
3092
|
+
payload: {
|
|
3093
|
+
method: 'firmwareUpdateV4',
|
|
3094
|
+
platform: 'web',
|
|
3095
|
+
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
3096
|
+
},
|
|
3097
|
+
});
|
|
3098
|
+
method.init();
|
|
3099
|
+
|
|
3100
|
+
(method as any).device = stubDevice({
|
|
3101
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3102
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3103
|
+
});
|
|
3104
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3105
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3106
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3107
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3108
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3109
|
+
bootloaderVersion: '1.0.0',
|
|
3110
|
+
bleVersion: '0.0.0',
|
|
3111
|
+
firmwareVersion: '1.0.0',
|
|
3112
|
+
});
|
|
3113
|
+
method.postTipMessage = jest.fn();
|
|
3114
|
+
|
|
3115
|
+
await method.run();
|
|
3116
|
+
|
|
3117
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3118
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3119
|
+
bootloaderBinary: null,
|
|
3120
|
+
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
3121
|
+
});
|
|
3122
|
+
});
|
|
3123
|
+
|
|
3124
|
+
test('treats manual resource bundles as explicit payload without remote firmware auto-fill', async () => {
|
|
3125
|
+
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
3126
|
+
const method = new FirmwareUpdateV4({
|
|
3127
|
+
id: 1,
|
|
3128
|
+
payload: {
|
|
3129
|
+
method: 'firmwareUpdateV4',
|
|
3130
|
+
platform: 'web',
|
|
3131
|
+
resourceBundleFiles: [
|
|
3132
|
+
{
|
|
3133
|
+
binary: resourceBundle,
|
|
3134
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3135
|
+
},
|
|
3136
|
+
],
|
|
3137
|
+
},
|
|
3138
|
+
});
|
|
3139
|
+
method.init();
|
|
3140
|
+
|
|
3141
|
+
(method as any).device = stubDevice({
|
|
3142
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3143
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3144
|
+
});
|
|
3145
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3146
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3147
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3148
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3149
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3150
|
+
bootloaderVersion: '1.0.0',
|
|
3151
|
+
bleVersion: '0.0.0',
|
|
3152
|
+
firmwareVersion: '1.0.0',
|
|
3153
|
+
});
|
|
3154
|
+
method.postTipMessage = jest.fn();
|
|
3155
|
+
|
|
3156
|
+
await method.run();
|
|
3157
|
+
|
|
3158
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3159
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith(
|
|
3160
|
+
expect.objectContaining({
|
|
3161
|
+
resourceBundles: [
|
|
3162
|
+
{
|
|
3163
|
+
name: 'images.okpkg',
|
|
3164
|
+
binary: resourceBundle,
|
|
3165
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3166
|
+
},
|
|
3167
|
+
],
|
|
3168
|
+
})
|
|
3169
|
+
);
|
|
3170
|
+
});
|
|
3171
|
+
|
|
3172
|
+
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
3173
|
+
const method = new FirmwareUpdateV4({
|
|
3174
|
+
id: 1,
|
|
3175
|
+
payload: {
|
|
3176
|
+
method: 'firmwareUpdateV4',
|
|
3177
|
+
},
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
method.postTipMessage = jest.fn();
|
|
3181
|
+
method.postProgressMessage = jest.fn();
|
|
3182
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3183
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
3184
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
3185
|
+
|
|
3186
|
+
await (method as any).executeProtocolV2Update({
|
|
3187
|
+
resourceBundles: [
|
|
3188
|
+
{
|
|
3189
|
+
name: 'images.okpkg',
|
|
3190
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
3191
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3192
|
+
},
|
|
3193
|
+
],
|
|
3194
|
+
bootloaderBinary: null,
|
|
3195
|
+
fwBinaryMap: [],
|
|
3196
|
+
});
|
|
3197
|
+
|
|
3198
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
3199
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
3200
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
3201
|
+
});
|
|
3202
|
+
|
|
3203
|
+
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
3204
|
+
const method = new FirmwareUpdateV4({
|
|
3205
|
+
id: 1,
|
|
3206
|
+
payload: {
|
|
3207
|
+
method: 'firmwareUpdateV4',
|
|
3208
|
+
},
|
|
3209
|
+
});
|
|
3210
|
+
const typedCall = jest.fn(
|
|
3211
|
+
(
|
|
3212
|
+
_name: string,
|
|
3213
|
+
_resType: string,
|
|
3214
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
3215
|
+
) =>
|
|
3216
|
+
Promise.resolve({
|
|
3217
|
+
type: 'FilesystemFile',
|
|
2175
3218
|
message: {
|
|
2176
3219
|
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
2177
3220
|
},
|
|
@@ -2191,10 +3234,65 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2191
3234
|
});
|
|
2192
3235
|
|
|
2193
3236
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2194
|
-
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0,
|
|
2195
|
-
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([
|
|
3237
|
+
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
3238
|
+
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([4000, 97]);
|
|
2196
3239
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
2197
3240
|
expect(writePayloads.every(payload => payload.append === false)).toBe(true);
|
|
3241
|
+
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
3242
|
+
});
|
|
3243
|
+
|
|
3244
|
+
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
3245
|
+
const method = new FirmwareUpdateV4({
|
|
3246
|
+
id: 1,
|
|
3247
|
+
payload: {
|
|
3248
|
+
method: 'firmwareUpdateV4',
|
|
3249
|
+
},
|
|
3250
|
+
});
|
|
3251
|
+
const writeOffsets: number[] = [];
|
|
3252
|
+
let failedSecondChunk = false;
|
|
3253
|
+
const typedCall = jest.fn(
|
|
3254
|
+
(
|
|
3255
|
+
_name: string,
|
|
3256
|
+
_resType: string,
|
|
3257
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
3258
|
+
) => {
|
|
3259
|
+
writeOffsets.push(params.file.offset);
|
|
3260
|
+
if (params.file.offset === 4000 && !failedSecondChunk) {
|
|
3261
|
+
failedSecondChunk = true;
|
|
3262
|
+
return Promise.reject(new Error('response lost after device write'));
|
|
3263
|
+
}
|
|
3264
|
+
return Promise.resolve({
|
|
3265
|
+
type: 'FilesystemFile',
|
|
3266
|
+
message: {
|
|
3267
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
3268
|
+
},
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
);
|
|
3272
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
3273
|
+
callback: (...args: any[]) => void
|
|
3274
|
+
) => {
|
|
3275
|
+
callback();
|
|
3276
|
+
return 0 as any;
|
|
3277
|
+
}) as typeof setTimeout);
|
|
3278
|
+
|
|
3279
|
+
(method as any).device = stubDevice({
|
|
3280
|
+
getCommands: () => ({ typedCall }),
|
|
3281
|
+
});
|
|
3282
|
+
method.postProgressMessage = jest.fn();
|
|
3283
|
+
|
|
3284
|
+
try {
|
|
3285
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
3286
|
+
payload: new Uint8Array(8001).buffer,
|
|
3287
|
+
filePath: 'vol0:/firmware.bin',
|
|
3288
|
+
processedSize: 0,
|
|
3289
|
+
totalSize: 8001,
|
|
3290
|
+
});
|
|
3291
|
+
} finally {
|
|
3292
|
+
setTimeoutSpy.mockRestore();
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
expect(writeOffsets).toEqual([0, 4000, 0, 4000, 8000]);
|
|
2198
3296
|
});
|
|
2199
3297
|
|
|
2200
3298
|
test('continues to DeviceFirmwareUpdate when FilesystemFileWrite returns processed chunk length', async () => {
|
|
@@ -2213,9 +3311,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2213
3311
|
},
|
|
2214
3312
|
});
|
|
2215
3313
|
}
|
|
2216
|
-
if (name === '
|
|
3314
|
+
if (name === 'FilesystemPathInfoQuery') {
|
|
3315
|
+
return Promise.resolve({
|
|
3316
|
+
type: 'FilesystemPathInfo',
|
|
3317
|
+
message: { exist: true, directory: false, size: 4097 },
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
if (name === 'DeviceFirmwareUpdateRequest') {
|
|
2217
3321
|
return Promise.resolve({ type: 'Success', message: { message: 'ok' } });
|
|
2218
3322
|
}
|
|
3323
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
3324
|
+
return Promise.resolve({
|
|
3325
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3326
|
+
message: {
|
|
3327
|
+
records: [{ target_id: 4, status: 2 }],
|
|
3328
|
+
},
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
2219
3331
|
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2220
3332
|
});
|
|
2221
3333
|
|
|
@@ -2226,23 +3338,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2226
3338
|
method.postTipMessage = jest.fn();
|
|
2227
3339
|
|
|
2228
3340
|
await (method as any).executeProtocolV2Update({
|
|
2229
|
-
resourceBinary: null,
|
|
2230
3341
|
bootloaderBinary: null,
|
|
2231
3342
|
fwBinaryMap: [
|
|
2232
3343
|
{
|
|
2233
3344
|
fileName: 'firmware.bin',
|
|
2234
3345
|
binary: new Uint8Array(4097).buffer,
|
|
3346
|
+
targetId: 4,
|
|
2235
3347
|
},
|
|
2236
3348
|
],
|
|
2237
3349
|
});
|
|
2238
3350
|
|
|
2239
3351
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2240
|
-
'
|
|
2241
|
-
['Success', '
|
|
3352
|
+
'DeviceFirmwareUpdateRequest',
|
|
3353
|
+
['Success', 'DeviceFirmwareUpdateStatus'],
|
|
2242
3354
|
{
|
|
2243
|
-
targets: [{ target_id:
|
|
3355
|
+
targets: [{ target_id: 4, path: 'vol0:/firmware.bin' }],
|
|
2244
3356
|
},
|
|
2245
|
-
expect.
|
|
3357
|
+
expect.objectContaining({
|
|
3358
|
+
timeoutMs: 3 * 60 * 1000,
|
|
3359
|
+
})
|
|
2246
3360
|
);
|
|
2247
3361
|
});
|
|
2248
3362
|
|
|
@@ -2286,6 +3400,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2286
3400
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2287
3401
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
2288
3402
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
3403
|
+
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
2289
3404
|
});
|
|
2290
3405
|
|
|
2291
3406
|
test('consumes Protocol V2 install progress before final update success', async () => {
|
|
@@ -2304,18 +3419,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2304
3419
|
method.postTipMessage = jest.fn();
|
|
2305
3420
|
|
|
2306
3421
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2307
|
-
targets: [{ target_id:
|
|
3422
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2308
3423
|
});
|
|
2309
3424
|
|
|
2310
3425
|
const callOptions = typedCall.mock.calls[0][3];
|
|
2311
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
2312
|
-
expect(callOptions.intermediateTypes).toEqual(['
|
|
3426
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
3427
|
+
expect(callOptions.intermediateTypes).toEqual(['DeviceFirmwareUpdateStatus']);
|
|
2313
3428
|
callOptions.onIntermediateResponse({
|
|
2314
|
-
type: '
|
|
2315
|
-
message: { target_id:
|
|
3429
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3430
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2316
3431
|
});
|
|
2317
3432
|
|
|
2318
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
3433
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2319
3434
|
});
|
|
2320
3435
|
|
|
2321
3436
|
test('accepts Protocol V2 firmware update status as start response', async () => {
|
|
@@ -2326,8 +3441,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2326
3441
|
},
|
|
2327
3442
|
});
|
|
2328
3443
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2329
|
-
type: '
|
|
2330
|
-
message: {
|
|
3444
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3445
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2331
3446
|
});
|
|
2332
3447
|
|
|
2333
3448
|
(method as any).device = stubDevice({
|
|
@@ -2337,29 +3452,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2337
3452
|
method.postTipMessage = jest.fn();
|
|
2338
3453
|
|
|
2339
3454
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2340
|
-
targets: [{ target_id:
|
|
3455
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2341
3456
|
});
|
|
2342
3457
|
|
|
2343
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3458
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2344
3459
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2345
3460
|
});
|
|
2346
3461
|
});
|
|
2347
3462
|
|
|
2348
3463
|
describe('Protocol V2 firmware update method', () => {
|
|
2349
|
-
test('returns
|
|
3464
|
+
test('returns DeviceFirmwareUpdateStatus from low-level update trigger', async () => {
|
|
2350
3465
|
const method = new DeviceFirmwareUpdate({
|
|
2351
3466
|
id: 1,
|
|
2352
3467
|
payload: {
|
|
2353
3468
|
method: 'deviceFirmwareUpdate',
|
|
2354
|
-
targetId:
|
|
3469
|
+
targetId: 4,
|
|
2355
3470
|
path: 'vol0:firmware.bin',
|
|
2356
3471
|
},
|
|
2357
3472
|
});
|
|
2358
3473
|
method.init();
|
|
2359
3474
|
|
|
2360
3475
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2361
|
-
type: '
|
|
2362
|
-
message: {
|
|
3476
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3477
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2363
3478
|
});
|
|
2364
3479
|
|
|
2365
3480
|
(method as any).device = stubDevice({
|
|
@@ -2367,11 +3482,11 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2367
3482
|
});
|
|
2368
3483
|
|
|
2369
3484
|
await expect(method.run()).resolves.toEqual({
|
|
2370
|
-
|
|
3485
|
+
records: [{ target_id: 4, status: 1 }],
|
|
2371
3486
|
});
|
|
2372
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3487
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2373
3488
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2374
|
-
targets: [{ target_id:
|
|
3489
|
+
targets: [{ target_id: 4, path: 'vol0:firmware.bin' }],
|
|
2375
3490
|
});
|
|
2376
3491
|
});
|
|
2377
3492
|
|
|
@@ -2380,7 +3495,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2380
3495
|
id: 1,
|
|
2381
3496
|
payload: {
|
|
2382
3497
|
method: 'deviceFirmwareUpdate',
|
|
2383
|
-
targetId:
|
|
3498
|
+
targetId: 4,
|
|
2384
3499
|
path: 'vol0:firmware.bin',
|
|
2385
3500
|
},
|
|
2386
3501
|
});
|
|
@@ -2425,6 +3540,30 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2425
3540
|
expect(typedCall).not.toHaveBeenCalled();
|
|
2426
3541
|
});
|
|
2427
3542
|
|
|
3543
|
+
test.each([0, 2])(
|
|
3544
|
+
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
3545
|
+
async targetId => {
|
|
3546
|
+
const typedCall = jest.fn();
|
|
3547
|
+
const method = new DeviceFirmwareUpdate({
|
|
3548
|
+
id: 1,
|
|
3549
|
+
payload: {
|
|
3550
|
+
method: 'deviceFirmwareUpdate',
|
|
3551
|
+
targetId,
|
|
3552
|
+
path: 'vol0:firmware.bin',
|
|
3553
|
+
},
|
|
3554
|
+
});
|
|
3555
|
+
method.init();
|
|
3556
|
+
(method as any).device = stubDevice({
|
|
3557
|
+
commands: { typedCall },
|
|
3558
|
+
});
|
|
3559
|
+
|
|
3560
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
3561
|
+
errorCode: HardwareErrorCode.CallMethodInvalidParameter,
|
|
3562
|
+
});
|
|
3563
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
3564
|
+
}
|
|
3565
|
+
);
|
|
3566
|
+
|
|
2428
3567
|
test('accepts targetId alias inside firmware targets', async () => {
|
|
2429
3568
|
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
2430
3569
|
const method = new DeviceFirmwareUpdate({
|
|
@@ -2434,8 +3573,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2434
3573
|
targets: [
|
|
2435
3574
|
{
|
|
2436
3575
|
target_id: undefined,
|
|
2437
|
-
targetId:
|
|
2438
|
-
path: 'vol0:
|
|
3576
|
+
targetId: 1,
|
|
3577
|
+
path: 'vol0:resource.crate.okpkg',
|
|
2439
3578
|
},
|
|
2440
3579
|
],
|
|
2441
3580
|
} as any,
|
|
@@ -2447,18 +3586,77 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2447
3586
|
|
|
2448
3587
|
await method.run();
|
|
2449
3588
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2450
|
-
targets: [{ target_id:
|
|
3589
|
+
targets: [{ target_id: 1, path: 'vol0:resource.crate.okpkg' }],
|
|
2451
3590
|
});
|
|
2452
3591
|
});
|
|
3592
|
+
|
|
3593
|
+
test('accepts firmware target enum names from low-level update params', async () => {
|
|
3594
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3595
|
+
const method = new DeviceFirmwareUpdate({
|
|
3596
|
+
id: 1,
|
|
3597
|
+
payload: {
|
|
3598
|
+
method: 'deviceFirmwareUpdate',
|
|
3599
|
+
targetId: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
3600
|
+
path: 'vol0:application_p1.bin',
|
|
3601
|
+
},
|
|
3602
|
+
});
|
|
3603
|
+
method.init();
|
|
3604
|
+
(method as any).device = stubDevice({
|
|
3605
|
+
commands: { typedCall },
|
|
3606
|
+
});
|
|
3607
|
+
|
|
3608
|
+
await method.run();
|
|
3609
|
+
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3610
|
+
targets: [{ target_id: 4, path: 'vol0:application_p1.bin' }],
|
|
3611
|
+
});
|
|
3612
|
+
});
|
|
3613
|
+
|
|
3614
|
+
test('passes firmware update status fields through to Protocol V2', async () => {
|
|
3615
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
3616
|
+
message: {
|
|
3617
|
+
records: [{ target_id: 4, status: 2, payload_version: 1, path: 'vol1:application_p1.bin' }],
|
|
3618
|
+
},
|
|
3619
|
+
});
|
|
3620
|
+
const method = new DeviceGetFirmwareUpdateStatus({
|
|
3621
|
+
id: 1,
|
|
3622
|
+
payload: {
|
|
3623
|
+
method: 'deviceGetFirmwareUpdateStatus',
|
|
3624
|
+
fields: {
|
|
3625
|
+
status: true,
|
|
3626
|
+
payload_version: true,
|
|
3627
|
+
path: true,
|
|
3628
|
+
},
|
|
3629
|
+
},
|
|
3630
|
+
});
|
|
3631
|
+
method.init();
|
|
3632
|
+
(method as any).device = stubDevice({
|
|
3633
|
+
commands: { typedCall },
|
|
3634
|
+
});
|
|
3635
|
+
|
|
3636
|
+
await expect(method.run()).resolves.toEqual({
|
|
3637
|
+
records: [{ target_id: 4, status: 2, payload_version: 1, path: 'vol1:application_p1.bin' }],
|
|
3638
|
+
});
|
|
3639
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
3640
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
3641
|
+
'DeviceFirmwareUpdateStatus',
|
|
3642
|
+
{
|
|
3643
|
+
fields: {
|
|
3644
|
+
status: true,
|
|
3645
|
+
payload_version: true,
|
|
3646
|
+
path: true,
|
|
3647
|
+
},
|
|
3648
|
+
}
|
|
3649
|
+
);
|
|
3650
|
+
});
|
|
2453
3651
|
});
|
|
2454
3652
|
|
|
2455
3653
|
describe('Protocol V2 reboot methods', () => {
|
|
2456
|
-
test('sends
|
|
3654
|
+
test('sends DeviceReboot from deviceReboot', async () => {
|
|
2457
3655
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
2458
|
-
const method = new
|
|
3656
|
+
const method = new DeviceReboot({
|
|
2459
3657
|
id: 1,
|
|
2460
3658
|
payload: {
|
|
2461
|
-
method: '
|
|
3659
|
+
method: 'deviceReboot',
|
|
2462
3660
|
rebootType: 2,
|
|
2463
3661
|
},
|
|
2464
3662
|
});
|
|
@@ -2467,37 +3665,103 @@ describe('Protocol V2 reboot methods', () => {
|
|
|
2467
3665
|
|
|
2468
3666
|
await method.run();
|
|
2469
3667
|
|
|
2470
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
3668
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceReboot', 'Success', {
|
|
2471
3669
|
reboot_type: 2,
|
|
2472
3670
|
});
|
|
2473
3671
|
});
|
|
3672
|
+
});
|
|
2474
3673
|
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const
|
|
3674
|
+
describe('Protocol V2 current low-level methods', () => {
|
|
3675
|
+
test('sends ProtocolInfoRequest from protocolInfoRequest', async () => {
|
|
3676
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { version: 1 } });
|
|
3677
|
+
const method = new ProtocolInfoRequest({
|
|
3678
|
+
id: 1,
|
|
3679
|
+
payload: { method: 'protocolInfoRequest' },
|
|
3680
|
+
});
|
|
3681
|
+
method.init();
|
|
3682
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3683
|
+
|
|
3684
|
+
await method.run();
|
|
3685
|
+
|
|
3686
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
3687
|
+
});
|
|
3688
|
+
|
|
3689
|
+
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|
|
3690
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3691
|
+
const setMethod = new DeviceFactoryInfoSet({
|
|
2478
3692
|
id: 1,
|
|
2479
3693
|
payload: {
|
|
2480
|
-
method: '
|
|
2481
|
-
|
|
3694
|
+
method: 'deviceFactoryInfoSet',
|
|
3695
|
+
serial_number: 'PR2SERIAL',
|
|
3696
|
+
burn_in_completed: true,
|
|
2482
3697
|
},
|
|
2483
3698
|
});
|
|
3699
|
+
setMethod.init();
|
|
3700
|
+
(setMethod as any).device = stubDevice({ commands: { typedCall } });
|
|
3701
|
+
|
|
3702
|
+
await setMethod.run();
|
|
3703
|
+
|
|
3704
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceFactoryInfoSet', 'Success', {
|
|
3705
|
+
info: {
|
|
3706
|
+
version: undefined,
|
|
3707
|
+
serial_number: 'PR2SERIAL',
|
|
3708
|
+
burn_in_completed: true,
|
|
3709
|
+
factory_test_completed: undefined,
|
|
3710
|
+
manufacture_time: undefined,
|
|
3711
|
+
},
|
|
3712
|
+
});
|
|
3713
|
+
|
|
3714
|
+
typedCall.mockResolvedValueOnce({ message: { serial_number: 'PR2SERIAL' } });
|
|
3715
|
+
const getMethod = new DeviceFactoryInfoGet({
|
|
3716
|
+
id: 2,
|
|
3717
|
+
payload: { method: 'deviceFactoryInfoGet' },
|
|
3718
|
+
});
|
|
3719
|
+
getMethod.init();
|
|
3720
|
+
(getMethod as any).device = stubDevice({ commands: { typedCall } });
|
|
3721
|
+
|
|
3722
|
+
await getMethod.run();
|
|
3723
|
+
|
|
3724
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceFactoryInfoGet', 'DeviceFactoryInfo', {});
|
|
3725
|
+
});
|
|
3726
|
+
|
|
3727
|
+
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|
|
3728
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3729
|
+
const method = new FilesystemPermissionFix({
|
|
3730
|
+
id: 1,
|
|
3731
|
+
payload: { method: 'filesystemPermissionFix' },
|
|
3732
|
+
});
|
|
2484
3733
|
method.init();
|
|
2485
3734
|
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
2486
3735
|
|
|
2487
3736
|
await method.run();
|
|
2488
3737
|
|
|
2489
|
-
expect(typedCall).toHaveBeenCalledWith('
|
|
2490
|
-
|
|
3738
|
+
expect(typedCall).toHaveBeenCalledWith('FilesystemPermissionFix', 'Success', {});
|
|
3739
|
+
});
|
|
3740
|
+
|
|
3741
|
+
test('sends required FilesystemFormat partition flags', async () => {
|
|
3742
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3743
|
+
const method = new FilesystemFormat({
|
|
3744
|
+
id: 1,
|
|
3745
|
+
payload: { method: 'filesystemFormat' },
|
|
3746
|
+
});
|
|
3747
|
+
method.init();
|
|
3748
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3749
|
+
|
|
3750
|
+
await method.run();
|
|
3751
|
+
|
|
3752
|
+
expect(typedCall).toHaveBeenCalledWith('FilesystemFormat', 'Success', {
|
|
3753
|
+
data: true,
|
|
3754
|
+
user: true,
|
|
2491
3755
|
});
|
|
2492
3756
|
});
|
|
2493
3757
|
});
|
|
2494
3758
|
|
|
2495
3759
|
describe('Protocol V2 raw device info method', () => {
|
|
2496
3760
|
const buildMethod = (payload: Record<string, unknown> = {}) => {
|
|
2497
|
-
const method = new
|
|
3761
|
+
const method = new DeviceInfoGet({
|
|
2498
3762
|
id: 1,
|
|
2499
3763
|
payload: {
|
|
2500
|
-
method: '
|
|
3764
|
+
method: 'deviceInfoGet',
|
|
2501
3765
|
...payload,
|
|
2502
3766
|
},
|
|
2503
3767
|
});
|
|
@@ -2507,7 +3771,7 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2507
3771
|
|
|
2508
3772
|
test('passes requested targets/types through and returns the raw DeviceInfo message', async () => {
|
|
2509
3773
|
const method = buildMethod({
|
|
2510
|
-
targets: { hw: true, se1: true
|
|
3774
|
+
targets: { hw: true, se1: true },
|
|
2511
3775
|
types: { version: true, hash: true },
|
|
2512
3776
|
});
|
|
2513
3777
|
const typedCall = jest.fn().mockResolvedValue({
|
|
@@ -2523,9 +3787,8 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2523
3787
|
protocol_version: 1,
|
|
2524
3788
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2525
3789
|
});
|
|
2526
|
-
// 未知 key(junk)被过滤,已选 key 原样透传,不构建 DeviceProfile
|
|
2527
3790
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2528
|
-
'
|
|
3791
|
+
'DeviceInfoGet',
|
|
2529
3792
|
'DeviceInfo',
|
|
2530
3793
|
{
|
|
2531
3794
|
targets: { hw: true, se1: true },
|
|
@@ -2535,6 +3798,20 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2535
3798
|
);
|
|
2536
3799
|
});
|
|
2537
3800
|
|
|
3801
|
+
test('rejects removed target/type names before transport call', () => {
|
|
3802
|
+
expect(() =>
|
|
3803
|
+
buildMethod({
|
|
3804
|
+
targets: { hw: true, bt: true },
|
|
3805
|
+
})
|
|
3806
|
+
).toThrow('targets');
|
|
3807
|
+
|
|
3808
|
+
expect(() =>
|
|
3809
|
+
buildMethod({
|
|
3810
|
+
types: { version: true, digest: true },
|
|
3811
|
+
})
|
|
3812
|
+
).toThrow('types');
|
|
3813
|
+
});
|
|
3814
|
+
|
|
2538
3815
|
test('defaults to the basic targets/types when none are given', async () => {
|
|
2539
3816
|
const method = buildMethod();
|
|
2540
3817
|
const typedCall = jest.fn().mockResolvedValue({ type: 'DeviceInfo', message: {} });
|
|
@@ -2546,10 +3823,10 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2546
3823
|
await method.run();
|
|
2547
3824
|
|
|
2548
3825
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2549
|
-
'
|
|
3826
|
+
'DeviceInfoGet',
|
|
2550
3827
|
'DeviceInfo',
|
|
2551
3828
|
{
|
|
2552
|
-
targets: { hw: true, fw: true,
|
|
3829
|
+
targets: { hw: true, fw: true, coprocessor: true, status: true },
|
|
2553
3830
|
types: { version: true, specific: true },
|
|
2554
3831
|
},
|
|
2555
3832
|
{ timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }
|
|
@@ -2569,48 +3846,6 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2569
3846
|
});
|
|
2570
3847
|
});
|
|
2571
3848
|
|
|
2572
|
-
describe('Protocol V2 onboarding status method', () => {
|
|
2573
|
-
test('returns OnboardingStatus from low-level status query', async () => {
|
|
2574
|
-
const method = new DeviceGetOnboardingStatus({
|
|
2575
|
-
id: 1,
|
|
2576
|
-
payload: {
|
|
2577
|
-
method: 'deviceGetOnboardingStatus',
|
|
2578
|
-
},
|
|
2579
|
-
});
|
|
2580
|
-
method.init();
|
|
2581
|
-
|
|
2582
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
2583
|
-
type: 'OnboardingStatus',
|
|
2584
|
-
message: {
|
|
2585
|
-
step: 3,
|
|
2586
|
-
setup: {
|
|
2587
|
-
restore: {
|
|
2588
|
-
mnemonic: true,
|
|
2589
|
-
},
|
|
2590
|
-
},
|
|
2591
|
-
detail_code: 7,
|
|
2592
|
-
detail_str: 'Recovery Phrase',
|
|
2593
|
-
},
|
|
2594
|
-
});
|
|
2595
|
-
|
|
2596
|
-
(method as any).device = stubDevice({
|
|
2597
|
-
commands: { typedCall },
|
|
2598
|
-
});
|
|
2599
|
-
|
|
2600
|
-
await expect(method.run()).resolves.toEqual({
|
|
2601
|
-
step: 3,
|
|
2602
|
-
setup: {
|
|
2603
|
-
restore: {
|
|
2604
|
-
mnemonic: true,
|
|
2605
|
-
},
|
|
2606
|
-
},
|
|
2607
|
-
detail_code: 7,
|
|
2608
|
-
detail_str: 'Recovery Phrase',
|
|
2609
|
-
});
|
|
2610
|
-
expect(typedCall).toHaveBeenCalledWith('GetOnboardingStatus', 'OnboardingStatus', {});
|
|
2611
|
-
});
|
|
2612
|
-
});
|
|
2613
|
-
|
|
2614
3849
|
describe('Protocol V2 file write method', () => {
|
|
2615
3850
|
test('rejects invalid write parameters before transport call', () => {
|
|
2616
3851
|
expect(() => {
|
|
@@ -2660,17 +3895,22 @@ describe('Protocol V2 file write method', () => {
|
|
|
2660
3895
|
method.init();
|
|
2661
3896
|
await method.run();
|
|
2662
3897
|
|
|
2663
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
3898
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
3899
|
+
'FilesystemFileWrite',
|
|
3900
|
+
'FilesystemFile',
|
|
3901
|
+
{
|
|
3902
|
+
file: {
|
|
3903
|
+
path: 'vol1:test.bin',
|
|
3904
|
+
offset: 1,
|
|
3905
|
+
total_size: 2,
|
|
3906
|
+
data: new Uint8Array([1]),
|
|
3907
|
+
},
|
|
3908
|
+
overwrite: false,
|
|
3909
|
+
append: false,
|
|
3910
|
+
ui_percentage: 100,
|
|
2669
3911
|
},
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
ui_percentage: 99,
|
|
2673
|
-
}, { timeoutMs: undefined });
|
|
3912
|
+
{ timeoutMs: undefined }
|
|
3913
|
+
);
|
|
2674
3914
|
expect(method.postMessage).toHaveBeenCalledWith({
|
|
2675
3915
|
event: 'UI_EVENT',
|
|
2676
3916
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
@@ -2703,28 +3943,40 @@ describe('Protocol V2 file write method', () => {
|
|
|
2703
3943
|
const result = await method.run();
|
|
2704
3944
|
|
|
2705
3945
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2706
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3946
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3947
|
+
1,
|
|
3948
|
+
'FilesystemFileWrite',
|
|
3949
|
+
'FilesystemFile',
|
|
3950
|
+
{
|
|
3951
|
+
file: {
|
|
3952
|
+
path: 'vol1:test.bin',
|
|
3953
|
+
offset: 0,
|
|
3954
|
+
total_size: 4097,
|
|
3955
|
+
data: data.slice(0, 4000),
|
|
3956
|
+
},
|
|
3957
|
+
overwrite: true,
|
|
3958
|
+
append: false,
|
|
3959
|
+
ui_percentage: 0,
|
|
3960
|
+
},
|
|
3961
|
+
{ timeoutMs: undefined }
|
|
3962
|
+
);
|
|
3963
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3964
|
+
2,
|
|
3965
|
+
'FilesystemFileWrite',
|
|
3966
|
+
'FilesystemFile',
|
|
3967
|
+
{
|
|
3968
|
+
file: {
|
|
3969
|
+
path: 'vol1:test.bin',
|
|
3970
|
+
offset: 4000,
|
|
3971
|
+
total_size: 4097,
|
|
3972
|
+
data: data.slice(4000),
|
|
3973
|
+
},
|
|
3974
|
+
overwrite: false,
|
|
3975
|
+
append: false,
|
|
3976
|
+
ui_percentage: 100,
|
|
3977
|
+
},
|
|
3978
|
+
{ timeoutMs: undefined }
|
|
3979
|
+
);
|
|
2728
3980
|
expect(result).toMatchObject({
|
|
2729
3981
|
path: 'vol1:test.bin',
|
|
2730
3982
|
processed_byte: 4097,
|
|
@@ -2734,8 +3986,8 @@ describe('Protocol V2 file write method', () => {
|
|
|
2734
3986
|
event: 'UI_EVENT',
|
|
2735
3987
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
2736
3988
|
payload: expect.objectContaining({
|
|
2737
|
-
progress:
|
|
2738
|
-
transferredBytes:
|
|
3989
|
+
progress: 97,
|
|
3990
|
+
transferredBytes: 4000,
|
|
2739
3991
|
totalBytes: 4097,
|
|
2740
3992
|
elapsedMs: expect.any(Number),
|
|
2741
3993
|
}),
|
|
@@ -2784,6 +4036,36 @@ describe('Protocol V2 file write method', () => {
|
|
|
2784
4036
|
});
|
|
2785
4037
|
|
|
2786
4038
|
describe('Protocol V2 file read method', () => {
|
|
4039
|
+
test('uses a 900-byte chunk limit by default in BLE environments', async () => {
|
|
4040
|
+
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native');
|
|
4041
|
+
const typedCall = jest
|
|
4042
|
+
.fn()
|
|
4043
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(900) } })
|
|
4044
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(1) } });
|
|
4045
|
+
const method = new FileRead({
|
|
4046
|
+
id: 1,
|
|
4047
|
+
payload: {
|
|
4048
|
+
method: 'fileRead',
|
|
4049
|
+
path: 'vol1:test.bin',
|
|
4050
|
+
offset: 0,
|
|
4051
|
+
totalSize: 901,
|
|
4052
|
+
},
|
|
4053
|
+
});
|
|
4054
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4055
|
+
|
|
4056
|
+
try {
|
|
4057
|
+
method.init();
|
|
4058
|
+
await method.run();
|
|
4059
|
+
} finally {
|
|
4060
|
+
getSettingsSpy.mockRestore();
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4063
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4064
|
+
expect(typedCall.mock.calls[0][2].chunk_len).toBe(900);
|
|
4065
|
+
expect(typedCall.mock.calls[1][2].file.offset).toBe(900);
|
|
4066
|
+
expect(typedCall.mock.calls[1][2].chunk_len).toBe(1);
|
|
4067
|
+
});
|
|
4068
|
+
|
|
2787
4069
|
test('rejects invalid read and directory parameters before transport call', () => {
|
|
2788
4070
|
expect(() => {
|
|
2789
4071
|
const method = new FileRead({
|
|
@@ -2860,7 +4142,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2860
4142
|
total_size: 0,
|
|
2861
4143
|
},
|
|
2862
4144
|
chunk_len: 64,
|
|
2863
|
-
ui_percentage:
|
|
4145
|
+
ui_percentage: 0,
|
|
2864
4146
|
});
|
|
2865
4147
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'FilesystemFileRead', 'FilesystemFile', {
|
|
2866
4148
|
file: {
|
|
@@ -2869,7 +4151,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2869
4151
|
total_size: 0,
|
|
2870
4152
|
},
|
|
2871
4153
|
chunk_len: 1,
|
|
2872
|
-
ui_percentage:
|
|
4154
|
+
ui_percentage: 100,
|
|
2873
4155
|
});
|
|
2874
4156
|
expect(result.data.byteLength).toBe(65);
|
|
2875
4157
|
expect(result.data[0]).toBe(1);
|