@onekeyfe/hd-core 1.2.0-alpha.97 → 1.2.0-alpha.99
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__/check-all-firmware-release-protocol-v2.test.ts +411 -86
- package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
- package/__tests__/firmware-memory-host.test.ts +126 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -2
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +573 -0
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +65 -2
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +293 -71
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
- package/__tests__/protocol-v2-firmware-targets.test.ts +16 -0
- package/__tests__/protocol-v2-resources.test.ts +148 -232
- package/__tests__/protocol-v2.test.ts +1326 -510
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +2 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +1 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +11 -9
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +7 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -1
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts +33 -0
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +108 -114
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1825 -1006
- package/dist/protocols/protocol-v2/resources.d.ts +10 -28
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -12
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/settings.d.ts +35 -36
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +113 -80
- package/src/api/CheckBLEFirmwareRelease.ts +37 -5
- package/src/api/CheckBootloaderRelease.ts +35 -3
- package/src/api/CheckFirmwareRelease.ts +35 -7
- package/src/api/FirmwareUpdateV2.ts +231 -138
- package/src/api/FirmwareUpdateV3.ts +101 -56
- package/src/api/FirmwareUpdateV4.ts +936 -408
- package/src/api/UploadPortfolio.ts +18 -0
- package/src/api/device/DeviceUpdateBootloader.ts +37 -11
- package/src/api/firmware/FirmwareHostBinding.ts +26 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +122 -73
- package/src/api/firmware/FirmwareUpdatePlan.ts +295 -122
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +49 -12
- package/src/api/firmware/protocolV2Release.ts +168 -0
- package/src/data-manager/DataManager.ts +35 -19
- package/src/index.ts +10 -0
- package/src/protocols/protocol-v2/resources.ts +157 -341
- package/src/types/api/checkAllFirmwareRelease.ts +27 -12
- package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
- package/src/types/api/checkBootloaderRelease.ts +2 -6
- package/src/types/api/checkFirmwareRelease.ts +2 -13
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/deviceUpdateBootloader.ts +1 -0
- package/src/types/api/export.ts +6 -1
- package/src/types/api/firmwareUpdate.ts +12 -17
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/settings.ts +35 -61
- package/src/utils/deviceFeaturesUtils.ts +2 -2
|
@@ -29,10 +29,12 @@ import { DataManager } from '../data-manager';
|
|
|
29
29
|
import { DEVICE } from '../events';
|
|
30
30
|
import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
31
31
|
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
32
|
+
import { readVerifiedPreparedResourceArchive } from './firmware/FirmwarePreparedResourceArchive';
|
|
32
33
|
import {
|
|
33
34
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
34
35
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
35
|
-
|
|
36
|
+
getFirmwareUpdatePreparedRawArtifact,
|
|
37
|
+
validateFirmwareUpdatePreparedPlan,
|
|
36
38
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
37
39
|
|
|
38
40
|
import type { Features, KnownDevice } from '../types';
|
|
@@ -46,6 +48,7 @@ import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePrep
|
|
|
46
48
|
type Params = {
|
|
47
49
|
binary?: ArrayBuffer;
|
|
48
50
|
artifact?: FirmwareArtifactReference;
|
|
51
|
+
hostBindingGeneration?: number;
|
|
49
52
|
resourceEntries?: Array<{
|
|
50
53
|
entryName: string;
|
|
51
54
|
artifact: FirmwareArtifactReference;
|
|
@@ -159,15 +162,17 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
159
162
|
{ name: 'firmwareType', type: 'string' },
|
|
160
163
|
]);
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
payload.binary !== undefined &&
|
|
165
|
-
'artifact' in payload &&
|
|
166
|
-
payload.artifact !== undefined
|
|
167
|
-
) {
|
|
165
|
+
const hasPreparedPlan = payload.preparedPlan !== undefined;
|
|
166
|
+
if (hasPreparedPlan && (payload.binary !== undefined || payload.version !== undefined)) {
|
|
168
167
|
throw ERRORS.TypedError(
|
|
169
168
|
HardwareErrorCode.CallMethodInvalidParameter,
|
|
170
|
-
'
|
|
169
|
+
'Prepared firmware plans cannot be combined with legacy firmware inputs'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (!hasPreparedPlan && payload.artifact !== undefined) {
|
|
173
|
+
throw ERRORS.TypedError(
|
|
174
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
175
|
+
'Firmware artifacts require a prepared plan'
|
|
171
176
|
);
|
|
172
177
|
}
|
|
173
178
|
|
|
@@ -184,7 +189,7 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
184
189
|
isUpdateBootloader: payload.isUpdateBootloader,
|
|
185
190
|
};
|
|
186
191
|
|
|
187
|
-
if ('version' in payload) {
|
|
192
|
+
if (!hasPreparedPlan && 'version' in payload) {
|
|
188
193
|
this.params = {
|
|
189
194
|
...this.params,
|
|
190
195
|
version: payload.version,
|
|
@@ -192,16 +197,25 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
192
197
|
};
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
if ('binary' in payload) {
|
|
200
|
+
if (!hasPreparedPlan && 'binary' in payload) {
|
|
196
201
|
this.params = {
|
|
197
202
|
...this.params,
|
|
198
203
|
binary: payload.binary,
|
|
199
204
|
};
|
|
200
205
|
}
|
|
201
206
|
|
|
202
|
-
if (
|
|
203
|
-
const
|
|
207
|
+
if (hasPreparedPlan) {
|
|
208
|
+
const preparedPlan = validateFirmwareUpdatePreparedPlan(payload.preparedPlan);
|
|
209
|
+
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
210
|
+
payload.hostBindingGeneration,
|
|
211
|
+
preparedPlan.preparedPlanDigest
|
|
212
|
+
);
|
|
204
213
|
const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
|
|
214
|
+
const plannedArtifact = getFirmwareUpdatePreparedRawArtifact({
|
|
215
|
+
preparedPlan,
|
|
216
|
+
target,
|
|
217
|
+
role: target,
|
|
218
|
+
}).artifact;
|
|
205
219
|
const resourceBindings = (payload.resourceEntries ?? []).map(
|
|
206
220
|
(entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
|
|
207
221
|
target: 'resource' as const,
|
|
@@ -210,18 +224,21 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
210
224
|
})
|
|
211
225
|
);
|
|
212
226
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
213
|
-
preparedPlan
|
|
227
|
+
preparedPlan,
|
|
214
228
|
executor: 'v2',
|
|
215
229
|
platform: payload.platform,
|
|
216
|
-
scopeTargets: [
|
|
217
|
-
|
|
230
|
+
scopeTargets: [
|
|
231
|
+
target,
|
|
232
|
+
...(target === 'firmware' && resourceBindings.length ? (['resource'] as const) : []),
|
|
233
|
+
],
|
|
234
|
+
bindings: [{ target, artifact: payload.artifact ?? plannedArtifact }, ...resourceBindings],
|
|
218
235
|
});
|
|
219
236
|
this.params = {
|
|
220
237
|
...this.params,
|
|
221
|
-
preparedPlan
|
|
222
|
-
artifact:
|
|
223
|
-
resourceEntries: payload.resourceEntries,
|
|
238
|
+
preparedPlan,
|
|
239
|
+
artifact: plannedArtifact,
|
|
224
240
|
artifactReader: hostBinding.artifactReader,
|
|
241
|
+
firmwareType: preparedPlan.firmwareType,
|
|
225
242
|
};
|
|
226
243
|
}
|
|
227
244
|
}
|
|
@@ -269,84 +286,142 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
269
286
|
// check device goto bootloader mode
|
|
270
287
|
let isFirstCheck = true;
|
|
271
288
|
let checkCount = 0;
|
|
272
|
-
|
|
289
|
+
let hasPromptedWebDevice = false;
|
|
290
|
+
let isPromptingWebDevice = false;
|
|
291
|
+
let isFinished = false;
|
|
292
|
+
let intervalTimer: ReturnType<typeof setInterval> | undefined;
|
|
273
293
|
let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
|
|
274
294
|
|
|
275
295
|
const deviceType = this.device?.getCurrentDeviceType();
|
|
276
296
|
const isTouchOrProDevice = deviceType === EDeviceType.Touch || deviceType === EDeviceType.Pro;
|
|
277
297
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
298
|
+
const clearPollingTimers = () => {
|
|
299
|
+
if (intervalTimer !== undefined) {
|
|
300
|
+
clearInterval(intervalTimer);
|
|
301
|
+
intervalTimer = undefined;
|
|
302
|
+
}
|
|
303
|
+
if (timeoutTimer !== undefined) {
|
|
304
|
+
clearTimeout(timeoutTimer);
|
|
305
|
+
timeoutTimer = undefined;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
287
308
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
309
|
+
const checkForBootloader = async (clearActiveTimers: boolean) => {
|
|
310
|
+
const found = await this._checkDeviceInBootloaderMode(
|
|
311
|
+
connectId,
|
|
312
|
+
clearActiveTimers ? intervalTimer : undefined,
|
|
313
|
+
clearActiveTimers ? timeoutTimer : undefined
|
|
314
|
+
);
|
|
315
|
+
if (found) {
|
|
316
|
+
isFinished = true;
|
|
317
|
+
clearPollingTimers();
|
|
318
|
+
}
|
|
319
|
+
return found;
|
|
320
|
+
};
|
|
295
321
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
322
|
+
let startPolling: () => void = () => undefined;
|
|
323
|
+
const pollForBootloader = async () => {
|
|
324
|
+
if (isFinished || isPromptingWebDevice) return;
|
|
325
|
+
checkCount += 1;
|
|
326
|
+
Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isFirstCheck: ', isFirstCheck);
|
|
327
|
+
if (isTouchOrProDevice && isFirstCheck) {
|
|
328
|
+
isFirstCheck = false;
|
|
329
|
+
Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] wait 3000ms');
|
|
330
|
+
await wait(3000);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
checkCount > 4 &&
|
|
335
|
+
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
336
|
+
!this.payload.skipWebDevicePrompt &&
|
|
337
|
+
!hasPromptedWebDevice &&
|
|
338
|
+
!isPromptingWebDevice
|
|
339
|
+
) {
|
|
340
|
+
clearPollingTimers();
|
|
341
|
+
isPromptingWebDevice = true;
|
|
342
|
+
try {
|
|
343
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
|
|
344
|
+
const confirmed = await this._promptDeviceInBootloaderForWebDevice();
|
|
345
|
+
hasPromptedWebDevice = true;
|
|
346
|
+
if (confirmed) {
|
|
347
|
+
await checkForBootloader(false);
|
|
348
|
+
}
|
|
349
|
+
// WebUSB enumeration can still be empty immediately after the chooser
|
|
350
|
+
// resolves. Resume a fresh bounded polling window instead of leaving the
|
|
351
|
+
// deferred check pending forever after the original timers were paused.
|
|
352
|
+
if (!isFinished) {
|
|
353
|
+
startPolling();
|
|
308
354
|
}
|
|
309
|
-
|
|
355
|
+
} catch (e) {
|
|
356
|
+
isFinished = true;
|
|
357
|
+
clearPollingTimers();
|
|
358
|
+
Log.log(
|
|
359
|
+
'FirmwareUpdateV2 [checkDeviceToBootloader] promptDeviceInBootloaderForWebDevice failed: ',
|
|
360
|
+
e
|
|
361
|
+
);
|
|
362
|
+
this.checkPromise?.reject(e);
|
|
363
|
+
} finally {
|
|
364
|
+
isPromptingWebDevice = false;
|
|
310
365
|
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
311
368
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
} catch (e) {
|
|
331
|
-
// ignore error because of device is not connected
|
|
332
|
-
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
333
|
-
} finally {
|
|
334
|
-
bleProbeInFlight = false;
|
|
369
|
+
if (isBleReconnect) {
|
|
370
|
+
if (bleProbeInFlight) return;
|
|
371
|
+
bleProbeInFlight = true;
|
|
372
|
+
try {
|
|
373
|
+
await this.device.deviceConnector?.acquire(
|
|
374
|
+
this.device.originalDescriptor.id,
|
|
375
|
+
null,
|
|
376
|
+
true,
|
|
377
|
+
this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
|
|
378
|
+
);
|
|
379
|
+
// Bound each probe so a request the rebooting device never received
|
|
380
|
+
// frees the slot for the next tick instead of hanging into the
|
|
381
|
+
// 30s reboot budget.
|
|
382
|
+
await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
|
|
383
|
+
if (this.device.isBootloader()) {
|
|
384
|
+
isFinished = true;
|
|
385
|
+
clearPollingTimers();
|
|
386
|
+
this.checkPromise?.resolve(true);
|
|
335
387
|
}
|
|
336
|
-
}
|
|
337
|
-
|
|
388
|
+
} catch (e) {
|
|
389
|
+
// ignore error because of device is not connected
|
|
390
|
+
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
391
|
+
} finally {
|
|
392
|
+
bleProbeInFlight = false;
|
|
338
393
|
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
// check goto bootloader mode timeout and throw error
|
|
344
|
-
timeoutTimer = setTimeout(() => {
|
|
345
|
-
if (this.checkPromise) {
|
|
346
|
-
clearInterval(intervalTimer);
|
|
347
|
-
this.checkPromise.reject(new Error());
|
|
394
|
+
} else {
|
|
395
|
+
await checkForBootloader(true);
|
|
348
396
|
}
|
|
349
|
-
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
startPolling = () => {
|
|
400
|
+
if (isFinished) return;
|
|
401
|
+
clearPollingTimers();
|
|
402
|
+
intervalTimer = setInterval(
|
|
403
|
+
() => {
|
|
404
|
+
pollForBootloader().catch(error => {
|
|
405
|
+
if (isFinished) return;
|
|
406
|
+
isFinished = true;
|
|
407
|
+
clearPollingTimers();
|
|
408
|
+
this.checkPromise?.reject(error);
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
isBleReconnect ? 3000 : 2000
|
|
412
|
+
);
|
|
413
|
+
// Each automatic polling phase is bounded. Time spent in the browser's
|
|
414
|
+
// permission chooser is intentionally excluded from this deadline.
|
|
415
|
+
timeoutTimer = setTimeout(() => {
|
|
416
|
+
if (!isFinished && this.checkPromise) {
|
|
417
|
+
isFinished = true;
|
|
418
|
+
clearPollingTimers();
|
|
419
|
+
this.checkPromise.reject(new Error());
|
|
420
|
+
}
|
|
421
|
+
}, 30000);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
startPolling();
|
|
350
425
|
}
|
|
351
426
|
|
|
352
427
|
private async _checkDeviceInBootloaderMode(
|
|
@@ -531,6 +606,18 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
531
606
|
}
|
|
532
607
|
};
|
|
533
608
|
|
|
609
|
+
const preparedResourceRequested =
|
|
610
|
+
!params.isUpdateBootloader &&
|
|
611
|
+
params.updateType === 'firmware' &&
|
|
612
|
+
(params.preparedPlan?.targetsToUpdate.includes('resource') ?? false);
|
|
613
|
+
if (preparedResourceRequested && device.isBootloader()) {
|
|
614
|
+
throw ERRORS.TypedError(
|
|
615
|
+
HardwareErrorCode.RuntimeError,
|
|
616
|
+
'Prepared firmware resources require the device application mode',
|
|
617
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared', artifactName: 'resource' }
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
534
621
|
if (!device.isBootloader() && features) {
|
|
535
622
|
const serialNo = device.getCurrentSerialNo();
|
|
536
623
|
// should go to bootloader mode manually
|
|
@@ -541,72 +628,78 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
541
628
|
// All network-backed input must be ready before the first device mutation.
|
|
542
629
|
await acquireFirmwareSource();
|
|
543
630
|
|
|
544
|
-
//
|
|
545
|
-
if (
|
|
631
|
+
// PreparedPlan 资源只依赖获批 ZIP;live release 仅保留给非 prepared 旧流程。
|
|
632
|
+
if (preparedResourceRequested) {
|
|
633
|
+
if (!this.isSupportResourceUpdate(params.updateType)) {
|
|
634
|
+
throw ERRORS.TypedError(
|
|
635
|
+
HardwareErrorCode.RuntimeError,
|
|
636
|
+
'Prepared firmware resources are not supported by this device',
|
|
637
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared', artifactName: 'resource' }
|
|
638
|
+
);
|
|
639
|
+
}
|
|
546
640
|
this.postTipMessage('CheckLatestUiResource');
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
641
|
+
this.postTipMessage('DownloadLatestUiResource');
|
|
642
|
+
const verifiedEntries = await readVerifiedPreparedResourceArchive({
|
|
643
|
+
preparedPlan: params.preparedPlan as FirmwareUpdatePreparedPlan,
|
|
644
|
+
reader: params.artifactReader,
|
|
551
645
|
});
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
source: FirmwareByteSource;
|
|
558
|
-
}> = [];
|
|
559
|
-
try {
|
|
560
|
-
for (const entry of params.resourceEntries) {
|
|
561
|
-
const resourceName = getFirmwareUpdateResourceName(entry.entryName);
|
|
562
|
-
const source = await openFirmwareByteSource({
|
|
563
|
-
artifact: entry.artifact,
|
|
564
|
-
reader: params.artifactReader,
|
|
565
|
-
});
|
|
566
|
-
if (!source) {
|
|
567
|
-
throw new Error('Firmware resource entry is not prepared');
|
|
568
|
-
}
|
|
569
|
-
sources.push({ entryName: resourceName, source });
|
|
570
|
-
}
|
|
571
|
-
await updateResourcesFromSources(
|
|
572
|
-
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
573
|
-
this.postMessage,
|
|
574
|
-
device,
|
|
575
|
-
sources.map(entry => ({
|
|
576
|
-
entryName: entry.entryName,
|
|
577
|
-
source: entry.source,
|
|
578
|
-
}))
|
|
579
|
-
);
|
|
580
|
-
} finally {
|
|
581
|
-
await Promise.all(
|
|
582
|
-
sources.map(entry => entry.source.close().catch(() => undefined))
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
586
|
-
} else {
|
|
587
|
-
if (
|
|
588
|
-
params.artifactReader ||
|
|
589
|
-
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
590
|
-
) {
|
|
646
|
+
const sources: Array<{ entryName: string; source: FirmwareByteSource }> = [];
|
|
647
|
+
try {
|
|
648
|
+
for (const entry of verifiedEntries) {
|
|
649
|
+
const source = await openFirmwareByteSource({ binary: entry.binary });
|
|
650
|
+
if (!source) {
|
|
591
651
|
throw ERRORS.TypedError(
|
|
592
652
|
HardwareErrorCode.RuntimeError,
|
|
593
|
-
|
|
653
|
+
`Firmware resource entry ${entry.entryName} is empty`,
|
|
594
654
|
{
|
|
595
|
-
firmwareUpdateCode: '
|
|
655
|
+
firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch',
|
|
596
656
|
artifactName: 'resource',
|
|
597
657
|
}
|
|
598
658
|
);
|
|
599
659
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
660
|
+
sources.push({ entryName: entry.entryName, source });
|
|
661
|
+
}
|
|
662
|
+
await updateResourcesFromSources(
|
|
663
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
664
|
+
this.postMessage,
|
|
665
|
+
device,
|
|
666
|
+
sources
|
|
667
|
+
);
|
|
668
|
+
} finally {
|
|
669
|
+
await Promise.all(sources.map(entry => entry.source.close().catch(() => undefined)));
|
|
670
|
+
}
|
|
671
|
+
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
672
|
+
} else if (this.isSupportResourceUpdate(params.updateType)) {
|
|
673
|
+
this.postTipMessage('CheckLatestUiResource');
|
|
674
|
+
const resourceUrl = DataManager.getSysResourcesLatestRelease({
|
|
675
|
+
features,
|
|
676
|
+
forcedUpdateRes: params.forcedUpdateRes,
|
|
677
|
+
firmwareType,
|
|
678
|
+
});
|
|
679
|
+
if (resourceUrl) {
|
|
680
|
+
this.postTipMessage('DownloadLatestUiResource');
|
|
681
|
+
if (
|
|
682
|
+
params.artifactReader ||
|
|
683
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
684
|
+
) {
|
|
685
|
+
throw ERRORS.TypedError(
|
|
686
|
+
HardwareErrorCode.RuntimeError,
|
|
687
|
+
'Firmware resource must be prepared by the external firmware host',
|
|
688
|
+
{
|
|
689
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
690
|
+
artifactName: 'resource',
|
|
691
|
+
}
|
|
608
692
|
);
|
|
609
693
|
}
|
|
694
|
+
const resourceBinary: ArrayBuffer | Buffer = (await getSysResourceBinary(resourceUrl))
|
|
695
|
+
.binary;
|
|
696
|
+
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
697
|
+
await updateResources(
|
|
698
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
699
|
+
this.postMessage,
|
|
700
|
+
device,
|
|
701
|
+
resourceBinary
|
|
702
|
+
);
|
|
610
703
|
}
|
|
611
704
|
}
|
|
612
705
|
|
|
@@ -21,10 +21,12 @@ import { DEVICE } from '../events';
|
|
|
21
21
|
import { buildProtocolV1FeaturesPayload } from '../deviceProfile';
|
|
22
22
|
import { openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
23
23
|
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
24
|
+
import { readVerifiedPreparedResourceArchive } from './firmware/FirmwarePreparedResourceArchive';
|
|
24
25
|
import {
|
|
25
26
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
26
27
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
27
|
-
|
|
28
|
+
getFirmwareUpdatePreparedRawArtifact,
|
|
29
|
+
validateFirmwareUpdatePreparedPlan,
|
|
28
30
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
29
31
|
|
|
30
32
|
import type {
|
|
@@ -77,54 +79,97 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
77
79
|
{ name: 'firmwareType', type: 'string' },
|
|
78
80
|
{ name: 'platform', type: 'string' },
|
|
79
81
|
]);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const hasPreparedPlan = payload.preparedPlan !== undefined;
|
|
83
|
+
if (!hasPreparedPlan && payload.artifacts !== undefined) {
|
|
84
|
+
throw ERRORS.TypedError(
|
|
85
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
86
|
+
'Firmware artifacts require a prepared plan'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const preparedPlan = hasPreparedPlan
|
|
90
|
+
? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
|
|
91
|
+
: undefined;
|
|
92
|
+
const hasLegacyInputs = [
|
|
93
|
+
payload.bleVersion,
|
|
94
|
+
payload.bleBinary,
|
|
95
|
+
payload.firmwareVersion,
|
|
96
|
+
payload.firmwareBinary,
|
|
97
|
+
payload.resourceBinary,
|
|
98
|
+
payload.bootloaderVersion,
|
|
99
|
+
payload.bootloaderBinary,
|
|
100
|
+
].some(input => input !== undefined);
|
|
101
|
+
if (preparedPlan && hasLegacyInputs) {
|
|
102
|
+
throw ERRORS.TypedError(
|
|
103
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
104
|
+
'Prepared firmware plans cannot be combined with legacy firmware inputs'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const artifactReader = preparedPlan
|
|
108
|
+
? resolveFirmwareUpdateHostBinding(
|
|
109
|
+
payload.hostBindingGeneration,
|
|
110
|
+
preparedPlan.preparedPlanDigest
|
|
111
|
+
).artifactReader
|
|
112
|
+
: payload.artifactReader;
|
|
113
|
+
const preparedArtifacts: NonNullable<FirmwareUpdateV3Params['artifacts']> = {};
|
|
114
|
+
if (preparedPlan) {
|
|
115
|
+
const componentTargets = ['bootloader', 'firmware', 'ble'] as const;
|
|
116
|
+
const supportedTargets = new Set<string>([...componentTargets, 'resource']);
|
|
117
|
+
const unsupportedTarget = preparedPlan.targetsToUpdate.find(
|
|
118
|
+
target => !supportedTargets.has(target)
|
|
119
|
+
);
|
|
120
|
+
if (unsupportedTarget) {
|
|
121
|
+
throw ERRORS.TypedError(
|
|
122
|
+
HardwareErrorCode.RuntimeError,
|
|
123
|
+
`Firmware prepared plan target ${unsupportedTarget} is not supported by V3`,
|
|
124
|
+
{ firmwareUpdateCode: 'FirmwarePreparedPlanInvalid' }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
for (const target of componentTargets) {
|
|
128
|
+
if (preparedPlan.targetsToUpdate.includes(target)) {
|
|
129
|
+
preparedArtifacts[target] = getFirmwareUpdatePreparedRawArtifact({
|
|
130
|
+
preparedPlan,
|
|
131
|
+
target,
|
|
132
|
+
role: target,
|
|
133
|
+
}).artifact;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const resourceBindings = (payload.artifacts?.resourceEntries ?? []).map(
|
|
137
|
+
(entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
|
|
138
|
+
target: 'resource' as const,
|
|
139
|
+
entryName: entry.entryName,
|
|
140
|
+
artifact: entry.artifact,
|
|
141
|
+
})
|
|
142
|
+
);
|
|
85
143
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
86
|
-
preparedPlan
|
|
144
|
+
preparedPlan,
|
|
87
145
|
executor: 'v3',
|
|
88
146
|
platform: payload.platform,
|
|
89
|
-
scopeTargets: [
|
|
147
|
+
scopeTargets: [
|
|
148
|
+
'bootloader',
|
|
149
|
+
'firmware',
|
|
150
|
+
'ble',
|
|
151
|
+
...(resourceBindings.length ? (['resource'] as const) : []),
|
|
152
|
+
],
|
|
90
153
|
bindings: [
|
|
91
|
-
...(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
]
|
|
106
|
-
: []),
|
|
107
|
-
...(payload.artifacts?.ble
|
|
108
|
-
? [
|
|
109
|
-
{
|
|
110
|
-
target: 'ble' as const,
|
|
111
|
-
artifact: payload.artifacts.ble,
|
|
112
|
-
},
|
|
113
|
-
]
|
|
114
|
-
: []),
|
|
115
|
-
...(payload.artifacts?.resourceEntries ?? []).map(
|
|
116
|
-
(entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
|
|
117
|
-
target: 'resource' as const,
|
|
118
|
-
entryName: entry.entryName,
|
|
119
|
-
artifact: entry.artifact,
|
|
120
|
-
})
|
|
121
|
-
),
|
|
154
|
+
...componentTargets.flatMap(target => {
|
|
155
|
+
const plannedArtifact = preparedArtifacts[target];
|
|
156
|
+
const suppliedArtifact = payload.artifacts?.[target];
|
|
157
|
+
return plannedArtifact || suppliedArtifact
|
|
158
|
+
? [
|
|
159
|
+
{
|
|
160
|
+
target,
|
|
161
|
+
artifact: suppliedArtifact ?? (plannedArtifact as FirmwareArtifactReference),
|
|
162
|
+
},
|
|
163
|
+
]
|
|
164
|
+
: [];
|
|
165
|
+
}),
|
|
166
|
+
...resourceBindings,
|
|
122
167
|
],
|
|
123
168
|
});
|
|
124
169
|
}
|
|
125
170
|
|
|
126
171
|
this.params = {
|
|
127
|
-
preparedPlan
|
|
172
|
+
preparedPlan,
|
|
128
173
|
bleBinary: payload.bleBinary,
|
|
129
174
|
firmwareBinary: payload.firmwareBinary,
|
|
130
175
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
@@ -133,10 +178,10 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
133
178
|
bootloaderBinary: payload.bootloaderBinary,
|
|
134
179
|
firmwareVersion: payload.firmwareVersion,
|
|
135
180
|
resourceBinary: payload.resourceBinary,
|
|
136
|
-
firmwareType: payload.firmwareType,
|
|
181
|
+
firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
|
|
137
182
|
platform: payload.platform,
|
|
138
|
-
artifactReader
|
|
139
|
-
artifacts:
|
|
183
|
+
artifactReader,
|
|
184
|
+
artifacts: preparedPlan ? preparedArtifacts : undefined,
|
|
140
185
|
};
|
|
141
186
|
}
|
|
142
187
|
|
|
@@ -263,25 +308,25 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
263
308
|
}
|
|
264
309
|
|
|
265
310
|
private async prepareResourceInput(firmwareType: EFirmwareType) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
if (preparedEntries?.length) {
|
|
311
|
+
if (this.params.preparedPlan?.targetsToUpdate.includes('resource')) {
|
|
312
|
+
const verifiedEntries = await readVerifiedPreparedResourceArchive({
|
|
313
|
+
preparedPlan: this.params.preparedPlan,
|
|
314
|
+
reader: this.params.artifactReader,
|
|
315
|
+
});
|
|
274
316
|
const resourceEntries: Array<{ entryName: string; source: FirmwareByteSource }> = [];
|
|
275
|
-
for (const entry of
|
|
276
|
-
const
|
|
277
|
-
const source = await this.openArtifactSource(undefined, entry.artifact);
|
|
317
|
+
for (const entry of verifiedEntries) {
|
|
318
|
+
const source = await this.openArtifactSource(entry.binary, undefined);
|
|
278
319
|
if (!source) {
|
|
279
320
|
throw ERRORS.TypedError(
|
|
280
321
|
HardwareErrorCode.RuntimeError,
|
|
281
|
-
`Firmware resource entry ${entry.entryName} is
|
|
322
|
+
`Firmware resource entry ${entry.entryName} is empty`,
|
|
323
|
+
{
|
|
324
|
+
firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch',
|
|
325
|
+
artifactName: 'resource',
|
|
326
|
+
}
|
|
282
327
|
);
|
|
283
328
|
}
|
|
284
|
-
resourceEntries.push({ entryName:
|
|
329
|
+
resourceEntries.push({ entryName: entry.entryName, source });
|
|
285
330
|
}
|
|
286
331
|
return {
|
|
287
332
|
resourceBinary: null,
|