@onekeyfe/hd-core 1.2.0-alpha.75 → 1.2.0-alpha.76
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 +45 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +64 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/protocol-v2.test.ts +405 -100
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +25 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- 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 +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +179 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3079 -1062
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +68 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +9 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +57 -6
- package/src/api/FirmwareUpdate.ts +13 -1
- package/src/api/FirmwareUpdateV2.ts +296 -121
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +881 -349
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/data-manager/DataManager.ts +62 -54
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +6 -1
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -2
- package/src/lowLevelInject.ts +5 -1
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +75 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -0
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesCompat.ts +6 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
|
|
4
|
+
import {
|
|
5
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
6
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
7
|
+
prepareFirmwareUpdatePlan,
|
|
8
|
+
validateFirmwareUpdatePreparedPlan,
|
|
9
|
+
} from '../../src/api/firmware/FirmwareUpdatePreparedPlan';
|
|
10
|
+
|
|
11
|
+
import type { Features } from '../../src/types';
|
|
12
|
+
|
|
13
|
+
const buildPlan = () =>
|
|
14
|
+
buildFirmwareUpdatePlan({
|
|
15
|
+
features: {
|
|
16
|
+
deviceType: EDeviceType.Classic1s,
|
|
17
|
+
serialNo: 'classic1s-device-id',
|
|
18
|
+
firmwareVersion: '1.0.0',
|
|
19
|
+
bootloaderVersion: '1.0.0',
|
|
20
|
+
} as Features,
|
|
21
|
+
firmwareType: EFirmwareType.Universal,
|
|
22
|
+
platform: 'native',
|
|
23
|
+
firmware: {
|
|
24
|
+
status: 'outdated',
|
|
25
|
+
release: {
|
|
26
|
+
url: 'https://firmware.onekey.so/classic/firmware.bin',
|
|
27
|
+
expectedSize: 4,
|
|
28
|
+
fingerprint: 'a'.repeat(64),
|
|
29
|
+
version: [3, 0, 0],
|
|
30
|
+
resource: 'https://firmware.onekey.so/classic/resource.zip',
|
|
31
|
+
resourceExpectedSize: 8,
|
|
32
|
+
resourceFingerprint: 'b'.repeat(64),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
ble: {
|
|
36
|
+
status: 'valid',
|
|
37
|
+
release: undefined,
|
|
38
|
+
},
|
|
39
|
+
bootloader: {
|
|
40
|
+
status: 'valid',
|
|
41
|
+
release: undefined,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const artifact = (sha256: string, size: number) => ({
|
|
46
|
+
artifactRef: `fw:${sha256}`,
|
|
47
|
+
size,
|
|
48
|
+
sha256,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('FirmwareUpdatePreparedPlan', () => {
|
|
52
|
+
test('binds all receipts into a URL-free, network-forbidden plan', () => {
|
|
53
|
+
const plan = buildPlan();
|
|
54
|
+
const prepared = prepareFirmwareUpdatePlan({
|
|
55
|
+
plan,
|
|
56
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
57
|
+
artifacts: [
|
|
58
|
+
{
|
|
59
|
+
artifactId: 'firmware',
|
|
60
|
+
artifact: artifact('a'.repeat(64), 4),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
artifactId: 'resource',
|
|
64
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
65
|
+
materializedEntries: [
|
|
66
|
+
{
|
|
67
|
+
entryName: 'resource/index.bin',
|
|
68
|
+
artifact: artifact('c'.repeat(64), 2),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(prepared.networkPolicy).toBe('forbid');
|
|
76
|
+
expect(prepared.planDigest).toBe(plan.planDigest);
|
|
77
|
+
expect(prepared.preparedPlanDigest).toMatch(/^[a-f0-9]{64}$/u);
|
|
78
|
+
expect(JSON.stringify(prepared)).not.toContain('https://');
|
|
79
|
+
expect(validateFirmwareUpdatePreparedPlan(prepared)).toEqual(prepared);
|
|
80
|
+
expect(
|
|
81
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
82
|
+
preparedPlan: prepared,
|
|
83
|
+
deviceIdentity: plan.deviceIdentity,
|
|
84
|
+
})
|
|
85
|
+
).toEqual(prepared);
|
|
86
|
+
expect(() =>
|
|
87
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
88
|
+
preparedPlan: prepared,
|
|
89
|
+
deviceIdentity: 'different-device-id',
|
|
90
|
+
})
|
|
91
|
+
).toThrow();
|
|
92
|
+
expect(
|
|
93
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
94
|
+
preparedPlan: prepared,
|
|
95
|
+
executor: 'v2',
|
|
96
|
+
platform: 'native',
|
|
97
|
+
scopeTargets: ['firmware', 'resource'],
|
|
98
|
+
bindings: [
|
|
99
|
+
{
|
|
100
|
+
target: 'firmware',
|
|
101
|
+
artifact: artifact('a'.repeat(64), 4),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
target: 'resource',
|
|
105
|
+
entryName: 'resource/index.bin',
|
|
106
|
+
artifact: artifact('c'.repeat(64), 2),
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
).toEqual(prepared);
|
|
111
|
+
expect(() =>
|
|
112
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
113
|
+
preparedPlan: prepared,
|
|
114
|
+
executor: 'v2',
|
|
115
|
+
platform: 'native',
|
|
116
|
+
scopeTargets: ['firmware', 'resource'],
|
|
117
|
+
bindings: [
|
|
118
|
+
{
|
|
119
|
+
target: 'firmware',
|
|
120
|
+
artifact: artifact('a'.repeat(64), 4),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
target: 'resource',
|
|
124
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
).toThrow('artifact binding is invalid');
|
|
129
|
+
expect(() =>
|
|
130
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
131
|
+
preparedPlan: prepared,
|
|
132
|
+
executor: 'v2',
|
|
133
|
+
platform: 'native',
|
|
134
|
+
scopeTargets: ['firmware'],
|
|
135
|
+
bindings: [
|
|
136
|
+
{
|
|
137
|
+
target: 'firmware',
|
|
138
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
})
|
|
142
|
+
).toThrow('artifact binding is invalid');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('rejects receipt drift and a tampered update plan digest', () => {
|
|
146
|
+
const plan = buildPlan();
|
|
147
|
+
expect(() =>
|
|
148
|
+
prepareFirmwareUpdatePlan({
|
|
149
|
+
plan,
|
|
150
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
151
|
+
artifacts: [
|
|
152
|
+
{
|
|
153
|
+
artifactId: 'firmware',
|
|
154
|
+
artifact: artifact('d'.repeat(64), 4),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
artifactId: 'resource',
|
|
158
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
159
|
+
materializedEntries: [
|
|
160
|
+
{
|
|
161
|
+
entryName: 'resource/index.bin',
|
|
162
|
+
artifact: artifact('c'.repeat(64), 2),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
})
|
|
168
|
+
).toThrow('receipt does not match');
|
|
169
|
+
|
|
170
|
+
expect(() =>
|
|
171
|
+
prepareFirmwareUpdatePlan({
|
|
172
|
+
plan: { ...plan, planDigest: 'f'.repeat(64) },
|
|
173
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
174
|
+
artifacts: [],
|
|
175
|
+
})
|
|
176
|
+
).toThrow('plan digest is invalid');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('projects portable archive paths to unique device resource names', () => {
|
|
180
|
+
const plan = buildPlan();
|
|
181
|
+
const buildArtifacts = (entryNames: string[]) => [
|
|
182
|
+
{
|
|
183
|
+
artifactId: 'firmware',
|
|
184
|
+
artifact: artifact('a'.repeat(64), 4),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
artifactId: 'resource',
|
|
188
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
189
|
+
materializedEntries: entryNames.map((entryName, index) => ({
|
|
190
|
+
entryName,
|
|
191
|
+
artifact: artifact(String(index + 1).repeat(64), 2),
|
|
192
|
+
})),
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
expect(() =>
|
|
197
|
+
prepareFirmwareUpdatePlan({
|
|
198
|
+
plan,
|
|
199
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
200
|
+
artifacts: buildArtifacts(['icons/index.bin', 'fonts/index.bin']),
|
|
201
|
+
})
|
|
202
|
+
).toThrow('duplicate entry names');
|
|
203
|
+
expect(() =>
|
|
204
|
+
prepareFirmwareUpdatePlan({
|
|
205
|
+
plan,
|
|
206
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
207
|
+
artifacts: buildArtifacts(['../index.bin']),
|
|
208
|
+
})
|
|
209
|
+
).toThrow('entry name is invalid');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('rejects unknown fields even when a caller presents a plan-shaped object', () => {
|
|
213
|
+
const plan = buildPlan();
|
|
214
|
+
expect(() =>
|
|
215
|
+
prepareFirmwareUpdatePlan({
|
|
216
|
+
plan: {
|
|
217
|
+
...plan,
|
|
218
|
+
artifacts: [
|
|
219
|
+
{
|
|
220
|
+
...plan.artifacts[0],
|
|
221
|
+
unexpectedUrl: 'https://attacker.invalid/payload',
|
|
222
|
+
},
|
|
223
|
+
plan.artifacts[1],
|
|
224
|
+
],
|
|
225
|
+
} as typeof plan,
|
|
226
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
227
|
+
artifacts: [],
|
|
228
|
+
})
|
|
229
|
+
).toThrow('invalid field set');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import FirmwareUpdateV2 from '../../src/api/FirmwareUpdateV2';
|
|
4
4
|
import { getBinary } from '../../src/api/firmware/getBinary';
|
|
5
|
-
import {
|
|
5
|
+
import { uploadFirmwareFromSource } from '../../src/api/firmware/uploadFirmware';
|
|
6
6
|
import * as utils from '../../src/utils';
|
|
7
7
|
|
|
8
8
|
jest.mock('../../src/data/config', () => ({
|
|
@@ -18,7 +18,8 @@ jest.mock('../../src/api/firmware/getBinary', () => ({
|
|
|
18
18
|
|
|
19
19
|
jest.mock('../../src/api/firmware/uploadFirmware', () => ({
|
|
20
20
|
updateResources: jest.fn(),
|
|
21
|
-
|
|
21
|
+
updateResourcesFromSources: jest.fn(),
|
|
22
|
+
uploadFirmwareFromSource: jest.fn(),
|
|
22
23
|
}));
|
|
23
24
|
|
|
24
25
|
jest.mock('../../src/device/DevicePool', () => ({
|
|
@@ -29,7 +30,9 @@ jest.mock('../../src/device/DevicePool', () => ({
|
|
|
29
30
|
}));
|
|
30
31
|
|
|
31
32
|
const mockGetBinary = getBinary as jest.MockedFunction<typeof getBinary>;
|
|
32
|
-
const
|
|
33
|
+
const mockUploadFirmwareFromSource = uploadFirmwareFromSource as jest.MockedFunction<
|
|
34
|
+
typeof uploadFirmwareFromSource
|
|
35
|
+
>;
|
|
33
36
|
|
|
34
37
|
type DeviceType = 'CLASSIC1S' | 'PURE';
|
|
35
38
|
|
|
@@ -175,7 +178,26 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
175
178
|
expect(mockGetBinary).toHaveBeenCalledTimes(1);
|
|
176
179
|
expectNoFirmwareMutationCalls(typedCall);
|
|
177
180
|
expect(acquire).not.toHaveBeenCalled();
|
|
178
|
-
expect(
|
|
181
|
+
expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('preserves structured preparation errors before any firmware mutation', async () => {
|
|
185
|
+
const preparationError = ERRORS.TypedError(
|
|
186
|
+
HardwareErrorCode.RuntimeError,
|
|
187
|
+
'Firmware must be prepared by the external firmware host',
|
|
188
|
+
{
|
|
189
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
190
|
+
artifactName: 'firmware',
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
mockGetBinary.mockRejectedValue(preparationError);
|
|
194
|
+
const { method, typedCall, acquire } = createMethod();
|
|
195
|
+
|
|
196
|
+
await expect(method.run()).rejects.toBe(preparationError);
|
|
197
|
+
|
|
198
|
+
expectNoFirmwareMutationCalls(typedCall);
|
|
199
|
+
expect(acquire).not.toHaveBeenCalled();
|
|
200
|
+
expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
|
|
179
201
|
});
|
|
180
202
|
|
|
181
203
|
it.each([
|
|
@@ -216,9 +238,11 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
216
238
|
binary: firmwareBinary,
|
|
217
239
|
} as Awaited<ReturnType<typeof getBinary>>);
|
|
218
240
|
});
|
|
219
|
-
|
|
241
|
+
mockUploadFirmwareFromSource.mockImplementation(() => {
|
|
220
242
|
callOrder.push('upload');
|
|
221
|
-
return Promise.resolve(
|
|
243
|
+
return Promise.resolve(
|
|
244
|
+
expectedResponse as Awaited<ReturnType<typeof uploadFirmwareFromSource>>
|
|
245
|
+
);
|
|
222
246
|
});
|
|
223
247
|
const { method, typedCall } = createMethod({
|
|
224
248
|
updateType,
|
|
@@ -248,15 +272,15 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
248
272
|
},
|
|
249
273
|
})
|
|
250
274
|
);
|
|
251
|
-
expect(
|
|
275
|
+
expect(mockUploadFirmwareFromSource).toHaveBeenCalledWith(
|
|
252
276
|
updateType,
|
|
253
277
|
expect.any(Function),
|
|
254
278
|
expect.any(Function),
|
|
255
279
|
method.device,
|
|
256
|
-
{
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
size: firmwareBinary.byteLength,
|
|
282
|
+
}),
|
|
283
|
+
true,
|
|
260
284
|
isUpdateBootloader
|
|
261
285
|
);
|
|
262
286
|
}
|
|
@@ -271,11 +295,11 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
271
295
|
binary: firmwareBinary,
|
|
272
296
|
} as Awaited<ReturnType<typeof getBinary>>);
|
|
273
297
|
});
|
|
274
|
-
|
|
298
|
+
mockUploadFirmwareFromSource.mockImplementation(() => {
|
|
275
299
|
callOrder.push('upload');
|
|
276
300
|
return Promise.resolve({
|
|
277
301
|
success: true,
|
|
278
|
-
} as Awaited<ReturnType<typeof
|
|
302
|
+
} as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
|
|
279
303
|
});
|
|
280
304
|
const { method, typedCall, acquire } = createMethod({
|
|
281
305
|
bootloaderMode: true,
|
|
@@ -300,9 +324,9 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
300
324
|
resolveDownload = resolve;
|
|
301
325
|
})
|
|
302
326
|
);
|
|
303
|
-
|
|
327
|
+
mockUploadFirmwareFromSource.mockResolvedValue({
|
|
304
328
|
success: true,
|
|
305
|
-
} as Awaited<ReturnType<typeof
|
|
329
|
+
} as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
|
|
306
330
|
const { method, typedCall } = createMethod();
|
|
307
331
|
|
|
308
332
|
const runPromise = method.run();
|
|
@@ -333,7 +357,7 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
333
357
|
|
|
334
358
|
expectNoFirmwareMutationCalls(typedCall);
|
|
335
359
|
expect(acquire).not.toHaveBeenCalled();
|
|
336
|
-
expect(
|
|
360
|
+
expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
|
|
337
361
|
});
|
|
338
362
|
|
|
339
363
|
it.each([
|
|
@@ -342,23 +366,23 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
342
366
|
])(
|
|
343
367
|
'keeps the non-empty %s binary overload and never downloads it again',
|
|
344
368
|
async (_kind, binary) => {
|
|
345
|
-
|
|
369
|
+
mockUploadFirmwareFromSource.mockResolvedValue({
|
|
346
370
|
success: true,
|
|
347
|
-
} as Awaited<ReturnType<typeof
|
|
371
|
+
} as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
|
|
348
372
|
const { method } = createMethod({ binary });
|
|
349
373
|
|
|
350
374
|
await method.run();
|
|
351
375
|
|
|
352
376
|
expect(mockGetBinary).not.toHaveBeenCalled();
|
|
353
|
-
expect(
|
|
377
|
+
expect(mockUploadFirmwareFromSource).toHaveBeenCalledWith(
|
|
354
378
|
'firmware',
|
|
355
379
|
expect.any(Function),
|
|
356
380
|
expect.any(Function),
|
|
357
381
|
method.device,
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
382
|
+
expect.objectContaining({
|
|
383
|
+
size: binary.byteLength,
|
|
384
|
+
}),
|
|
385
|
+
true,
|
|
362
386
|
undefined
|
|
363
387
|
);
|
|
364
388
|
}
|
|
@@ -369,17 +393,21 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
369
393
|
['DataView', new DataView(new Uint8Array([9, 3, 4, 9]).buffer, 1, 2), [3, 4]],
|
|
370
394
|
['custom Buffer', createCustomBuffer(4), [1, 2, 3, 4]],
|
|
371
395
|
])('normalizes a non-empty %s before rebooting', async (_kind, binary, expectedBytes) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
let uploadedBytes: number[] = [];
|
|
397
|
+
mockUploadFirmwareFromSource.mockImplementation(
|
|
398
|
+
async (_updateType, _typedCall, _postMessage, _device, source) => {
|
|
399
|
+
uploadedBytes = Array.from(new Uint8Array(await source.readAt(0, source.size)));
|
|
400
|
+
return {
|
|
401
|
+
success: true,
|
|
402
|
+
} as Awaited<ReturnType<typeof uploadFirmwareFromSource>>;
|
|
403
|
+
}
|
|
404
|
+
);
|
|
375
405
|
const { method } = createMethod({ binary });
|
|
376
406
|
|
|
377
407
|
await method.run();
|
|
378
408
|
|
|
379
409
|
expect(mockGetBinary).not.toHaveBeenCalled();
|
|
380
|
-
|
|
381
|
-
expect(normalizedBinary).toBeInstanceOf(ArrayBuffer);
|
|
382
|
-
expect(Array.from(new Uint8Array(normalizedBinary))).toEqual(expectedBytes);
|
|
410
|
+
expect(uploadedBytes).toEqual(expectedBytes);
|
|
383
411
|
});
|
|
384
412
|
|
|
385
413
|
it.each([
|
|
@@ -397,6 +425,6 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
|
|
|
397
425
|
expect(mockGetBinary).not.toHaveBeenCalled();
|
|
398
426
|
expectNoFirmwareMutationCalls(typedCall);
|
|
399
427
|
expect(acquire).not.toHaveBeenCalled();
|
|
400
|
-
expect(
|
|
428
|
+
expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
|
|
401
429
|
});
|
|
402
430
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import FirmwareUpdateV3 from '../../src/api/FirmwareUpdateV3';
|
|
2
|
+
import { DevicePool } from '../../src/device/DevicePool';
|
|
3
|
+
|
|
4
|
+
import type { Device } from '../../src/device/Device';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../src/data/config', () => ({
|
|
7
|
+
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
8
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('FirmwareUpdateV3 reconnect', () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('keeps transient USB acquire failures inside the reconnect loop', async () => {
|
|
17
|
+
const acquire = jest.fn().mockResolvedValue(undefined);
|
|
18
|
+
const commands = {
|
|
19
|
+
disposed: true,
|
|
20
|
+
mainId: '',
|
|
21
|
+
};
|
|
22
|
+
const cachedDevice = {} as Device;
|
|
23
|
+
const updateFromCache = jest.fn();
|
|
24
|
+
jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
|
|
25
|
+
devices: {},
|
|
26
|
+
deviceList: [cachedDevice],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const method = new FirmwareUpdateV3({
|
|
30
|
+
id: 1,
|
|
31
|
+
payload: {
|
|
32
|
+
method: 'firmwareUpdateV3',
|
|
33
|
+
connectId: 'usb-path',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
method.isBleReconnect = jest.fn(() => false);
|
|
37
|
+
method.device = {
|
|
38
|
+
originalDescriptor: {
|
|
39
|
+
id: 'usb-path',
|
|
40
|
+
path: 'usb-path',
|
|
41
|
+
protocolType: 'V1',
|
|
42
|
+
},
|
|
43
|
+
deviceConnector: {
|
|
44
|
+
enumerate: jest.fn().mockResolvedValue({
|
|
45
|
+
descriptors: [{ path: 'usb-path' }],
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
updateFromCache,
|
|
49
|
+
acquire,
|
|
50
|
+
commands,
|
|
51
|
+
getCommands: () => commands,
|
|
52
|
+
mainId: 'usb-session',
|
|
53
|
+
} as unknown as Device;
|
|
54
|
+
|
|
55
|
+
await method.waitForDeviceReconnect(1000);
|
|
56
|
+
|
|
57
|
+
expect(updateFromCache).toHaveBeenCalledWith(cachedDevice);
|
|
58
|
+
expect(acquire).toHaveBeenCalledWith(undefined, {
|
|
59
|
+
throwOnRunPromiseError: true,
|
|
60
|
+
});
|
|
61
|
+
expect(commands.disposed).toBe(false);
|
|
62
|
+
expect(commands.mainId).toBe('usb-session');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { uploadFirmwareFromSource } from '../../src/api/firmware/uploadFirmware';
|
|
4
|
+
|
|
5
|
+
import type { Device } from '../../src/device/Device';
|
|
6
|
+
import type { FirmwareByteSource } from '../../src/api/firmware/FirmwareArtifactSource';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../src/data/config', () => ({
|
|
9
|
+
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
10
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('uploadFirmwareFromSource', () => {
|
|
14
|
+
it('serves legacy Touch firmware requests without materializing the complete artifact', async () => {
|
|
15
|
+
const size = 700 * 1024;
|
|
16
|
+
const requestedRanges: Array<{ offset: number; length: number }> = [];
|
|
17
|
+
const source: FirmwareByteSource = {
|
|
18
|
+
size,
|
|
19
|
+
readAt: jest.fn((offset: number, length: number) => {
|
|
20
|
+
requestedRanges.push({ offset, length });
|
|
21
|
+
return Promise.resolve(new ArrayBuffer(length));
|
|
22
|
+
}),
|
|
23
|
+
close: jest.fn(() => Promise.resolve()),
|
|
24
|
+
};
|
|
25
|
+
let nextOffset = 0;
|
|
26
|
+
const requestedLength = 128 * 1024;
|
|
27
|
+
const typedCall = jest.fn((type: string) => {
|
|
28
|
+
if (type === 'FirmwareErase') {
|
|
29
|
+
return Promise.resolve({
|
|
30
|
+
type: 'FirmwareRequest',
|
|
31
|
+
message: {
|
|
32
|
+
offset: nextOffset,
|
|
33
|
+
length: Math.min(requestedLength, size - nextOffset),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (type === 'FirmwareUpload') {
|
|
38
|
+
nextOffset += Math.min(requestedLength, size - nextOffset);
|
|
39
|
+
if (nextOffset < size) {
|
|
40
|
+
return Promise.resolve({
|
|
41
|
+
type: 'FirmwareRequest',
|
|
42
|
+
message: {
|
|
43
|
+
offset: nextOffset,
|
|
44
|
+
length: Math.min(requestedLength, size - nextOffset),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return Promise.resolve({
|
|
49
|
+
type: 'Success',
|
|
50
|
+
message: { message: 'installed' },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return Promise.reject(new Error(`Unexpected command: ${type}`));
|
|
54
|
+
});
|
|
55
|
+
const device = {
|
|
56
|
+
features: undefined,
|
|
57
|
+
emit: jest.fn(),
|
|
58
|
+
getCurrentDeviceType: () => EDeviceType.Touch,
|
|
59
|
+
toMessageObject: () => ({}),
|
|
60
|
+
} as unknown as Device;
|
|
61
|
+
|
|
62
|
+
await expect(
|
|
63
|
+
uploadFirmwareFromSource('firmware', typedCall as never, jest.fn(), device, source)
|
|
64
|
+
).resolves.toEqual({ message: 'installed' });
|
|
65
|
+
|
|
66
|
+
expect(requestedRanges).toHaveLength(Math.ceil(size / requestedLength));
|
|
67
|
+
expect(requestedRanges.every(({ length }) => length <= 256 * 1024)).toBe(true);
|
|
68
|
+
expect(requestedRanges.some(({ length }) => length === size)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|