@onekeyfe/hd-core 1.2.0-alpha.0 → 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 +1930 -765
- 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/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +26 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceInfo.d.ts.map +1 -1
- package/dist/api/GetFeatures.d.ts +1 -1
- package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +4 -4
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +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/bootloaderHelper.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/firmware/uploadFirmware.d.ts.map +1 -1
- 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 -19
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignData.d.ts +1 -5
- package/dist/api/ton/TonSignData.d.ts.map +1 -1
- package/dist/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/constants/index.d.ts +2 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +17 -18
- 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 +6 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceProfile.d.ts +3 -4
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/deviceProfile/index.d.ts +1 -1
- package/dist/deviceProfile/index.d.ts.map +1 -1
- package/dist/index.d.ts +657 -539
- package/dist/index.js +2579 -2161
- 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 +4 -2
- package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts +4 -4
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +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 +88 -8
- 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 -1
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/capabilitieUtils.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/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
- package/dist/utils/findDefectiveBatchDevice.d.ts +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/FirmwareUpdateV2.ts +9 -2
- package/src/api/FirmwareUpdateV3.ts +2 -1
- package/src/api/FirmwareUpdateV4.ts +1089 -285
- package/src/api/GetDeviceInfo.ts +6 -13
- package/src/api/GetOnekeyFeatures.ts +3 -14
- package/src/api/GetPassphraseState.ts +7 -4
- package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
- package/src/api/device/DeviceRebootToBootloader.ts +4 -4
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +18 -4
- package/src/api/firmware/bootloaderHelper.ts +3 -2
- package/src/api/firmware/getBinary.ts +1 -1
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/releaseHelper.ts +3 -3
- package/src/api/firmware/uploadFirmware.ts +5 -2
- 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 +2 -2
- 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 +58 -59
- 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/constants/index.ts +10 -1
- package/src/core/index.ts +22 -10
- package/src/data/messages/messages-protocol-v2.json +992 -1420
- package/src/data/messages/messages.json +8 -0
- package/src/data-manager/DataManager.ts +11 -4
- package/src/data-manager/connectSettings.ts +4 -0
- package/src/device/Device.ts +158 -294
- package/src/device/DevicePool.ts +9 -9
- package/src/device/DeviceWalletSessionStore.ts +78 -0
- package/src/deviceProfile/buildDeviceFeatures.ts +365 -0
- package/src/deviceProfile/buildDeviceProfile.ts +165 -200
- package/src/deviceProfile/index.ts +4 -1
- package/src/inject.ts +13 -16
- package/src/protocols/protocol-v2/features.ts +28 -67
- 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 +4 -2
- package/src/types/api/getPassphraseState.ts +4 -4
- package/src/types/api/index.ts +17 -20
- package/src/types/api/protocolV2.ts +35 -40
- package/src/types/api/sessionCache.ts +14 -0
- package/src/types/device.ts +98 -34
- package/src/types/params.ts +4 -0
- package/src/types/settings.ts +42 -30
- package/src/utils/capabilitieUtils.ts +1 -2
- package/src/utils/deviceFeaturesUtils.ts +54 -29
- package/src/utils/deviceInfoUtils.ts +11 -29
- package/src/utils/deviceVersionUtils.ts +7 -25
- package/src/utils/findDefectiveBatchDevice.ts +6 -6
- 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/dist/deviceProfile/legacyFeaturesView.d.ts +0 -5
- package/dist/deviceProfile/legacyFeaturesView.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
- package/src/deviceProfile/legacyFeaturesView.ts +0 -123
|
@@ -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',
|
|
@@ -91,47 +91,48 @@ const descriptor = {
|
|
|
91
91
|
*/
|
|
92
92
|
function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
93
93
|
const d = device as any;
|
|
94
|
-
d.isProtocolV2 ??= () =>
|
|
95
|
-
d.profile?.protocol === 'V2' || d.originalDescriptor?.protocolType === 'V2';
|
|
94
|
+
d.isProtocolV2 ??= () => d.originalDescriptor?.protocolType === 'V2';
|
|
96
95
|
d.getProtocol ??= () => (d.isProtocolV2() ? 'V2' : 'V1');
|
|
97
|
-
d.getCurrentDeviceType ??= () =>
|
|
98
|
-
d.getCurrentFirmwareType ??= () =>
|
|
96
|
+
d.getCurrentDeviceType ??= () => getDeviceType(d.features);
|
|
97
|
+
d.getCurrentFirmwareType ??= () => getFirmwareType(d.features);
|
|
99
98
|
d.getCurrentFirmwareVersionString ??= () =>
|
|
100
|
-
d.
|
|
101
|
-
|
|
102
|
-
d.
|
|
103
|
-
d.
|
|
104
|
-
d.
|
|
105
|
-
d.
|
|
106
|
-
d.getCurrentPassphraseProtection ??= () =>
|
|
107
|
-
d.profile ? d.profile.status?.passphraseProtection : d.features?.passphrase_protection;
|
|
99
|
+
d.features ? getDeviceFirmwareVersion(d.features).join('.') : undefined;
|
|
100
|
+
d.getCurrentBLEFirmwareVersionString ??= () => d.features?.bleVersion;
|
|
101
|
+
d.getCurrentSafetyChecks ??= () => d.features?.safetyChecks;
|
|
102
|
+
d.getCurrentDeviceId ??= () => d.features?.deviceId || undefined;
|
|
103
|
+
d.getCurrentSerialNo ??= () => d.features?.serialNo || '';
|
|
104
|
+
d.getCurrentPassphraseProtection ??= () => d.features?.passphraseProtection;
|
|
108
105
|
d.getCurrentMethodVersionRange ??= (fn: (model: any) => any) => {
|
|
109
106
|
const deviceType = d.getCurrentDeviceType();
|
|
110
107
|
const range = fn(deviceType);
|
|
111
108
|
if (range) return range;
|
|
112
109
|
return getMethodVersionRange(d.features, fn);
|
|
113
110
|
};
|
|
114
|
-
d.
|
|
115
|
-
d.
|
|
116
|
-
|
|
117
|
-
d.applyProfileUpdate ??= (p: any) => {
|
|
118
|
-
d.profile = p;
|
|
119
|
-
return p;
|
|
111
|
+
d.updateProtocolV2Features ??= (deviceInfo?: ProtocolV2DeviceInfo) => {
|
|
112
|
+
d.features = buildProtocolV2FeaturesPayload(deviceInfo, d.features);
|
|
113
|
+
return d.features;
|
|
120
114
|
};
|
|
121
115
|
return device;
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
// 直接复用生产映射函数,避免测试内副本与实现漂移(之前的手抄副本已缺失 se boot 字段)
|
|
125
119
|
function normalizeProtocolV2Features(_descriptor: unknown, deviceInfo?: ProtocolV2DeviceInfo) {
|
|
126
|
-
|
|
127
|
-
deviceInfo,
|
|
128
|
-
sources: ['deviceInfo'],
|
|
129
|
-
scope: 'verify',
|
|
130
|
-
});
|
|
131
|
-
return buildProtocolV2GetFeaturesPayload(profile, deviceInfo);
|
|
120
|
+
return buildProtocolV2FeaturesPayload(deviceInfo);
|
|
132
121
|
}
|
|
133
122
|
|
|
134
|
-
|
|
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({
|
|
135
136
|
commands,
|
|
136
137
|
descriptor: inputDescriptor,
|
|
137
138
|
}: {
|
|
@@ -143,32 +144,6 @@ async function requestProtocolV2LegacyFeatures({
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
describe('Protocol V2 feature adapter', () => {
|
|
146
|
-
test('skips DeviceGetDeviceInfo wire call and returns mock when mock is enabled', async () => {
|
|
147
|
-
const typedCall = jest.fn();
|
|
148
|
-
setProtocolV2DeviceInfoMock(true);
|
|
149
|
-
try {
|
|
150
|
-
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
151
|
-
commands: { typedCall } as unknown as DeviceCommands,
|
|
152
|
-
});
|
|
153
|
-
expect(typedCall).not.toHaveBeenCalled();
|
|
154
|
-
expect(deviceInfo).toEqual(buildMockProtocolV2DeviceInfo());
|
|
155
|
-
// 空 serial_no + fallbackSerialNo:身份字段回退 transport path
|
|
156
|
-
const profile = buildProfileFromProtocolV2({
|
|
157
|
-
deviceInfo,
|
|
158
|
-
fallbackSerialNo: 'usb-path',
|
|
159
|
-
});
|
|
160
|
-
expect(profile).toMatchObject({
|
|
161
|
-
protocol: 'V2',
|
|
162
|
-
deviceId: 'usb-path',
|
|
163
|
-
serialNo: 'usb-path',
|
|
164
|
-
status: { initialized: true },
|
|
165
|
-
versions: { firmware: '0.1.0' },
|
|
166
|
-
});
|
|
167
|
-
} finally {
|
|
168
|
-
setProtocolV2DeviceInfoMock(false);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
147
|
test('normalizes Protocol V2 DeviceInfo into existing Features fields', () => {
|
|
173
148
|
const features = normalizeProtocolV2Features(descriptor as any, {
|
|
174
149
|
protocol_version: 1,
|
|
@@ -176,31 +151,31 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
176
151
|
serial_no: 'PR2SERIAL',
|
|
177
152
|
},
|
|
178
153
|
fw: {
|
|
179
|
-
|
|
154
|
+
application_data: {
|
|
180
155
|
version: '0.1.0',
|
|
181
156
|
hash: [1, 2, 255],
|
|
182
157
|
},
|
|
183
|
-
|
|
158
|
+
bootloader: {
|
|
184
159
|
version: '0.2.0',
|
|
185
160
|
build_id: 'boot-build',
|
|
186
161
|
hash: new Uint8Array([10, 11]),
|
|
187
162
|
},
|
|
188
|
-
|
|
163
|
+
application: {
|
|
189
164
|
version: '1.2.3',
|
|
190
165
|
build_id: 'app-build',
|
|
191
166
|
hash: 'abc123',
|
|
192
167
|
},
|
|
193
168
|
},
|
|
194
|
-
|
|
195
|
-
|
|
169
|
+
coprocessor: {
|
|
170
|
+
application: {
|
|
196
171
|
version: '4.5.6',
|
|
197
172
|
build_id: 'bt-build',
|
|
198
173
|
hash: [12, 13],
|
|
199
174
|
},
|
|
200
|
-
|
|
175
|
+
bt_adv_name: 'Pro2 BLE',
|
|
201
176
|
},
|
|
202
177
|
se1: {
|
|
203
|
-
|
|
178
|
+
application: {
|
|
204
179
|
version: '7.8.9',
|
|
205
180
|
build_id: 'se-build',
|
|
206
181
|
hash: [14, 15],
|
|
@@ -208,61 +183,80 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
208
183
|
state: 85,
|
|
209
184
|
},
|
|
210
185
|
se2: {
|
|
211
|
-
|
|
186
|
+
application: {
|
|
212
187
|
version: '8.0.0',
|
|
213
188
|
},
|
|
214
189
|
state: 0,
|
|
215
190
|
},
|
|
216
191
|
status: {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
bt_enable: true,
|
|
192
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
193
|
+
unlocked: true,
|
|
220
194
|
init_states: false,
|
|
221
195
|
backup_required: true,
|
|
222
|
-
|
|
196
|
+
passphrase_enabled: true,
|
|
197
|
+
attach_to_pin_enabled: true,
|
|
198
|
+
unlocked_by_attach_to_pin: true,
|
|
223
199
|
},
|
|
224
200
|
});
|
|
225
201
|
|
|
226
|
-
expect(features.
|
|
227
|
-
expect(features.
|
|
228
|
-
expect(features.
|
|
229
|
-
expect(features.
|
|
230
|
-
expect(features.
|
|
231
|
-
expect(features.
|
|
232
|
-
expect(features.
|
|
233
|
-
expect(features.
|
|
234
|
-
expect(features.
|
|
235
|
-
expect(features.
|
|
236
|
-
expect(features.
|
|
237
|
-
expect(features.
|
|
238
|
-
expect(features.
|
|
239
|
-
expect(features.
|
|
240
|
-
expect(features.
|
|
241
|
-
expect(features.
|
|
242
|
-
expect(features.
|
|
243
|
-
expect(features.
|
|
244
|
-
expect(features.onekey_se01_version).toBe('7.8.9');
|
|
245
|
-
expect(features.onekey_se01_hash).toBe('0e0f');
|
|
246
|
-
expect(features.onekey_se01_state).toBe('APP');
|
|
247
|
-
expect(features.onekey_se02_state).toBe('BOOT');
|
|
248
|
-
expect(features.label).toBe('My Pro2');
|
|
249
|
-
expect(features.language).toBe('en-US');
|
|
202
|
+
expect(features.deviceId).toBe('PRO2-DEVICE-ID');
|
|
203
|
+
expect(features.serialNo).toBe('PR2SERIAL');
|
|
204
|
+
expect(features.deviceType).toBe('pro2');
|
|
205
|
+
expect(features.protocolVersion).toBe(1);
|
|
206
|
+
expect(features.firmwareVersion).toBe('1.2.3');
|
|
207
|
+
expect(features.verify?.firmwareBuildId).toBe('app-build');
|
|
208
|
+
expect(features.verify?.firmwareHash).toBe('abc123');
|
|
209
|
+
expect(features.bootloaderVersion).toBe('0.2.0');
|
|
210
|
+
expect(features.verify?.bootloaderBuildId).toBe('boot-build');
|
|
211
|
+
expect(features.verify?.bootloaderHash).toBe('0a0b');
|
|
212
|
+
expect(features.verify?.boardHash).toBe('0102ff');
|
|
213
|
+
expect(features.bleName).toBe('Pro2 BLE');
|
|
214
|
+
expect(features.bleVersion).toBe('4.5.6');
|
|
215
|
+
expect(features.verify?.bleHash).toBe('0c0d');
|
|
216
|
+
expect(features.se01Version).toBe('7.8.9');
|
|
217
|
+
expect(features.verify?.se01Hash).toBe('0e0f');
|
|
218
|
+
expect(features.label).toBeNull();
|
|
219
|
+
expect(features.language).toBeNull();
|
|
250
220
|
expect(features.initialized).toBe(false);
|
|
251
|
-
expect(features.
|
|
252
|
-
expect(features.
|
|
253
|
-
expect(features.
|
|
221
|
+
expect(features.unlocked).toBe(true);
|
|
222
|
+
expect(features.backupRequired).toBe(true);
|
|
223
|
+
expect(features.passphraseProtection).toBe(true);
|
|
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);
|
|
254
249
|
});
|
|
255
250
|
|
|
256
|
-
test('uses
|
|
251
|
+
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
257
252
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
258
|
-
|
|
259
|
-
features.
|
|
253
|
+
features.firmwareVersion = '1.2.3';
|
|
254
|
+
features.passphraseProtection = true;
|
|
260
255
|
const typedCall = jest.fn().mockResolvedValue({
|
|
261
|
-
type: '
|
|
256
|
+
type: 'DeviceSession',
|
|
262
257
|
message: {
|
|
263
|
-
|
|
258
|
+
btc_test_address: 'state-1',
|
|
264
259
|
session_id: 'session-1',
|
|
265
|
-
unlocked_attach_pin: false,
|
|
266
260
|
},
|
|
267
261
|
});
|
|
268
262
|
const commands = { typedCall } as unknown as DeviceCommands;
|
|
@@ -273,35 +267,294 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
273
267
|
await getPassphraseState(device, {
|
|
274
268
|
expectPassphraseState: 'state-1',
|
|
275
269
|
});
|
|
276
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
277
|
-
|
|
278
|
-
|
|
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', {});
|
|
279
283
|
|
|
280
284
|
await getPassphraseState(device, {
|
|
281
|
-
|
|
282
|
-
|
|
285
|
+
onlyMainPin: true,
|
|
286
|
+
});
|
|
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 },
|
|
283
294
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
295
|
+
const updateProtocolV2Features = jest.fn();
|
|
296
|
+
const method = new DeviceStatusGet({
|
|
297
|
+
payload: { method: 'deviceStatusGet', connectId: 'connect-id' },
|
|
287
298
|
});
|
|
299
|
+
method.init();
|
|
300
|
+
method.device = stubDevice({
|
|
301
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
302
|
+
commands: { typedCall },
|
|
303
|
+
updateProtocolV2Features,
|
|
304
|
+
}) as any;
|
|
288
305
|
|
|
289
|
-
await
|
|
290
|
-
|
|
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
|
+
},
|
|
291
535
|
});
|
|
292
|
-
|
|
293
|
-
|
|
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',
|
|
294
546
|
});
|
|
547
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
295
548
|
});
|
|
296
549
|
|
|
297
550
|
test('returns unified GetPassphraseState object payload for existing Pro devices', async () => {
|
|
298
551
|
const features = {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
552
|
+
deviceId: 'pro-device-id',
|
|
553
|
+
deviceType: 'pro',
|
|
554
|
+
firmwareVersion: '4.15.0',
|
|
555
|
+
passphraseProtection: true,
|
|
556
|
+
sessionId: 'feature-session',
|
|
557
|
+
unlockedAttachPin: true,
|
|
305
558
|
};
|
|
306
559
|
const typedCall = jest.fn().mockResolvedValue({
|
|
307
560
|
type: 'PassphraseState',
|
|
@@ -319,6 +572,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
319
572
|
},
|
|
320
573
|
});
|
|
321
574
|
method.device = stubDevice({
|
|
575
|
+
originalDescriptor: { ...descriptor, protocolType: 'V1' },
|
|
322
576
|
features,
|
|
323
577
|
commands: { typedCall },
|
|
324
578
|
updateInternalState,
|
|
@@ -327,10 +581,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
327
581
|
}) as any;
|
|
328
582
|
|
|
329
583
|
await expect(method.run()).resolves.toEqual({
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
584
|
+
passphraseState: 'state-pro',
|
|
585
|
+
sessionId: 'session-pro',
|
|
586
|
+
unlockedAttachPin: false,
|
|
587
|
+
passphraseProtection: true,
|
|
334
588
|
});
|
|
335
589
|
expect(updateInternalState).toHaveBeenCalledWith(
|
|
336
590
|
true,
|
|
@@ -341,21 +595,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
341
595
|
);
|
|
342
596
|
});
|
|
343
597
|
|
|
344
|
-
test('
|
|
598
|
+
test('uses features for GetPassphraseState response metadata', async () => {
|
|
345
599
|
const features = {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
600
|
+
deviceId: null,
|
|
601
|
+
deviceType: 'pro2',
|
|
602
|
+
firmwareVersion: '4.15.0',
|
|
603
|
+
passphraseProtection: true,
|
|
604
|
+
sessionId: 'feature-session',
|
|
605
|
+
unlockedAttachPin: true,
|
|
352
606
|
};
|
|
353
607
|
const typedCall = jest.fn().mockResolvedValue({
|
|
354
|
-
type: '
|
|
608
|
+
type: 'DeviceSession',
|
|
355
609
|
message: {
|
|
356
|
-
|
|
610
|
+
btc_test_address: 'state-pro2',
|
|
357
611
|
session_id: 'session-pro2',
|
|
358
|
-
unlocked_attach_pin: false,
|
|
359
612
|
},
|
|
360
613
|
});
|
|
361
614
|
const updateInternalState = jest.fn();
|
|
@@ -367,66 +620,90 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
367
620
|
},
|
|
368
621
|
});
|
|
369
622
|
method.device = stubDevice({
|
|
623
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
370
624
|
features,
|
|
371
|
-
profile: {
|
|
372
|
-
protocol: 'V2',
|
|
373
|
-
sources: ['deviceInfo'],
|
|
374
|
-
deviceType: 'pro2',
|
|
375
|
-
firmwareType: 'universal',
|
|
376
|
-
deviceId: 'PR2SERIAL',
|
|
377
|
-
serialNo: 'PR2SERIAL',
|
|
378
|
-
label: null,
|
|
379
|
-
bleName: null,
|
|
380
|
-
status: {
|
|
381
|
-
mode: 'normal',
|
|
382
|
-
initialized: true,
|
|
383
|
-
bootloaderMode: false,
|
|
384
|
-
unlocked: null,
|
|
385
|
-
passphraseProtection: true,
|
|
386
|
-
backupRequired: false,
|
|
387
|
-
noBackup: null,
|
|
388
|
-
language: null,
|
|
389
|
-
},
|
|
390
|
-
versions: {
|
|
391
|
-
firmware: '4.15.0',
|
|
392
|
-
bootloader: null,
|
|
393
|
-
board: null,
|
|
394
|
-
ble: null,
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
625
|
commands: { typedCall },
|
|
398
626
|
updateInternalState,
|
|
399
627
|
getFeatures,
|
|
400
628
|
getCurrentDeviceType: () => 'pro2',
|
|
401
|
-
getCurrentDeviceId: () =>
|
|
629
|
+
getCurrentDeviceId: () => undefined,
|
|
402
630
|
getCurrentPassphraseProtection: () => true,
|
|
403
631
|
}) as any;
|
|
404
632
|
|
|
405
633
|
await expect(method.run()).resolves.toEqual({
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
634
|
+
passphraseState: 'state-pro2',
|
|
635
|
+
sessionId: 'session-pro2',
|
|
636
|
+
unlockedAttachPin: true,
|
|
637
|
+
passphraseProtection: true,
|
|
410
638
|
});
|
|
411
639
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
412
640
|
});
|
|
413
641
|
|
|
414
|
-
test('
|
|
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
|
+
|
|
692
|
+
test('stores Pro2 passphrase session cache without synthetic device id', async () => {
|
|
415
693
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
416
694
|
const typedCall = jest.fn().mockResolvedValue({
|
|
417
|
-
type: '
|
|
695
|
+
type: 'DeviceSession',
|
|
418
696
|
message: {
|
|
419
|
-
|
|
697
|
+
btc_test_address: 'state-profile',
|
|
420
698
|
session_id: 'session-profile',
|
|
421
|
-
unlocked_attach_pin: false,
|
|
422
699
|
},
|
|
423
700
|
});
|
|
424
701
|
|
|
425
702
|
(device as any).features = {
|
|
426
703
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
427
704
|
hw: { serial_no: 'PR2SERIAL' },
|
|
428
|
-
fw: {
|
|
429
|
-
status: {
|
|
705
|
+
fw: { application: { version: '4.15.0' } },
|
|
706
|
+
status: { passphrase_enabled: true },
|
|
430
707
|
}),
|
|
431
708
|
unlocked: true,
|
|
432
709
|
};
|
|
@@ -436,17 +713,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
436
713
|
|
|
437
714
|
device.passphraseState = 'state-profile';
|
|
438
715
|
expect(device.getInternalState()).toBe('session-profile');
|
|
439
|
-
expect(device.getInternalState('
|
|
716
|
+
expect(device.getInternalState('CACHED-ID')).toBeUndefined();
|
|
440
717
|
});
|
|
441
718
|
|
|
442
719
|
test('stores Pro2 passphrase sessions without selecting them implicitly', async () => {
|
|
443
720
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
444
721
|
const typedCall = jest.fn().mockResolvedValue({
|
|
445
|
-
type: '
|
|
722
|
+
type: 'DeviceSession',
|
|
446
723
|
message: {
|
|
447
|
-
|
|
724
|
+
btc_test_address: 'state-auto',
|
|
448
725
|
session_id: 'session-auto',
|
|
449
|
-
unlocked_attach_pin: false,
|
|
450
726
|
},
|
|
451
727
|
});
|
|
452
728
|
|
|
@@ -459,8 +735,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
459
735
|
hw: { serial_no: 'PR2SERIAL' },
|
|
460
736
|
}
|
|
461
737
|
);
|
|
462
|
-
(device as any).features.
|
|
463
|
-
(device as any).features.
|
|
738
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
739
|
+
(device as any).features.passphraseProtection = true;
|
|
464
740
|
(device as any).features.unlocked = true;
|
|
465
741
|
(device as any).commands = { typedCall };
|
|
466
742
|
|
|
@@ -470,23 +746,20 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
470
746
|
});
|
|
471
747
|
|
|
472
748
|
expect(device.passphraseState).toBeUndefined();
|
|
473
|
-
expect(device.features?.
|
|
474
|
-
expect(device.features?.
|
|
749
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
750
|
+
expect(device.features?.sessionId).toBeNull();
|
|
475
751
|
expect(device.getInternalState()).toBeUndefined();
|
|
476
752
|
device.passphraseState = 'state-auto';
|
|
477
753
|
expect(device.getInternalState()).toBe('session-auto');
|
|
478
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
479
|
-
passphrase_state: undefined,
|
|
480
|
-
});
|
|
754
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
481
755
|
});
|
|
482
756
|
|
|
483
757
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
484
758
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
485
759
|
const typedCall = jest.fn().mockResolvedValue({
|
|
486
|
-
type: '
|
|
760
|
+
type: 'DeviceSession',
|
|
487
761
|
message: {
|
|
488
762
|
session_id: 'main-pin-session',
|
|
489
|
-
unlocked_attach_pin: false,
|
|
490
763
|
},
|
|
491
764
|
});
|
|
492
765
|
|
|
@@ -497,11 +770,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
497
770
|
} as any,
|
|
498
771
|
{
|
|
499
772
|
status: {
|
|
500
|
-
|
|
773
|
+
passphrase_enabled: false,
|
|
501
774
|
},
|
|
502
775
|
}
|
|
503
776
|
);
|
|
504
|
-
(device as any).features.
|
|
777
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
505
778
|
(device as any).features.unlocked = true;
|
|
506
779
|
(device as any).commands = { typedCall };
|
|
507
780
|
|
|
@@ -512,22 +785,19 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
512
785
|
newSession: 'main-pin-session',
|
|
513
786
|
});
|
|
514
787
|
|
|
515
|
-
expect(device.features?.
|
|
516
|
-
expect(device.features?.
|
|
788
|
+
expect(device.features?.passphraseProtection).toBe(false);
|
|
789
|
+
expect(device.features?.sessionId).toBeNull();
|
|
517
790
|
expect(device.getInternalState()).toBeUndefined();
|
|
518
|
-
expect(typedCall).toHaveBeenLastCalledWith('
|
|
519
|
-
_only_main_pin: true,
|
|
520
|
-
});
|
|
791
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
521
792
|
});
|
|
522
793
|
|
|
523
794
|
test('does not let skipPassphraseCheck hide Pro2 passphrase state mismatch', async () => {
|
|
524
795
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
525
796
|
const typedCall = jest.fn().mockResolvedValue({
|
|
526
|
-
type: '
|
|
797
|
+
type: 'DeviceSession',
|
|
527
798
|
message: {
|
|
528
|
-
|
|
799
|
+
btc_test_address: 'wrong-state',
|
|
529
800
|
session_id: 'wrong-session',
|
|
530
|
-
unlocked_attach_pin: false,
|
|
531
801
|
},
|
|
532
802
|
});
|
|
533
803
|
|
|
@@ -535,32 +805,52 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
535
805
|
...descriptor,
|
|
536
806
|
protocolType: 'V2',
|
|
537
807
|
} as any);
|
|
538
|
-
(device as any).features.
|
|
539
|
-
(device as any).features.
|
|
808
|
+
(device as any).features.firmwareVersion = '4.15.0';
|
|
809
|
+
(device as any).features.passphraseProtection = true;
|
|
540
810
|
(device as any).commands = { typedCall };
|
|
541
811
|
|
|
542
|
-
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).
|
|
543
|
-
|
|
812
|
+
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).rejects.toEqual(
|
|
813
|
+
expect.objectContaining({
|
|
814
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
815
|
+
})
|
|
544
816
|
);
|
|
545
817
|
|
|
546
818
|
expect(device.getInternalState()).toBeUndefined();
|
|
547
|
-
expect(typedCall).toHaveBeenNthCalledWith(1, '
|
|
548
|
-
passphrase_state: 'expected-state',
|
|
549
|
-
});
|
|
819
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'DeviceSessionGet', 'DeviceSession', {});
|
|
550
820
|
});
|
|
551
821
|
|
|
552
822
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
553
823
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
554
824
|
|
|
555
|
-
expect(features.
|
|
556
|
-
expect(features.
|
|
557
|
-
expect(features.
|
|
558
|
-
expect(features.
|
|
559
|
-
expect(features.
|
|
560
|
-
|
|
825
|
+
expect(features.deviceId).toBeNull();
|
|
826
|
+
expect(features.serialNo).toBe('');
|
|
827
|
+
expect(features.initialized).toBeNull();
|
|
828
|
+
expect(features.unlocked).toBeNull();
|
|
829
|
+
expect(features.firmwarePresent).toBeNull();
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test('uses Protocol V2 status.device_id and does not fall back to serial_no', () => {
|
|
833
|
+
const features = normalizeProtocolV2Features(
|
|
834
|
+
{
|
|
835
|
+
id: 'PR2000000000',
|
|
836
|
+
path: 'PR2000000000',
|
|
837
|
+
protocolType: 'V2',
|
|
838
|
+
} as any,
|
|
839
|
+
{
|
|
840
|
+
hw: {
|
|
841
|
+
serial_no: 'PR9999999999',
|
|
842
|
+
},
|
|
843
|
+
status: {
|
|
844
|
+
device_id: 'DEVICE-ID-9999',
|
|
845
|
+
},
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
expect(features.deviceId).toBe('DEVICE-ID-9999');
|
|
850
|
+
expect(features.serialNo).toBe('PR9999999999');
|
|
561
851
|
});
|
|
562
852
|
|
|
563
|
-
test('
|
|
853
|
+
test('does not use Protocol V2 serial_no as deviceId when hw.device_id is absent', () => {
|
|
564
854
|
const features = normalizeProtocolV2Features(
|
|
565
855
|
{
|
|
566
856
|
id: 'PR2000000000',
|
|
@@ -574,140 +864,83 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
574
864
|
}
|
|
575
865
|
);
|
|
576
866
|
|
|
577
|
-
expect(features.
|
|
578
|
-
expect(features.
|
|
579
|
-
expect(features.serial_no).toBe('PR9999999999');
|
|
867
|
+
expect(features.deviceId).toBeNull();
|
|
868
|
+
expect(features.serialNo).toBe('PR9999999999');
|
|
580
869
|
});
|
|
581
870
|
|
|
582
|
-
test('
|
|
871
|
+
test('uses Protocol V2 features directly when profile is absent', () => {
|
|
583
872
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
584
873
|
(device as any).features = {
|
|
585
874
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
passphrase_protection: true,
|
|
875
|
+
serialNo: 'CACHED-SERIAL',
|
|
876
|
+
label: 'Cached Label',
|
|
877
|
+
bleName: 'Cached BLE',
|
|
878
|
+
passphraseProtection: true,
|
|
591
879
|
};
|
|
592
|
-
device.updateProfile({
|
|
593
|
-
protocol: 'V2',
|
|
594
|
-
sources: ['deviceInfo'],
|
|
595
|
-
deviceType: 'pro2',
|
|
596
|
-
firmwareType: 'universal',
|
|
597
|
-
deviceId: '',
|
|
598
|
-
serialNo: '',
|
|
599
|
-
label: null,
|
|
600
|
-
bleName: null,
|
|
601
|
-
status: {
|
|
602
|
-
mode: 'normal',
|
|
603
|
-
initialized: true,
|
|
604
|
-
bootloaderMode: false,
|
|
605
|
-
unlocked: null,
|
|
606
|
-
passphraseProtection: null,
|
|
607
|
-
backupRequired: false,
|
|
608
|
-
noBackup: null,
|
|
609
|
-
language: null,
|
|
610
|
-
},
|
|
611
|
-
versions: {
|
|
612
|
-
firmware: null,
|
|
613
|
-
bootloader: null,
|
|
614
|
-
board: null,
|
|
615
|
-
ble: null,
|
|
616
|
-
},
|
|
617
|
-
});
|
|
618
880
|
|
|
619
881
|
expect(device.toMessageObject()).toMatchObject({
|
|
620
|
-
uuid: '',
|
|
882
|
+
uuid: 'CACHED-SERIAL',
|
|
621
883
|
deviceId: null,
|
|
622
|
-
bleName:
|
|
623
|
-
label: '
|
|
884
|
+
bleName: 'Cached BLE',
|
|
885
|
+
label: 'Cached Label',
|
|
624
886
|
deviceType: 'pro2',
|
|
625
887
|
});
|
|
626
|
-
expect(device.getCurrentPassphraseProtection()).
|
|
888
|
+
expect(device.getCurrentPassphraseProtection()).toBe(true);
|
|
627
889
|
expect(device.hasUsePassphrase()).toBe(true);
|
|
628
|
-
expect(device.isInitialized()).toBe(true);
|
|
629
|
-
expect(device.getMode()).toBe('normal');
|
|
630
|
-
expect(device.getFirmwareVersion()).toBeNull();
|
|
631
890
|
});
|
|
632
891
|
|
|
633
|
-
test('
|
|
892
|
+
test('syncs Protocol V2 cached features without cached profile', () => {
|
|
634
893
|
const cached = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
635
894
|
(cached as any).features = {
|
|
636
895
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
onekey_serial_no: 'LEGACY-SERIAL',
|
|
896
|
+
deviceId: null,
|
|
897
|
+
serialNo: 'CACHED-SERIAL',
|
|
640
898
|
};
|
|
641
|
-
cached.updateProfile({
|
|
642
|
-
protocol: 'V2',
|
|
643
|
-
sources: ['deviceInfo'],
|
|
644
|
-
deviceType: 'pro2',
|
|
645
|
-
firmwareType: 'universal',
|
|
646
|
-
deviceId: '',
|
|
647
|
-
serialNo: '',
|
|
648
|
-
label: null,
|
|
649
|
-
bleName: null,
|
|
650
|
-
status: {
|
|
651
|
-
mode: 'normal',
|
|
652
|
-
initialized: true,
|
|
653
|
-
bootloaderMode: false,
|
|
654
|
-
unlocked: null,
|
|
655
|
-
passphraseProtection: null,
|
|
656
|
-
backupRequired: false,
|
|
657
|
-
noBackup: null,
|
|
658
|
-
language: null,
|
|
659
|
-
},
|
|
660
|
-
versions: {
|
|
661
|
-
firmware: null,
|
|
662
|
-
bootloader: null,
|
|
663
|
-
board: null,
|
|
664
|
-
ble: null,
|
|
665
|
-
},
|
|
666
|
-
});
|
|
667
899
|
|
|
668
900
|
const current = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
669
901
|
current.updateFromCache(cached);
|
|
670
902
|
|
|
671
903
|
expect(current.getCurrentDeviceId()).toBeUndefined();
|
|
672
|
-
expect(current.getCurrentSerialNo()).toBe('');
|
|
904
|
+
expect(current.getCurrentSerialNo()).toBe('CACHED-SERIAL');
|
|
673
905
|
expect(current.toMessageObject()).toMatchObject({
|
|
674
|
-
uuid: '',
|
|
906
|
+
uuid: 'CACHED-SERIAL',
|
|
675
907
|
deviceId: null,
|
|
676
908
|
});
|
|
677
909
|
});
|
|
678
910
|
|
|
679
|
-
test('initializes Protocol V2 features from lightweight
|
|
911
|
+
test('initializes Protocol V2 features from lightweight DeviceInfoGet', async () => {
|
|
680
912
|
const commands = {
|
|
681
913
|
typedCall: jest.fn().mockResolvedValueOnce({
|
|
682
914
|
type: 'DeviceInfo',
|
|
683
915
|
message: {
|
|
684
916
|
hw: { serial_no: 'PR2SERIAL' },
|
|
685
917
|
status: {
|
|
918
|
+
device_id: 'PRO2-STATUS-ID',
|
|
686
919
|
init_states: true,
|
|
687
|
-
|
|
920
|
+
passphrase_enabled: true,
|
|
688
921
|
},
|
|
689
922
|
},
|
|
690
923
|
}),
|
|
691
924
|
};
|
|
692
925
|
|
|
693
|
-
const features = await
|
|
926
|
+
const features = await requestProtocolV2Features({
|
|
694
927
|
commands: commands as unknown as DeviceCommands,
|
|
695
928
|
descriptor: descriptor as any,
|
|
696
929
|
});
|
|
697
930
|
|
|
698
|
-
expect(features.
|
|
931
|
+
expect(features.deviceId).toBe('PRO2-STATUS-ID');
|
|
699
932
|
expect(features.initialized).toBe(true);
|
|
700
|
-
expect(features.
|
|
933
|
+
expect(features.passphraseProtection).toBe(true);
|
|
701
934
|
expect(commands.typedCall).toHaveBeenCalledTimes(1);
|
|
702
935
|
expect(commands.typedCall).toHaveBeenNthCalledWith(
|
|
703
936
|
1,
|
|
704
|
-
'
|
|
937
|
+
'DeviceInfoGet',
|
|
705
938
|
'DeviceInfo',
|
|
706
939
|
{
|
|
707
940
|
targets: {
|
|
708
941
|
hw: true,
|
|
709
942
|
fw: true,
|
|
710
|
-
|
|
943
|
+
coprocessor: true,
|
|
711
944
|
status: true,
|
|
712
945
|
},
|
|
713
946
|
types: {
|
|
@@ -719,13 +952,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
719
952
|
);
|
|
720
953
|
});
|
|
721
954
|
|
|
722
|
-
test('fails initialization when Protocol V2
|
|
955
|
+
test('fails initialization when Protocol V2 DeviceInfoGet fails', async () => {
|
|
723
956
|
const commands = {
|
|
724
957
|
typedCall: jest.fn().mockRejectedValueOnce(new Error('DeviceInfo not supported')),
|
|
725
958
|
};
|
|
726
959
|
|
|
727
960
|
await expect(
|
|
728
|
-
|
|
961
|
+
requestProtocolV2Features({
|
|
729
962
|
commands: commands as unknown as DeviceCommands,
|
|
730
963
|
descriptor: descriptor as any,
|
|
731
964
|
})
|
|
@@ -738,20 +971,21 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
738
971
|
message: {
|
|
739
972
|
hw: { serial_no: 'PR2SERIAL' },
|
|
740
973
|
fw: {
|
|
741
|
-
|
|
974
|
+
application: {
|
|
742
975
|
version: '5.6.7',
|
|
743
976
|
},
|
|
744
977
|
},
|
|
745
|
-
|
|
746
|
-
|
|
978
|
+
coprocessor: {
|
|
979
|
+
application: {
|
|
747
980
|
version: '8.9.10',
|
|
748
981
|
},
|
|
749
|
-
|
|
982
|
+
bt_adv_name: 'Raw Pro2 BLE',
|
|
750
983
|
},
|
|
751
984
|
status: {
|
|
985
|
+
device_id: 'PRO2-BASIC-ID',
|
|
986
|
+
unlocked_by_attach_to_pin: true,
|
|
752
987
|
init_states: true,
|
|
753
|
-
|
|
754
|
-
passphrase_protection: true,
|
|
988
|
+
passphrase_enabled: true,
|
|
755
989
|
},
|
|
756
990
|
},
|
|
757
991
|
});
|
|
@@ -768,27 +1002,26 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
768
1002
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
769
1003
|
features: {
|
|
770
1004
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
771
|
-
device_id: '
|
|
772
|
-
serial_no: '
|
|
773
|
-
label: '
|
|
1005
|
+
device_id: 'CACHED-ID',
|
|
1006
|
+
serial_no: 'CACHED-SERIAL',
|
|
1007
|
+
label: 'Cached Pro2',
|
|
774
1008
|
onekey_firmware_version: '1.2.3',
|
|
775
1009
|
onekey_ble_version: '2.3.4',
|
|
776
1010
|
},
|
|
777
1011
|
commands: { typedCall },
|
|
778
1012
|
_updateFeatures: jest.fn(),
|
|
779
|
-
updateProfile: jest.fn(),
|
|
780
1013
|
});
|
|
781
1014
|
|
|
782
1015
|
const result = await method.run();
|
|
783
1016
|
|
|
784
1017
|
expect(typedCall).toHaveBeenCalledWith(
|
|
785
|
-
'
|
|
1018
|
+
'DeviceInfoGet',
|
|
786
1019
|
'DeviceInfo',
|
|
787
1020
|
{
|
|
788
1021
|
targets: {
|
|
789
1022
|
hw: true,
|
|
790
1023
|
fw: true,
|
|
791
|
-
|
|
1024
|
+
coprocessor: true,
|
|
792
1025
|
status: true,
|
|
793
1026
|
},
|
|
794
1027
|
types: {
|
|
@@ -801,12 +1034,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
801
1034
|
expect(result).toMatchObject({
|
|
802
1035
|
protocol: 'V2',
|
|
803
1036
|
deviceType: 'pro2',
|
|
804
|
-
deviceId: '
|
|
1037
|
+
deviceId: 'PRO2-BASIC-ID',
|
|
805
1038
|
serialNo: 'PR2SERIAL',
|
|
806
|
-
label:
|
|
1039
|
+
label: null,
|
|
807
1040
|
bleName: 'Raw Pro2 BLE',
|
|
808
1041
|
status: {
|
|
809
1042
|
initialized: true,
|
|
1043
|
+
unlockedAttachPin: true,
|
|
810
1044
|
passphraseProtection: true,
|
|
811
1045
|
},
|
|
812
1046
|
versions: {
|
|
@@ -816,7 +1050,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
816
1050
|
});
|
|
817
1051
|
});
|
|
818
1052
|
|
|
819
|
-
test('does not fill Protocol V2 DeviceProfile from
|
|
1053
|
+
test('does not fill Protocol V2 DeviceProfile from cached V1-shaped fields', async () => {
|
|
820
1054
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
821
1055
|
type: 'DeviceInfo',
|
|
822
1056
|
message: {
|
|
@@ -837,26 +1071,25 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
837
1071
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
838
1072
|
features: {
|
|
839
1073
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
840
|
-
device_id: '
|
|
841
|
-
serial_no: '
|
|
842
|
-
label: '
|
|
843
|
-
ble_name: '
|
|
1074
|
+
device_id: 'STALE-ID',
|
|
1075
|
+
serial_no: 'STALE-SERIAL',
|
|
1076
|
+
label: 'Stale Pro2',
|
|
1077
|
+
ble_name: 'Stale BLE',
|
|
844
1078
|
passphrase_protection: true,
|
|
845
1079
|
onekey_firmware_version: '1.2.3',
|
|
846
1080
|
onekey_ble_version: '2.3.4',
|
|
847
1081
|
},
|
|
848
1082
|
commands: { typedCall },
|
|
849
|
-
updateProfile: jest.fn(),
|
|
850
1083
|
});
|
|
851
1084
|
|
|
852
1085
|
const result = await method.run();
|
|
853
1086
|
|
|
854
1087
|
expect(result).toMatchObject({
|
|
855
1088
|
protocol: 'V2',
|
|
856
|
-
//
|
|
857
|
-
// hw.serial_no
|
|
858
|
-
deviceId: '
|
|
859
|
-
serialNo: '
|
|
1089
|
+
// 身份字段不得取自缓存的 V1-shaped features(STALE-ID / STALE-SERIAL),
|
|
1090
|
+
// hw.serial_no 缺失时也不回退 descriptor.path。
|
|
1091
|
+
deviceId: '',
|
|
1092
|
+
serialNo: '',
|
|
860
1093
|
label: null,
|
|
861
1094
|
bleName: null,
|
|
862
1095
|
status: {
|
|
@@ -896,13 +1129,13 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
896
1129
|
await method.run();
|
|
897
1130
|
|
|
898
1131
|
expect(typedCall).toHaveBeenCalledWith(
|
|
899
|
-
'
|
|
1132
|
+
'DeviceInfoGet',
|
|
900
1133
|
'DeviceInfo',
|
|
901
1134
|
{
|
|
902
1135
|
targets: {
|
|
903
1136
|
hw: true,
|
|
904
1137
|
fw: true,
|
|
905
|
-
|
|
1138
|
+
coprocessor: true,
|
|
906
1139
|
se1: true,
|
|
907
1140
|
se2: true,
|
|
908
1141
|
se3: true,
|
|
@@ -926,12 +1159,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
926
1159
|
message: {
|
|
927
1160
|
hw: { serial_no: 'PR2SERIAL' },
|
|
928
1161
|
se1: {
|
|
929
|
-
|
|
930
|
-
|
|
1162
|
+
application: { version: '1.0.1' },
|
|
1163
|
+
bootloader: { version: '1.0.0' },
|
|
931
1164
|
},
|
|
932
1165
|
se2: {
|
|
933
|
-
|
|
934
|
-
|
|
1166
|
+
application: { version: '2.0.1' },
|
|
1167
|
+
bootloader: { version: '2.0.0' },
|
|
935
1168
|
},
|
|
936
1169
|
status: { init_states: true },
|
|
937
1170
|
},
|
|
@@ -948,19 +1181,18 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
948
1181
|
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
949
1182
|
features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
950
1183
|
commands: { typedCall },
|
|
951
|
-
updateProfile: jest.fn(),
|
|
952
1184
|
});
|
|
953
1185
|
|
|
954
1186
|
const result = await method.run();
|
|
955
1187
|
|
|
956
1188
|
expect(typedCall).toHaveBeenCalledWith(
|
|
957
|
-
'
|
|
1189
|
+
'DeviceInfoGet',
|
|
958
1190
|
'DeviceInfo',
|
|
959
1191
|
{
|
|
960
1192
|
targets: {
|
|
961
1193
|
hw: true,
|
|
962
1194
|
fw: true,
|
|
963
|
-
|
|
1195
|
+
coprocessor: true,
|
|
964
1196
|
se1: true,
|
|
965
1197
|
se2: true,
|
|
966
1198
|
se3: true,
|
|
@@ -999,10 +1231,23 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
999
1231
|
expect(checkedTypes).toEqual(['pro2']);
|
|
1000
1232
|
});
|
|
1001
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
|
+
|
|
1002
1247
|
test('marks known unsupported public-chain methods as unsupported on Protocol V2', () => {
|
|
1003
1248
|
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
1004
1249
|
fw: {
|
|
1005
|
-
|
|
1250
|
+
application: {
|
|
1006
1251
|
version: '9.9.9',
|
|
1007
1252
|
},
|
|
1008
1253
|
},
|
|
@@ -1052,7 +1297,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1052
1297
|
expect(isMethodVersionRangeUnsupported(neuraiRange)).toBe(true);
|
|
1053
1298
|
});
|
|
1054
1299
|
|
|
1055
|
-
test('does not block
|
|
1300
|
+
test('does not block batch public key support checks on Protocol V2', async () => {
|
|
1056
1301
|
const paths = [{ address_n: [0x8000002c, 0x80000000, 0x80000000] }] as any;
|
|
1057
1302
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1058
1303
|
type: 'EcdsaPublicKeys',
|
|
@@ -1066,7 +1311,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1066
1311
|
originalDescriptor: { protocolType: 'V2' },
|
|
1067
1312
|
features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
1068
1313
|
fw: {
|
|
1069
|
-
|
|
1314
|
+
application: {
|
|
1070
1315
|
version: '4.14.0',
|
|
1071
1316
|
},
|
|
1072
1317
|
},
|
|
@@ -1088,7 +1333,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1088
1333
|
});
|
|
1089
1334
|
});
|
|
1090
1335
|
|
|
1091
|
-
test('
|
|
1336
|
+
test('does not call V1 OnekeyGetFeatures for Protocol V2 devices', async () => {
|
|
1092
1337
|
const method = new GetOnekeyFeatures({
|
|
1093
1338
|
id: 1,
|
|
1094
1339
|
payload: {
|
|
@@ -1100,9 +1345,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1100
1345
|
message: {
|
|
1101
1346
|
protocol_version: 1,
|
|
1102
1347
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1103
|
-
fw: {
|
|
1104
|
-
|
|
1105
|
-
status: {
|
|
1348
|
+
fw: { application: { version: '1.2.3', build_id: 'app-build' } },
|
|
1349
|
+
coprocessor: { bt_adv_name: 'Pro2 BLE' },
|
|
1350
|
+
status: { init_states: true },
|
|
1106
1351
|
},
|
|
1107
1352
|
});
|
|
1108
1353
|
|
|
@@ -1113,10 +1358,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1113
1358
|
|
|
1114
1359
|
const message = await method.run();
|
|
1115
1360
|
|
|
1116
|
-
// V2 走
|
|
1361
|
+
// V2 走 DeviceInfoGet 完整请求(含 SE 与 hash/build_id),而不是 V1 OnekeyGetFeatures
|
|
1117
1362
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1118
1363
|
expect(typedCall).toHaveBeenCalledWith(
|
|
1119
|
-
'
|
|
1364
|
+
'DeviceInfoGet',
|
|
1120
1365
|
'DeviceInfo',
|
|
1121
1366
|
expect.objectContaining({
|
|
1122
1367
|
targets: expect.objectContaining({ se1: true, se2: true, se3: true, se4: true }),
|
|
@@ -1124,17 +1369,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1124
1369
|
}),
|
|
1125
1370
|
expect.anything()
|
|
1126
1371
|
);
|
|
1127
|
-
expect(message).
|
|
1128
|
-
onekey_device_type: 'pro2',
|
|
1129
|
-
onekey_firmware_version: '1.2.3',
|
|
1130
|
-
onekey_firmware_build_id: 'app-build',
|
|
1131
|
-
onekey_serial_no: 'PR2SERIAL',
|
|
1132
|
-
onekey_ble_name: 'Pro2 BLE',
|
|
1133
|
-
});
|
|
1372
|
+
expect(message).toEqual({});
|
|
1134
1373
|
expect(message).not.toHaveProperty('label');
|
|
1135
1374
|
});
|
|
1136
1375
|
|
|
1137
|
-
test('refreshes cached Protocol V2
|
|
1376
|
+
test('refreshes cached Protocol V2 features with a lightweight status request on later runs', async () => {
|
|
1138
1377
|
const device = Device.fromDescriptor({
|
|
1139
1378
|
path: 'usb-path',
|
|
1140
1379
|
protocolType: 'V2',
|
|
@@ -1145,9 +1384,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1145
1384
|
type: 'DeviceInfo',
|
|
1146
1385
|
message: {
|
|
1147
1386
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1148
|
-
fw: {
|
|
1387
|
+
fw: { application: { version: '1.2.3' } },
|
|
1149
1388
|
status: {
|
|
1150
|
-
|
|
1389
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1390
|
+
passphrase_enabled: true,
|
|
1151
1391
|
},
|
|
1152
1392
|
},
|
|
1153
1393
|
})
|
|
@@ -1157,8 +1397,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1157
1397
|
message: {
|
|
1158
1398
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1159
1399
|
status: {
|
|
1160
|
-
|
|
1161
|
-
|
|
1400
|
+
device_id: 'PRO2-REFRESH-ID',
|
|
1401
|
+
passphrase_enabled: false,
|
|
1162
1402
|
},
|
|
1163
1403
|
},
|
|
1164
1404
|
});
|
|
@@ -1169,27 +1409,27 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1169
1409
|
await device.initialize();
|
|
1170
1410
|
|
|
1171
1411
|
expect(device.features).toMatchObject({
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
label:
|
|
1412
|
+
deviceId: 'PRO2-REFRESH-ID',
|
|
1413
|
+
firmwareVersion: '1.2.3',
|
|
1414
|
+
passphraseProtection: false,
|
|
1415
|
+
label: null,
|
|
1176
1416
|
});
|
|
1177
|
-
expect(device.profile
|
|
1417
|
+
expect((device as any).profile).toBeUndefined();
|
|
1178
1418
|
// status 字段被第二次刷新更新
|
|
1179
|
-
expect(device.
|
|
1180
|
-
expect(device.
|
|
1419
|
+
expect(device.features?.passphraseProtection).toBe(false);
|
|
1420
|
+
expect(device.features?.label).toBeNull();
|
|
1181
1421
|
// 轻量刷新不含 fw target,已有版本信息按字段级合并保留
|
|
1182
|
-
expect(device.
|
|
1422
|
+
expect(device.features?.firmwareVersion).toBe('1.2.3');
|
|
1183
1423
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1184
1424
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1185
1425
|
1,
|
|
1186
|
-
'
|
|
1426
|
+
'DeviceInfoGet',
|
|
1187
1427
|
'DeviceInfo',
|
|
1188
1428
|
{
|
|
1189
1429
|
targets: {
|
|
1190
1430
|
hw: true,
|
|
1191
1431
|
fw: true,
|
|
1192
|
-
|
|
1432
|
+
coprocessor: true,
|
|
1193
1433
|
status: true,
|
|
1194
1434
|
},
|
|
1195
1435
|
types: {
|
|
@@ -1201,15 +1441,15 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1201
1441
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
1202
1442
|
}
|
|
1203
1443
|
);
|
|
1204
|
-
// 第二次为 status-only 轻量请求(hw/
|
|
1444
|
+
// 第二次为 status-only 轻量请求(hw/coprocessor 仅用于身份字段,避免顶层覆盖清空)
|
|
1205
1445
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1206
1446
|
2,
|
|
1207
|
-
'
|
|
1447
|
+
'DeviceInfoGet',
|
|
1208
1448
|
'DeviceInfo',
|
|
1209
1449
|
{
|
|
1210
1450
|
targets: {
|
|
1211
1451
|
hw: true,
|
|
1212
|
-
|
|
1452
|
+
coprocessor: true,
|
|
1213
1453
|
status: true,
|
|
1214
1454
|
},
|
|
1215
1455
|
types: {
|
|
@@ -1223,7 +1463,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1223
1463
|
);
|
|
1224
1464
|
});
|
|
1225
1465
|
|
|
1226
|
-
test('refreshes Protocol V2 features without falling back to
|
|
1466
|
+
test('refreshes Protocol V2 features without falling back to V1 GetFeatures', async () => {
|
|
1227
1467
|
const device = Device.fromDescriptor({
|
|
1228
1468
|
path: 'usb-path',
|
|
1229
1469
|
protocolType: 'V2',
|
|
@@ -1234,7 +1474,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1234
1474
|
type: 'DeviceInfo',
|
|
1235
1475
|
message: {
|
|
1236
1476
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1237
|
-
fw: {
|
|
1477
|
+
fw: { application: { version: '1.2.3' } },
|
|
1238
1478
|
status: { init_states: true },
|
|
1239
1479
|
},
|
|
1240
1480
|
})
|
|
@@ -1242,8 +1482,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1242
1482
|
type: 'DeviceInfo',
|
|
1243
1483
|
message: {
|
|
1244
1484
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1245
|
-
fw: {
|
|
1246
|
-
status: { init_states: true,
|
|
1485
|
+
fw: { application: { version: '1.2.4' } },
|
|
1486
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1247
1487
|
},
|
|
1248
1488
|
});
|
|
1249
1489
|
|
|
@@ -1253,26 +1493,26 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1253
1493
|
const features = await device.getFeatures();
|
|
1254
1494
|
|
|
1255
1495
|
expect(device.features).toMatchObject({
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1496
|
+
deviceId: null,
|
|
1497
|
+
firmwareVersion: '1.2.4',
|
|
1498
|
+
passphraseProtection: true,
|
|
1259
1499
|
});
|
|
1260
1500
|
expect(features).toMatchObject({
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1501
|
+
deviceType: 'pro2',
|
|
1502
|
+
serialNo: 'PR2SERIAL',
|
|
1503
|
+
firmwareVersion: '1.2.4',
|
|
1504
|
+
passphraseProtection: true,
|
|
1265
1505
|
});
|
|
1266
1506
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1267
1507
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1268
1508
|
2,
|
|
1269
|
-
'
|
|
1509
|
+
'DeviceInfoGet',
|
|
1270
1510
|
'DeviceInfo',
|
|
1271
1511
|
{
|
|
1272
1512
|
targets: {
|
|
1273
1513
|
hw: true,
|
|
1274
1514
|
fw: true,
|
|
1275
|
-
|
|
1515
|
+
coprocessor: true,
|
|
1276
1516
|
status: true,
|
|
1277
1517
|
},
|
|
1278
1518
|
types: {
|
|
@@ -1285,14 +1525,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1285
1525
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1286
1526
|
});
|
|
1287
1527
|
|
|
1288
|
-
test('keeps Protocol V2 features available for
|
|
1528
|
+
test('keeps Protocol V2 features available for method internals such as evmSignTypedData', async () => {
|
|
1289
1529
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1290
1530
|
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
1291
1531
|
type: 'DeviceInfo',
|
|
1292
1532
|
message: {
|
|
1293
1533
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1294
|
-
fw: {
|
|
1295
|
-
status: { init_states: true,
|
|
1534
|
+
fw: { application: { version: '1.2.4' } },
|
|
1535
|
+
status: { init_states: true, passphrase_enabled: true },
|
|
1296
1536
|
},
|
|
1297
1537
|
});
|
|
1298
1538
|
(device as any).commands = { typedCall };
|
|
@@ -1319,7 +1559,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1319
1559
|
});
|
|
1320
1560
|
method.init();
|
|
1321
1561
|
(method as any).device = stubDevice({
|
|
1322
|
-
profile: device.profile,
|
|
1323
1562
|
features: device.features,
|
|
1324
1563
|
commands: { typedCall: jest.fn() },
|
|
1325
1564
|
});
|
|
@@ -1330,90 +1569,155 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1330
1569
|
await expect(method.run()).resolves.toEqual({ address: '0x0', signature: '0x1' });
|
|
1331
1570
|
});
|
|
1332
1571
|
|
|
1333
|
-
test('unlocks Protocol V2 devices via
|
|
1572
|
+
test('unlocks Protocol V2 devices via DeviceSessionAskPin regardless of Pro-series version gates', async () => {
|
|
1334
1573
|
const device = Device.fromDescriptor({
|
|
1335
1574
|
path: 'usb-path',
|
|
1336
1575
|
protocolType: 'V2',
|
|
1337
1576
|
} as any);
|
|
1338
|
-
const typedCall = jest.fn().
|
|
1339
|
-
|
|
1340
|
-
|
|
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}`);
|
|
1341
1595
|
});
|
|
1342
1596
|
|
|
1343
1597
|
(device as any).commands = { typedCall };
|
|
1344
1598
|
// Pro2 版本线独立于 Pro(这里是 1.2.3,不满足 Pro 系列 4.15.0 门槛),
|
|
1345
|
-
// Protocol V2
|
|
1599
|
+
// Protocol V2 走独立的设备端 PIN 解锁流程,不走 Pro 系列版本门槛或 GetAddress 探测回退。
|
|
1346
1600
|
(device as any).features = normalizeProtocolV2Features(
|
|
1347
1601
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1348
1602
|
{
|
|
1349
1603
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1350
|
-
fw: {
|
|
1604
|
+
fw: { application: { version: '1.2.3' } },
|
|
1351
1605
|
status: { init_states: true },
|
|
1352
1606
|
}
|
|
1353
1607
|
);
|
|
1354
1608
|
|
|
1355
1609
|
const features = await device.unlockDevice();
|
|
1356
1610
|
|
|
1357
|
-
expect(typedCall).
|
|
1611
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1612
|
+
['DeviceSessionAskPin', 'DeviceSessionPinResult'],
|
|
1613
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1614
|
+
]);
|
|
1358
1615
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1359
1616
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1360
1617
|
expect(features).toMatchObject({
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1618
|
+
deviceType: 'pro2',
|
|
1619
|
+
deviceId: 'PRO2-DEVICE-ID',
|
|
1620
|
+
firmwareVersion: '1.2.3',
|
|
1364
1621
|
unlocked: true,
|
|
1622
|
+
passphraseProtection: true,
|
|
1365
1623
|
});
|
|
1366
1624
|
});
|
|
1367
1625
|
|
|
1368
|
-
test('syncs Protocol V2
|
|
1626
|
+
test('syncs Protocol V2 features passphrase state from DeviceStatusGet after unlock', async () => {
|
|
1369
1627
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1370
1628
|
(device as any).features = normalizeProtocolV2Features(
|
|
1371
1629
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1372
1630
|
{
|
|
1373
1631
|
hw: { serial_no: 'PR2SERIAL' },
|
|
1374
|
-
fw: {
|
|
1375
|
-
status: {
|
|
1632
|
+
fw: { application: { version: '4.15.0' } },
|
|
1633
|
+
status: { passphrase_enabled: false },
|
|
1376
1634
|
}
|
|
1377
1635
|
);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
board: null,
|
|
1401
|
-
ble: null,
|
|
1402
|
-
},
|
|
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
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1403
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()
|
|
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;
|
|
1404
1705
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1405
1706
|
type: 'UnLockDeviceResponse',
|
|
1406
1707
|
message: {
|
|
1407
1708
|
unlocked: true,
|
|
1709
|
+
unlocked_attach_pin: true,
|
|
1408
1710
|
passphrase_protection: true,
|
|
1409
1711
|
},
|
|
1410
1712
|
});
|
|
1411
1713
|
(device as any).commands = { typedCall };
|
|
1412
1714
|
|
|
1413
|
-
await device.unlockDevice()
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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']]);
|
|
1417
1721
|
});
|
|
1418
1722
|
});
|
|
1419
1723
|
|
|
@@ -1432,7 +1736,7 @@ describe('API compatibility handling', () => {
|
|
|
1432
1736
|
method.init();
|
|
1433
1737
|
(method as any).device = stubDevice({
|
|
1434
1738
|
features: {
|
|
1435
|
-
|
|
1739
|
+
deviceType: 'pro2',
|
|
1436
1740
|
},
|
|
1437
1741
|
originalDescriptor: {
|
|
1438
1742
|
protocolType: 'V2',
|
|
@@ -1450,27 +1754,9 @@ describe('API compatibility handling', () => {
|
|
|
1450
1754
|
);
|
|
1451
1755
|
});
|
|
1452
1756
|
|
|
1453
|
-
test('
|
|
1454
|
-
const method = new TronSignMessage({
|
|
1455
|
-
id: 1,
|
|
1456
|
-
payload: {
|
|
1457
|
-
method: 'tronSignMessage',
|
|
1458
|
-
path: "m/44'/195'/0'/0/0",
|
|
1459
|
-
messageHex: '0x1234',
|
|
1460
|
-
messageType: 'V1',
|
|
1461
|
-
},
|
|
1462
|
-
});
|
|
1463
|
-
|
|
1464
|
-
expect(() => method.init()).toThrow(
|
|
1465
|
-
expect.objectContaining({
|
|
1466
|
-
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1467
|
-
})
|
|
1468
|
-
);
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
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', () => {
|
|
1472
1758
|
const features = {
|
|
1473
|
-
|
|
1759
|
+
deviceType: 'pro2',
|
|
1474
1760
|
} as Features;
|
|
1475
1761
|
|
|
1476
1762
|
const tronMethod = new TronGetAddress({
|
|
@@ -1589,9 +1875,7 @@ describe('API compatibility handling', () => {
|
|
|
1589
1875
|
});
|
|
1590
1876
|
expect(
|
|
1591
1877
|
getMethodVersionRange(features, type => tonSignDataMethod.getVersionRange()[type])
|
|
1592
|
-
).
|
|
1593
|
-
min: '0.0.0',
|
|
1594
|
-
});
|
|
1878
|
+
).toBeUndefined();
|
|
1595
1879
|
expect(
|
|
1596
1880
|
getMethodVersionRange(features, type => suiGetAddressMethod.getVersionRange()[type])
|
|
1597
1881
|
).toEqual({
|
|
@@ -1621,7 +1905,7 @@ describe('API compatibility handling', () => {
|
|
|
1621
1905
|
|
|
1622
1906
|
test('allows Pro2 Solana signing methods through Protocol V2 version checks', () => {
|
|
1623
1907
|
const features = {
|
|
1624
|
-
|
|
1908
|
+
deviceType: 'pro2',
|
|
1625
1909
|
} as Features;
|
|
1626
1910
|
|
|
1627
1911
|
const solSignMessageMethod = new SolSignMessage({
|
|
@@ -1685,15 +1969,6 @@ describe('API compatibility handling', () => {
|
|
|
1685
1969
|
});
|
|
1686
1970
|
});
|
|
1687
1971
|
|
|
1688
|
-
test('includes TON signData in the Protocol V2 protobuf schema', () => {
|
|
1689
|
-
const protocolV2Messages = DataManager.getProtobufMessages('v2Schema') as any;
|
|
1690
|
-
|
|
1691
|
-
expect(protocolV2Messages.nested.TonSignData).toBeDefined();
|
|
1692
|
-
expect(protocolV2Messages.nested.TonSignedData).toBeDefined();
|
|
1693
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignData).toBe(11908);
|
|
1694
|
-
expect(protocolV2Messages.nested.MessageType.values.MessageType_TonSignedData).toBe(11909);
|
|
1695
|
-
});
|
|
1696
|
-
|
|
1697
1972
|
test('uses chunk transfer for large Sui transactions on Protocol V2', async () => {
|
|
1698
1973
|
const rawTx = '0x'.concat('ab'.repeat(5000));
|
|
1699
1974
|
const typedCall = jest.fn(() => ({
|
|
@@ -1715,7 +1990,7 @@ describe('API compatibility handling', () => {
|
|
|
1715
1990
|
method.init();
|
|
1716
1991
|
(method as any).device = stubDevice({
|
|
1717
1992
|
features: {
|
|
1718
|
-
|
|
1993
|
+
deviceType: 'pro2',
|
|
1719
1994
|
},
|
|
1720
1995
|
originalDescriptor: {
|
|
1721
1996
|
protocolType: 'V2',
|
|
@@ -1738,54 +2013,6 @@ describe('API compatibility handling', () => {
|
|
|
1738
2013
|
expect(params.data_initial_chunk).toHaveLength(2048);
|
|
1739
2014
|
});
|
|
1740
2015
|
|
|
1741
|
-
test('accepts string XRP payment amount values', () => {
|
|
1742
|
-
const method = new XrpSignTransaction({
|
|
1743
|
-
id: 1,
|
|
1744
|
-
payload: {
|
|
1745
|
-
method: 'xrpSignTransaction',
|
|
1746
|
-
path: "m/44'/144'/0'/0/0",
|
|
1747
|
-
transaction: {
|
|
1748
|
-
fee: '100000',
|
|
1749
|
-
flags: 2147483648,
|
|
1750
|
-
sequence: 25,
|
|
1751
|
-
maxLedgerVersion: 8820051,
|
|
1752
|
-
payment: {
|
|
1753
|
-
amount: '100000000',
|
|
1754
|
-
destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws',
|
|
1755
|
-
},
|
|
1756
|
-
},
|
|
1757
|
-
},
|
|
1758
|
-
});
|
|
1759
|
-
|
|
1760
|
-
expect(() => method.init()).not.toThrow();
|
|
1761
|
-
expect(method.params.payment?.amount).toBe('100000000');
|
|
1762
|
-
});
|
|
1763
|
-
|
|
1764
|
-
test('accepts Conflux base32 recipient addresses without hex formatting them', () => {
|
|
1765
|
-
const to = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91';
|
|
1766
|
-
const method = new ConfluxSignTransaction({
|
|
1767
|
-
id: 1,
|
|
1768
|
-
payload: {
|
|
1769
|
-
method: 'confluxSignTransaction',
|
|
1770
|
-
path: "m/44'/503'/0'/0/0",
|
|
1771
|
-
transaction: {
|
|
1772
|
-
to,
|
|
1773
|
-
value: '0x0',
|
|
1774
|
-
data: '0x',
|
|
1775
|
-
chainId: 1,
|
|
1776
|
-
nonce: '0x00',
|
|
1777
|
-
epochHeight: '0x00',
|
|
1778
|
-
gasLimit: '0x5208',
|
|
1779
|
-
storageLimit: '0x5208',
|
|
1780
|
-
gasPrice: '0xbebc200',
|
|
1781
|
-
},
|
|
1782
|
-
},
|
|
1783
|
-
});
|
|
1784
|
-
|
|
1785
|
-
expect(() => method.init()).not.toThrow();
|
|
1786
|
-
expect(method.formattedTx?.to).toBe(to);
|
|
1787
|
-
});
|
|
1788
|
-
|
|
1789
2016
|
test('returns a typed unsupported error for Dynex signing on Protocol V2', async () => {
|
|
1790
2017
|
const method = new DnxSignTransaction({
|
|
1791
2018
|
id: 1,
|
|
@@ -1841,7 +2068,41 @@ describe('API compatibility handling', () => {
|
|
|
1841
2068
|
});
|
|
1842
2069
|
|
|
1843
2070
|
describe('Protocol V2 firmware update targets', () => {
|
|
1844
|
-
|
|
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 () => {
|
|
1845
2106
|
const method = new FirmwareUpdateV3({
|
|
1846
2107
|
id: 1,
|
|
1847
2108
|
payload: {
|
|
@@ -1855,25 +2116,26 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1855
2116
|
await expect(method.run()).rejects.toThrow('firmwareUpdateV4');
|
|
1856
2117
|
});
|
|
1857
2118
|
|
|
1858
|
-
test('uses Protocol V2 features after BLE final reconnect without
|
|
2119
|
+
test('uses Protocol V2 features after BLE final reconnect without V1 Initialize', async () => {
|
|
1859
2120
|
const method = new FirmwareUpdateV4({
|
|
1860
2121
|
id: 1,
|
|
1861
2122
|
payload: {
|
|
1862
2123
|
method: 'firmwareUpdateV4',
|
|
2124
|
+
coprocessorBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
1863
2125
|
},
|
|
1864
2126
|
});
|
|
1865
2127
|
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-session' });
|
|
1866
2128
|
const typedCall = jest.fn().mockImplementation((name: string) => {
|
|
1867
|
-
if (name === '
|
|
2129
|
+
if (name === 'DeviceInfoGet') {
|
|
1868
2130
|
return Promise.resolve({
|
|
1869
2131
|
type: 'DeviceInfo',
|
|
1870
2132
|
message: {
|
|
1871
2133
|
fw: {
|
|
1872
|
-
|
|
1873
|
-
|
|
2134
|
+
bootloader: { version: '0.0.0' },
|
|
2135
|
+
application: { version: '0.0.0' },
|
|
1874
2136
|
},
|
|
1875
|
-
|
|
1876
|
-
|
|
2137
|
+
coprocessor: {
|
|
2138
|
+
application: { version: '0.0.0' },
|
|
1877
2139
|
},
|
|
1878
2140
|
},
|
|
1879
2141
|
});
|
|
@@ -1887,7 +2149,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1887
2149
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1888
2150
|
deviceConnector: { acquire },
|
|
1889
2151
|
getCommands: () => commands,
|
|
1890
|
-
|
|
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
|
+
})),
|
|
1891
2159
|
});
|
|
1892
2160
|
|
|
1893
2161
|
const versions = await (method as any).waitForProtocolV2FinalFeatures();
|
|
@@ -1896,13 +2164,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1896
2164
|
// 更新完成判定使用 VERSIONS 请求(含 SE targets),scope 与请求内容一致
|
|
1897
2165
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1898
2166
|
1,
|
|
1899
|
-
'
|
|
2167
|
+
'DeviceInfoGet',
|
|
1900
2168
|
'DeviceInfo',
|
|
1901
2169
|
{
|
|
1902
2170
|
targets: {
|
|
1903
2171
|
hw: true,
|
|
1904
2172
|
fw: true,
|
|
1905
|
-
|
|
2173
|
+
coprocessor: true,
|
|
1906
2174
|
se1: true,
|
|
1907
2175
|
se2: true,
|
|
1908
2176
|
se3: true,
|
|
@@ -1925,7 +2193,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1925
2193
|
});
|
|
1926
2194
|
});
|
|
1927
2195
|
|
|
1928
|
-
test('
|
|
2196
|
+
test('enters Protocol V2 bootloader before upload', async () => {
|
|
1929
2197
|
const method = new FirmwareUpdateV4({
|
|
1930
2198
|
id: 1,
|
|
1931
2199
|
payload: {
|
|
@@ -1938,14 +2206,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1938
2206
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1939
2207
|
features: { capabilities: [] },
|
|
1940
2208
|
});
|
|
1941
|
-
(method as any).
|
|
1942
|
-
(method as any).
|
|
2209
|
+
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
2210
|
+
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
1943
2211
|
{
|
|
1944
|
-
fileName: '
|
|
2212
|
+
fileName: 'coprocessor.bin',
|
|
1945
2213
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2214
|
+
targetId: 5,
|
|
1946
2215
|
},
|
|
1947
2216
|
]);
|
|
1948
|
-
(method as any).prepareBootloaderBinary = jest.fn().mockResolvedValue(null);
|
|
1949
2217
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
1950
2218
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
1951
2219
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
@@ -1959,17 +2227,17 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1959
2227
|
|
|
1960
2228
|
await method.run();
|
|
1961
2229
|
|
|
2230
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
1962
2231
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
1963
|
-
resourceBinary: null,
|
|
1964
2232
|
fwBinaryMap: [
|
|
1965
2233
|
{
|
|
1966
|
-
fileName: '
|
|
2234
|
+
fileName: 'coprocessor.bin',
|
|
1967
2235
|
binary: expect.any(ArrayBuffer),
|
|
2236
|
+
targetId: 5,
|
|
1968
2237
|
},
|
|
1969
2238
|
],
|
|
1970
2239
|
bootloaderBinary: null,
|
|
1971
2240
|
});
|
|
1972
|
-
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
1973
2241
|
});
|
|
1974
2242
|
|
|
1975
2243
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -1979,31 +2247,70 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1979
2247
|
method: 'firmwareUpdateV4',
|
|
1980
2248
|
},
|
|
1981
2249
|
});
|
|
1982
|
-
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
1983
2250
|
(method as any).device = stubDevice({
|
|
1984
2251
|
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
1985
2252
|
features: { bootloader_mode: false, capabilities: [] },
|
|
1986
2253
|
isBootloader: () => false,
|
|
1987
|
-
acquire,
|
|
1988
2254
|
});
|
|
1989
2255
|
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
1990
2256
|
message: 'Device rebooted successfully',
|
|
1991
2257
|
});
|
|
1992
|
-
(method as any).checkDeviceToBootloader = jest.fn(
|
|
1993
|
-
|
|
2258
|
+
(method as any).checkDeviceToBootloader = jest.fn();
|
|
2259
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
2260
|
+
type: 'DeviceInfo',
|
|
2261
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
1994
2262
|
});
|
|
2263
|
+
const reconnectProtocolV2Device = jest.fn().mockImplementation(() => {
|
|
2264
|
+
(method as any).device.isBootloader = () => true;
|
|
2265
|
+
return Promise.resolve();
|
|
2266
|
+
});
|
|
2267
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2268
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
1995
2269
|
method.postTipMessage = jest.fn();
|
|
1996
2270
|
|
|
1997
2271
|
await (method as any).enterProtocolV2BootloaderMode();
|
|
1998
2272
|
|
|
1999
2273
|
expect(method.postTipMessage).toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
2000
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
2274
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Bootloader);
|
|
2001
2275
|
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
2002
|
-
expect((method as any).checkDeviceToBootloader).
|
|
2003
|
-
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);
|
|
2004
2311
|
});
|
|
2005
2312
|
|
|
2006
|
-
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 () => {
|
|
2007
2314
|
const method = new FirmwareUpdateV4({
|
|
2008
2315
|
id: 1,
|
|
2009
2316
|
payload: {
|
|
@@ -2018,7 +2325,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2018
2325
|
await (method as any).exitProtocolV2BootloaderToNormal();
|
|
2019
2326
|
|
|
2020
2327
|
expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
|
|
2021
|
-
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(
|
|
2328
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
|
|
2022
2329
|
});
|
|
2023
2330
|
|
|
2024
2331
|
test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
|
|
@@ -2038,7 +2345,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2038
2345
|
getCommands: () => ({ typedCall }),
|
|
2039
2346
|
});
|
|
2040
2347
|
|
|
2041
|
-
await expect((method as any).protocolV2Reboot(
|
|
2348
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
2042
2349
|
message: 'Device rebooted successfully',
|
|
2043
2350
|
});
|
|
2044
2351
|
});
|
|
@@ -2058,7 +2365,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2058
2365
|
getCommands: () => ({ typedCall }),
|
|
2059
2366
|
});
|
|
2060
2367
|
|
|
2061
|
-
await expect((method as any).protocolV2Reboot(
|
|
2368
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
2062
2369
|
message: 'Device rebooted successfully',
|
|
2063
2370
|
});
|
|
2064
2371
|
});
|
|
@@ -2086,192 +2393,811 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2086
2393
|
|
|
2087
2394
|
await expect(
|
|
2088
2395
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
2089
|
-
targets: [{ target_id:
|
|
2396
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2090
2397
|
})
|
|
2091
2398
|
).resolves.toBeUndefined();
|
|
2092
2399
|
|
|
2093
2400
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2094
2401
|
});
|
|
2095
2402
|
|
|
2096
|
-
test('continues Protocol V2 install polling
|
|
2403
|
+
test('continues Protocol V2 install polling when update request transfer times out', async () => {
|
|
2097
2404
|
const method = new FirmwareUpdateV4({
|
|
2098
2405
|
id: 1,
|
|
2099
2406
|
payload: {
|
|
2100
2407
|
method: 'firmwareUpdateV4',
|
|
2101
2408
|
},
|
|
2102
2409
|
});
|
|
2103
|
-
const typedCall = jest
|
|
2104
|
-
.fn()
|
|
2105
|
-
.mockRejectedValueOnce(
|
|
2106
|
-
new Error(
|
|
2107
|
-
'Device protocol mismatch: expected V2, but device did not respond to expected protocol'
|
|
2108
|
-
)
|
|
2109
|
-
)
|
|
2110
|
-
.mockResolvedValueOnce({
|
|
2111
|
-
type: 'DevFirmwareUpdateStatus',
|
|
2112
|
-
message: {
|
|
2113
|
-
targets: [{ target_id: 2, status: 0 }],
|
|
2114
|
-
},
|
|
2115
|
-
});
|
|
2116
|
-
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2410
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
2117
2411
|
|
|
2118
2412
|
(method as any).device = stubDevice({
|
|
2119
2413
|
getCommands: () => ({ typedCall }),
|
|
2120
2414
|
});
|
|
2121
|
-
|
|
2122
|
-
method.postProgressMessage = jest.fn();
|
|
2415
|
+
method.postTipMessage = jest.fn();
|
|
2123
2416
|
|
|
2124
|
-
await (
|
|
2125
|
-
|
|
2126
|
-
|
|
2417
|
+
await expect(
|
|
2418
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
2419
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2420
|
+
})
|
|
2421
|
+
).resolves.toBeUndefined();
|
|
2127
2422
|
|
|
2128
|
-
expect(
|
|
2129
|
-
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2423
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2130
2424
|
});
|
|
2131
2425
|
|
|
2132
|
-
test('
|
|
2426
|
+
test('continues Protocol V2 install polling through temporary expected V2 probe failures', async () => {
|
|
2133
2427
|
const method = new FirmwareUpdateV4({
|
|
2134
2428
|
id: 1,
|
|
2135
2429
|
payload: {
|
|
2136
2430
|
method: 'firmwareUpdateV4',
|
|
2137
2431
|
},
|
|
2138
2432
|
});
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
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
|
+
});
|
|
2458
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
2142
2459
|
|
|
2143
|
-
|
|
2144
|
-
(
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
).toBe(true);
|
|
2460
|
+
(method as any).device = stubDevice({
|
|
2461
|
+
getCommands: () => ({ typedCall }),
|
|
2462
|
+
});
|
|
2463
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
2464
|
+
method.postProgressMessage = jest.fn();
|
|
2149
2465
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
expectedTargetIds
|
|
2154
|
-
)
|
|
2155
|
-
).toBe(false);
|
|
2156
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2466
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
2467
|
+
{ target_id: 6, path: 'vol1:ble-firmware.bin' },
|
|
2468
|
+
]);
|
|
2157
2469
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2166
|
-
}
|
|
2470
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
2471
|
+
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
2472
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2473
|
+
'DeviceInfoGet',
|
|
2474
|
+
'Ping',
|
|
2475
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2476
|
+
]);
|
|
2167
2477
|
});
|
|
2168
2478
|
|
|
2169
|
-
test('
|
|
2479
|
+
test('treats Protocol V2 normal mode after reconnect as firmware update complete', async () => {
|
|
2170
2480
|
const method = new FirmwareUpdateV4({
|
|
2171
2481
|
id: 1,
|
|
2172
2482
|
payload: {
|
|
2173
2483
|
method: 'firmwareUpdateV4',
|
|
2174
2484
|
},
|
|
2175
2485
|
});
|
|
2176
|
-
|
|
2177
|
-
|
|
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
|
+
]);
|
|
2533
|
+
});
|
|
2534
|
+
|
|
2535
|
+
test('uses SDK decoded enum names for Protocol V2 install polling', () => {
|
|
2536
|
+
const method = new FirmwareUpdateV4({
|
|
2537
|
+
id: 1,
|
|
2538
|
+
payload: {
|
|
2539
|
+
method: 'firmwareUpdateV4',
|
|
2540
|
+
},
|
|
2541
|
+
});
|
|
2542
|
+
method.postProgressMessage = jest.fn();
|
|
2543
|
+
|
|
2544
|
+
const expectedTargetIds = new Set([4]);
|
|
2545
|
+
|
|
2546
|
+
expect(
|
|
2547
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 2 }], expectedTargetIds)
|
|
2548
|
+
).toBe(true);
|
|
2549
|
+
expect(
|
|
2550
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2551
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' }],
|
|
2552
|
+
expectedTargetIds
|
|
2553
|
+
)
|
|
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);
|
|
2567
|
+
|
|
2568
|
+
expect(
|
|
2569
|
+
(method as any).assertProtocolV2TargetStatus([{ target_id: 4, status: 1 }], expectedTargetIds)
|
|
2570
|
+
).toBe(false);
|
|
2571
|
+
expect(
|
|
2572
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
2573
|
+
[{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS' }],
|
|
2574
|
+
expectedTargetIds
|
|
2575
|
+
)
|
|
2576
|
+
).toBe(false);
|
|
2577
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2578
|
+
|
|
2579
|
+
try {
|
|
2580
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
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' }],
|
|
2592
|
+
expectedTargetIds
|
|
2593
|
+
);
|
|
2594
|
+
throw new Error('Expected Protocol V2 failed firmware status to throw');
|
|
2595
|
+
} catch (error: any) {
|
|
2596
|
+
expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
|
|
2597
|
+
}
|
|
2598
|
+
});
|
|
2599
|
+
|
|
2600
|
+
test('passes bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
2601
|
+
const method = new FirmwareUpdateV4({
|
|
2602
|
+
id: 1,
|
|
2603
|
+
payload: {
|
|
2604
|
+
method: 'firmwareUpdateV4',
|
|
2605
|
+
},
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
const writtenPaths: string[] = [];
|
|
2178
2609
|
method.postTipMessage = jest.fn();
|
|
2179
2610
|
method.postProgressMessage = jest.fn();
|
|
2180
2611
|
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
|
|
2181
2612
|
writtenPaths.push(params.filePath);
|
|
2182
2613
|
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
2183
2614
|
});
|
|
2615
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2184
2616
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2185
2617
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2186
2618
|
.fn()
|
|
2187
2619
|
.mockResolvedValue(undefined);
|
|
2188
2620
|
|
|
2189
2621
|
await (method as any).executeProtocolV2Update({
|
|
2190
|
-
// resource 只支持单文件 .bin,整文件一次上传
|
|
2191
|
-
resourceBinary: new Uint8Array([1, 2, 3]).buffer,
|
|
2192
2622
|
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2193
2623
|
fwBinaryMap: [
|
|
2194
2624
|
{
|
|
2195
|
-
fileName: '
|
|
2625
|
+
fileName: 'coprocessor.bin',
|
|
2196
2626
|
binary: new Uint8Array([6]).buffer,
|
|
2627
|
+
targetId: 6,
|
|
2197
2628
|
},
|
|
2198
2629
|
{
|
|
2199
|
-
fileName: '
|
|
2630
|
+
fileName: 'se01.bin',
|
|
2200
2631
|
binary: new Uint8Array([7]).buffer,
|
|
2632
|
+
targetId: 7,
|
|
2201
2633
|
},
|
|
2202
2634
|
{
|
|
2203
|
-
fileName: '
|
|
2635
|
+
fileName: 'application_p1.bin',
|
|
2204
2636
|
binary: new Uint8Array([8]).buffer,
|
|
2637
|
+
targetId: 4,
|
|
2205
2638
|
},
|
|
2206
2639
|
],
|
|
2207
2640
|
});
|
|
2208
2641
|
|
|
2209
2642
|
expect(writtenPaths).toEqual([
|
|
2210
|
-
'
|
|
2211
|
-
'
|
|
2212
|
-
'
|
|
2213
|
-
'
|
|
2214
|
-
'vol1:firmware.bin',
|
|
2643
|
+
'vol0:/bootloader.bin',
|
|
2644
|
+
'vol0:/coprocessor.bin',
|
|
2645
|
+
'vol0:/se01.bin',
|
|
2646
|
+
'vol0:/application_p1.bin',
|
|
2215
2647
|
]);
|
|
2216
|
-
|
|
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);
|
|
2217
2655
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2218
2656
|
targets: [
|
|
2219
|
-
{ target_id:
|
|
2220
|
-
{ target_id:
|
|
2221
|
-
{ target_id:
|
|
2222
|
-
{ target_id:
|
|
2223
|
-
{ 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' },
|
|
2224
2661
|
],
|
|
2225
2662
|
});
|
|
2226
2663
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2227
2664
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2228
2665
|
});
|
|
2229
2666
|
|
|
2230
|
-
test('
|
|
2667
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
2668
|
+
const method = new FirmwareUpdateV4({
|
|
2669
|
+
id: 1,
|
|
2670
|
+
payload: {
|
|
2671
|
+
method: 'firmwareUpdateV4',
|
|
2672
|
+
},
|
|
2673
|
+
});
|
|
2674
|
+
|
|
2675
|
+
method.postTipMessage = jest.fn();
|
|
2676
|
+
method.postProgressMessage = jest.fn();
|
|
2677
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2678
|
+
.fn()
|
|
2679
|
+
.mockImplementation(params =>
|
|
2680
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2681
|
+
);
|
|
2682
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2683
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2684
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2685
|
+
.fn()
|
|
2686
|
+
.mockResolvedValue(undefined);
|
|
2687
|
+
|
|
2688
|
+
await (method as any).executeProtocolV2Update({
|
|
2689
|
+
resourceBundles: [
|
|
2690
|
+
{
|
|
2691
|
+
name: 'images.okpkg',
|
|
2692
|
+
binary: new Uint8Array([1, 2]).buffer,
|
|
2693
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
2694
|
+
},
|
|
2695
|
+
],
|
|
2696
|
+
bootloaderBinary: null,
|
|
2697
|
+
fwBinaryMap: [
|
|
2698
|
+
{
|
|
2699
|
+
fileName: 'application_p1.bin',
|
|
2700
|
+
binary: new Uint8Array([3]).buffer,
|
|
2701
|
+
targetId: 3,
|
|
2702
|
+
},
|
|
2703
|
+
],
|
|
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
|
+
);
|
|
2717
|
+
});
|
|
2718
|
+
|
|
2719
|
+
test('does not request installation when the staged file size does not match', async () => {
|
|
2720
|
+
const method = new FirmwareUpdateV4({
|
|
2721
|
+
id: 1,
|
|
2722
|
+
payload: {
|
|
2723
|
+
method: 'firmwareUpdateV4',
|
|
2724
|
+
},
|
|
2725
|
+
});
|
|
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 () => {
|
|
2231
3173
|
const method = new FirmwareUpdateV4({
|
|
2232
3174
|
id: 1,
|
|
2233
3175
|
payload: {
|
|
2234
3176
|
method: 'firmwareUpdateV4',
|
|
2235
|
-
platform: 'web',
|
|
2236
|
-
romloaderBinary: new Uint8Array([1]).buffer,
|
|
2237
|
-
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2238
|
-
se04Binary: new Uint8Array([3]).buffer,
|
|
2239
3177
|
},
|
|
2240
3178
|
});
|
|
2241
|
-
method.init();
|
|
2242
|
-
|
|
2243
|
-
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2244
|
-
expect(explicit).toEqual([
|
|
2245
|
-
{ fileName: 'romloader.bin', binary: expect.anything(), targetId: 1 },
|
|
2246
|
-
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 0 },
|
|
2247
|
-
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 6 },
|
|
2248
|
-
]);
|
|
2249
3179
|
|
|
2250
3180
|
method.postTipMessage = jest.fn();
|
|
2251
3181
|
method.postProgressMessage = jest.fn();
|
|
2252
|
-
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2256
|
-
);
|
|
2257
|
-
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2258
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2259
|
-
.fn()
|
|
2260
|
-
.mockResolvedValue(undefined);
|
|
3182
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3183
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
3184
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
2261
3185
|
|
|
2262
3186
|
await (method as any).executeProtocolV2Update({
|
|
2263
|
-
|
|
3187
|
+
resourceBundles: [
|
|
3188
|
+
{
|
|
3189
|
+
name: 'images.okpkg',
|
|
3190
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
3191
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3192
|
+
},
|
|
3193
|
+
],
|
|
2264
3194
|
bootloaderBinary: null,
|
|
2265
|
-
fwBinaryMap:
|
|
3195
|
+
fwBinaryMap: [],
|
|
2266
3196
|
});
|
|
2267
3197
|
|
|
2268
|
-
expect((method as any).
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
{ target_id: 0, path: 'vol1:application_p2.bin' },
|
|
2272
|
-
{ target_id: 6, path: 'vol1:se04.bin' },
|
|
2273
|
-
],
|
|
2274
|
-
});
|
|
3198
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
3199
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
3200
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
2275
3201
|
});
|
|
2276
3202
|
|
|
2277
3203
|
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
@@ -2308,10 +3234,65 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2308
3234
|
});
|
|
2309
3235
|
|
|
2310
3236
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2311
|
-
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0,
|
|
2312
|
-
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]);
|
|
2313
3239
|
expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
|
|
2314
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]);
|
|
2315
3296
|
});
|
|
2316
3297
|
|
|
2317
3298
|
test('continues to DeviceFirmwareUpdate when FilesystemFileWrite returns processed chunk length', async () => {
|
|
@@ -2330,9 +3311,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2330
3311
|
},
|
|
2331
3312
|
});
|
|
2332
3313
|
}
|
|
2333
|
-
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') {
|
|
2334
3321
|
return Promise.resolve({ type: 'Success', message: { message: 'ok' } });
|
|
2335
3322
|
}
|
|
3323
|
+
if (name === 'DeviceFirmwareUpdateStatusGet') {
|
|
3324
|
+
return Promise.resolve({
|
|
3325
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3326
|
+
message: {
|
|
3327
|
+
records: [{ target_id: 4, status: 2 }],
|
|
3328
|
+
},
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
2336
3331
|
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2337
3332
|
});
|
|
2338
3333
|
|
|
@@ -2343,23 +3338,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2343
3338
|
method.postTipMessage = jest.fn();
|
|
2344
3339
|
|
|
2345
3340
|
await (method as any).executeProtocolV2Update({
|
|
2346
|
-
resourceBinary: null,
|
|
2347
3341
|
bootloaderBinary: null,
|
|
2348
3342
|
fwBinaryMap: [
|
|
2349
3343
|
{
|
|
2350
3344
|
fileName: 'firmware.bin',
|
|
2351
3345
|
binary: new Uint8Array(4097).buffer,
|
|
3346
|
+
targetId: 4,
|
|
2352
3347
|
},
|
|
2353
3348
|
],
|
|
2354
3349
|
});
|
|
2355
3350
|
|
|
2356
3351
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2357
|
-
'
|
|
2358
|
-
['Success', '
|
|
3352
|
+
'DeviceFirmwareUpdateRequest',
|
|
3353
|
+
['Success', 'DeviceFirmwareUpdateStatus'],
|
|
2359
3354
|
{
|
|
2360
|
-
targets: [{ target_id:
|
|
3355
|
+
targets: [{ target_id: 4, path: 'vol0:/firmware.bin' }],
|
|
2361
3356
|
},
|
|
2362
|
-
expect.
|
|
3357
|
+
expect.objectContaining({
|
|
3358
|
+
timeoutMs: 3 * 60 * 1000,
|
|
3359
|
+
})
|
|
2363
3360
|
);
|
|
2364
3361
|
});
|
|
2365
3362
|
|
|
@@ -2403,6 +3400,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2403
3400
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
2404
3401
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
2405
3402
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
|
|
3403
|
+
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
2406
3404
|
});
|
|
2407
3405
|
|
|
2408
3406
|
test('consumes Protocol V2 install progress before final update success', async () => {
|
|
@@ -2421,18 +3419,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2421
3419
|
method.postTipMessage = jest.fn();
|
|
2422
3420
|
|
|
2423
3421
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2424
|
-
targets: [{ target_id:
|
|
3422
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2425
3423
|
});
|
|
2426
3424
|
|
|
2427
3425
|
const callOptions = typedCall.mock.calls[0][3];
|
|
2428
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
2429
|
-
expect(callOptions.intermediateTypes).toEqual(['
|
|
3426
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
3427
|
+
expect(callOptions.intermediateTypes).toEqual(['DeviceFirmwareUpdateStatus']);
|
|
2430
3428
|
callOptions.onIntermediateResponse({
|
|
2431
|
-
type: '
|
|
2432
|
-
message: { target_id:
|
|
3429
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3430
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2433
3431
|
});
|
|
2434
3432
|
|
|
2435
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(
|
|
3433
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(99, 'installingFirmware');
|
|
2436
3434
|
});
|
|
2437
3435
|
|
|
2438
3436
|
test('accepts Protocol V2 firmware update status as start response', async () => {
|
|
@@ -2443,8 +3441,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2443
3441
|
},
|
|
2444
3442
|
});
|
|
2445
3443
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2446
|
-
type: '
|
|
2447
|
-
message: {
|
|
3444
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3445
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2448
3446
|
});
|
|
2449
3447
|
|
|
2450
3448
|
(method as any).device = stubDevice({
|
|
@@ -2454,29 +3452,29 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2454
3452
|
method.postTipMessage = jest.fn();
|
|
2455
3453
|
|
|
2456
3454
|
await (method as any).protocolV2StartFirmwareUpdate({
|
|
2457
|
-
targets: [{ target_id:
|
|
3455
|
+
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
2458
3456
|
});
|
|
2459
3457
|
|
|
2460
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3458
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2461
3459
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
2462
3460
|
});
|
|
2463
3461
|
});
|
|
2464
3462
|
|
|
2465
3463
|
describe('Protocol V2 firmware update method', () => {
|
|
2466
|
-
test('returns
|
|
3464
|
+
test('returns DeviceFirmwareUpdateStatus from low-level update trigger', async () => {
|
|
2467
3465
|
const method = new DeviceFirmwareUpdate({
|
|
2468
3466
|
id: 1,
|
|
2469
3467
|
payload: {
|
|
2470
3468
|
method: 'deviceFirmwareUpdate',
|
|
2471
|
-
targetId:
|
|
3469
|
+
targetId: 4,
|
|
2472
3470
|
path: 'vol0:firmware.bin',
|
|
2473
3471
|
},
|
|
2474
3472
|
});
|
|
2475
3473
|
method.init();
|
|
2476
3474
|
|
|
2477
3475
|
const typedCall = jest.fn().mockResolvedValue({
|
|
2478
|
-
type: '
|
|
2479
|
-
message: {
|
|
3476
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
3477
|
+
message: { records: [{ target_id: 4, status: 1 }] },
|
|
2480
3478
|
});
|
|
2481
3479
|
|
|
2482
3480
|
(method as any).device = stubDevice({
|
|
@@ -2484,11 +3482,11 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2484
3482
|
});
|
|
2485
3483
|
|
|
2486
3484
|
await expect(method.run()).resolves.toEqual({
|
|
2487
|
-
|
|
3485
|
+
records: [{ target_id: 4, status: 1 }],
|
|
2488
3486
|
});
|
|
2489
|
-
expect(typedCall.mock.calls[0][1]).toEqual(['Success', '
|
|
3487
|
+
expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
|
|
2490
3488
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2491
|
-
targets: [{ target_id:
|
|
3489
|
+
targets: [{ target_id: 4, path: 'vol0:firmware.bin' }],
|
|
2492
3490
|
});
|
|
2493
3491
|
});
|
|
2494
3492
|
|
|
@@ -2497,7 +3495,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2497
3495
|
id: 1,
|
|
2498
3496
|
payload: {
|
|
2499
3497
|
method: 'deviceFirmwareUpdate',
|
|
2500
|
-
targetId:
|
|
3498
|
+
targetId: 4,
|
|
2501
3499
|
path: 'vol0:firmware.bin',
|
|
2502
3500
|
},
|
|
2503
3501
|
});
|
|
@@ -2542,6 +3540,30 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2542
3540
|
expect(typedCall).not.toHaveBeenCalled();
|
|
2543
3541
|
});
|
|
2544
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
|
+
|
|
2545
3567
|
test('accepts targetId alias inside firmware targets', async () => {
|
|
2546
3568
|
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
2547
3569
|
const method = new DeviceFirmwareUpdate({
|
|
@@ -2551,8 +3573,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2551
3573
|
targets: [
|
|
2552
3574
|
{
|
|
2553
3575
|
target_id: undefined,
|
|
2554
|
-
targetId:
|
|
2555
|
-
path: 'vol0:
|
|
3576
|
+
targetId: 1,
|
|
3577
|
+
path: 'vol0:resource.crate.okpkg',
|
|
2556
3578
|
},
|
|
2557
3579
|
],
|
|
2558
3580
|
} as any,
|
|
@@ -2564,31 +3586,71 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
2564
3586
|
|
|
2565
3587
|
await method.run();
|
|
2566
3588
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2567
|
-
targets: [{ target_id:
|
|
3589
|
+
targets: [{ target_id: 1, path: 'vol0:resource.crate.okpkg' }],
|
|
2568
3590
|
});
|
|
2569
3591
|
});
|
|
2570
|
-
});
|
|
2571
3592
|
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
const
|
|
2575
|
-
const method = new DevReboot({
|
|
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({
|
|
2576
3596
|
id: 1,
|
|
2577
3597
|
payload: {
|
|
2578
|
-
method: '
|
|
2579
|
-
|
|
3598
|
+
method: 'deviceFirmwareUpdate',
|
|
3599
|
+
targetId: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
3600
|
+
path: 'vol0:application_p1.bin',
|
|
2580
3601
|
},
|
|
2581
3602
|
});
|
|
2582
3603
|
method.init();
|
|
2583
|
-
(method as any).device = stubDevice({
|
|
3604
|
+
(method as any).device = stubDevice({
|
|
3605
|
+
commands: { typedCall },
|
|
3606
|
+
});
|
|
2584
3607
|
|
|
2585
3608
|
await method.run();
|
|
3609
|
+
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3610
|
+
targets: [{ target_id: 4, path: 'vol0:application_p1.bin' }],
|
|
3611
|
+
});
|
|
3612
|
+
});
|
|
2586
3613
|
|
|
2587
|
-
|
|
2588
|
-
|
|
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
|
+
},
|
|
2589
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
|
+
);
|
|
2590
3650
|
});
|
|
3651
|
+
});
|
|
2591
3652
|
|
|
3653
|
+
describe('Protocol V2 reboot methods', () => {
|
|
2592
3654
|
test('sends DeviceReboot from deviceReboot', async () => {
|
|
2593
3655
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
2594
3656
|
const method = new DeviceReboot({
|
|
@@ -2609,12 +3671,97 @@ describe('Protocol V2 reboot methods', () => {
|
|
|
2609
3671
|
});
|
|
2610
3672
|
});
|
|
2611
3673
|
|
|
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({
|
|
3692
|
+
id: 1,
|
|
3693
|
+
payload: {
|
|
3694
|
+
method: 'deviceFactoryInfoSet',
|
|
3695
|
+
serial_number: 'PR2SERIAL',
|
|
3696
|
+
burn_in_completed: true,
|
|
3697
|
+
},
|
|
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
|
+
});
|
|
3733
|
+
method.init();
|
|
3734
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3735
|
+
|
|
3736
|
+
await method.run();
|
|
3737
|
+
|
|
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,
|
|
3755
|
+
});
|
|
3756
|
+
});
|
|
3757
|
+
});
|
|
3758
|
+
|
|
2612
3759
|
describe('Protocol V2 raw device info method', () => {
|
|
2613
3760
|
const buildMethod = (payload: Record<string, unknown> = {}) => {
|
|
2614
|
-
const method = new
|
|
3761
|
+
const method = new DeviceInfoGet({
|
|
2615
3762
|
id: 1,
|
|
2616
3763
|
payload: {
|
|
2617
|
-
method: '
|
|
3764
|
+
method: 'deviceInfoGet',
|
|
2618
3765
|
...payload,
|
|
2619
3766
|
},
|
|
2620
3767
|
});
|
|
@@ -2624,7 +3771,7 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2624
3771
|
|
|
2625
3772
|
test('passes requested targets/types through and returns the raw DeviceInfo message', async () => {
|
|
2626
3773
|
const method = buildMethod({
|
|
2627
|
-
targets: { hw: true, se1: true
|
|
3774
|
+
targets: { hw: true, se1: true },
|
|
2628
3775
|
types: { version: true, hash: true },
|
|
2629
3776
|
});
|
|
2630
3777
|
const typedCall = jest.fn().mockResolvedValue({
|
|
@@ -2640,9 +3787,8 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2640
3787
|
protocol_version: 1,
|
|
2641
3788
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2642
3789
|
});
|
|
2643
|
-
// 未知 key(junk)被过滤,已选 key 原样透传,不构建 DeviceProfile
|
|
2644
3790
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2645
|
-
'
|
|
3791
|
+
'DeviceInfoGet',
|
|
2646
3792
|
'DeviceInfo',
|
|
2647
3793
|
{
|
|
2648
3794
|
targets: { hw: true, se1: true },
|
|
@@ -2652,6 +3798,20 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2652
3798
|
);
|
|
2653
3799
|
});
|
|
2654
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
|
+
|
|
2655
3815
|
test('defaults to the basic targets/types when none are given', async () => {
|
|
2656
3816
|
const method = buildMethod();
|
|
2657
3817
|
const typedCall = jest.fn().mockResolvedValue({ type: 'DeviceInfo', message: {} });
|
|
@@ -2663,10 +3823,10 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2663
3823
|
await method.run();
|
|
2664
3824
|
|
|
2665
3825
|
expect(typedCall).toHaveBeenCalledWith(
|
|
2666
|
-
'
|
|
3826
|
+
'DeviceInfoGet',
|
|
2667
3827
|
'DeviceInfo',
|
|
2668
3828
|
{
|
|
2669
|
-
targets: { hw: true, fw: true,
|
|
3829
|
+
targets: { hw: true, fw: true, coprocessor: true, status: true },
|
|
2670
3830
|
types: { version: true, specific: true },
|
|
2671
3831
|
},
|
|
2672
3832
|
{ timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }
|
|
@@ -2686,48 +3846,6 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
2686
3846
|
});
|
|
2687
3847
|
});
|
|
2688
3848
|
|
|
2689
|
-
describe('Protocol V2 onboarding status method', () => {
|
|
2690
|
-
test('returns OnboardingStatus from low-level status query', async () => {
|
|
2691
|
-
const method = new DeviceGetOnboardingStatus({
|
|
2692
|
-
id: 1,
|
|
2693
|
-
payload: {
|
|
2694
|
-
method: 'deviceGetOnboardingStatus',
|
|
2695
|
-
},
|
|
2696
|
-
});
|
|
2697
|
-
method.init();
|
|
2698
|
-
|
|
2699
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
2700
|
-
type: 'OnboardingStatus',
|
|
2701
|
-
message: {
|
|
2702
|
-
step: 3,
|
|
2703
|
-
setup: {
|
|
2704
|
-
restore: {
|
|
2705
|
-
mnemonic: true,
|
|
2706
|
-
},
|
|
2707
|
-
},
|
|
2708
|
-
detail_code: 7,
|
|
2709
|
-
detail_str: 'Recovery Phrase',
|
|
2710
|
-
},
|
|
2711
|
-
});
|
|
2712
|
-
|
|
2713
|
-
(method as any).device = stubDevice({
|
|
2714
|
-
commands: { typedCall },
|
|
2715
|
-
});
|
|
2716
|
-
|
|
2717
|
-
await expect(method.run()).resolves.toEqual({
|
|
2718
|
-
step: 3,
|
|
2719
|
-
setup: {
|
|
2720
|
-
restore: {
|
|
2721
|
-
mnemonic: true,
|
|
2722
|
-
},
|
|
2723
|
-
},
|
|
2724
|
-
detail_code: 7,
|
|
2725
|
-
detail_str: 'Recovery Phrase',
|
|
2726
|
-
});
|
|
2727
|
-
expect(typedCall).toHaveBeenCalledWith('GetOnboardingStatus', 'OnboardingStatus', {});
|
|
2728
|
-
});
|
|
2729
|
-
});
|
|
2730
|
-
|
|
2731
3849
|
describe('Protocol V2 file write method', () => {
|
|
2732
3850
|
test('rejects invalid write parameters before transport call', () => {
|
|
2733
3851
|
expect(() => {
|
|
@@ -2777,17 +3895,22 @@ describe('Protocol V2 file write method', () => {
|
|
|
2777
3895
|
method.init();
|
|
2778
3896
|
await method.run();
|
|
2779
3897
|
|
|
2780
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
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,
|
|
2786
3911
|
},
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
ui_percentage: 99,
|
|
2790
|
-
}, { timeoutMs: undefined });
|
|
3912
|
+
{ timeoutMs: undefined }
|
|
3913
|
+
);
|
|
2791
3914
|
expect(method.postMessage).toHaveBeenCalledWith({
|
|
2792
3915
|
event: 'UI_EVENT',
|
|
2793
3916
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
@@ -2820,28 +3943,40 @@ describe('Protocol V2 file write method', () => {
|
|
|
2820
3943
|
const result = await method.run();
|
|
2821
3944
|
|
|
2822
3945
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
2823
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
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
|
+
);
|
|
2845
3980
|
expect(result).toMatchObject({
|
|
2846
3981
|
path: 'vol1:test.bin',
|
|
2847
3982
|
processed_byte: 4097,
|
|
@@ -2851,8 +3986,8 @@ describe('Protocol V2 file write method', () => {
|
|
|
2851
3986
|
event: 'UI_EVENT',
|
|
2852
3987
|
type: UI_REQUEST.DEVICE_PROGRESS,
|
|
2853
3988
|
payload: expect.objectContaining({
|
|
2854
|
-
progress:
|
|
2855
|
-
transferredBytes:
|
|
3989
|
+
progress: 97,
|
|
3990
|
+
transferredBytes: 4000,
|
|
2856
3991
|
totalBytes: 4097,
|
|
2857
3992
|
elapsedMs: expect.any(Number),
|
|
2858
3993
|
}),
|
|
@@ -2901,6 +4036,36 @@ describe('Protocol V2 file write method', () => {
|
|
|
2901
4036
|
});
|
|
2902
4037
|
|
|
2903
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
|
+
|
|
2904
4069
|
test('rejects invalid read and directory parameters before transport call', () => {
|
|
2905
4070
|
expect(() => {
|
|
2906
4071
|
const method = new FileRead({
|
|
@@ -2977,7 +4142,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2977
4142
|
total_size: 0,
|
|
2978
4143
|
},
|
|
2979
4144
|
chunk_len: 64,
|
|
2980
|
-
ui_percentage:
|
|
4145
|
+
ui_percentage: 0,
|
|
2981
4146
|
});
|
|
2982
4147
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'FilesystemFileRead', 'FilesystemFile', {
|
|
2983
4148
|
file: {
|
|
@@ -2986,7 +4151,7 @@ describe('Protocol V2 file read method', () => {
|
|
|
2986
4151
|
total_size: 0,
|
|
2987
4152
|
},
|
|
2988
4153
|
chunk_len: 1,
|
|
2989
|
-
ui_percentage:
|
|
4154
|
+
ui_percentage: 100,
|
|
2990
4155
|
});
|
|
2991
4156
|
expect(result.data.byteLength).toBe(65);
|
|
2992
4157
|
expect(result.data[0]).toBe(1);
|