@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.22
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__/DeviceCommands.test.ts +87 -2
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-identity.test.ts +24 -2
- package/__tests__/device-settings.test.ts +1 -1
- package/__tests__/device-wallet-session-store.test.ts +111 -7
- 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-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/open-wallet-session.test.ts +253 -13
- package/__tests__/pro2Wallpaper.test.ts +7 -18
- package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
- package/__tests__/protocol-v2.test.ts +415 -145
- package/__tests__/public-device-state-api.test.ts +69 -62
- package/__tests__/public-pro2-api-boundary.test.ts +8 -34
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -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 +29 -10
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
- package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +1 -1
- package/dist/api/device/DeviceUnlock.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 +26 -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/FirmwareUpdateBaseMethod.d.ts +7 -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 +24 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +25 -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/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -17
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
- package/dist/api/nexa/NexaSignTransaction.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.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +265 -226
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3045 -1550
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +2 -2
- package/dist/types/api/deviceUnlock.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 +69 -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 +27 -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 +10 -19
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -76
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +4 -1
- package/dist/types/api/sessionCache.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/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -3
- package/src/api/CheckAllFirmwareRelease.ts +16 -3
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/FirmwareUpdate.ts +8 -1
- package/src/api/FirmwareUpdateV2.ts +284 -119
- package/src/api/FirmwareUpdateV3.ts +248 -55
- package/src/api/FirmwareUpdateV4.ts +816 -328
- package/src/api/OpenWalletSession.ts +74 -25
- package/src/api/UploadPortfolio.ts +1 -1
- package/src/api/conflux/ConfluxGetAddress.ts +2 -0
- package/src/api/conflux/ConfluxSignMessage.ts +2 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
- package/src/api/device/DeviceChangePin.ts +1 -1
- package/src/api/device/DeviceUnlock.ts +1 -2
- package/src/api/device/DeviceUpdateBootloader.ts +114 -6
- package/src/api/device/DeviceWipe.ts +1 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +378 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +6 -0
- package/src/api/index.ts +0 -18
- package/src/api/nexa/NexaSignTransaction.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/data/messages/messages-protocol-v2.json +77 -4
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +50 -60
- package/src/device/DeviceCommands.ts +58 -14
- package/src/device/DeviceStateMapper.ts +18 -1
- package/src/device/DeviceStateProjector.ts +7 -1
- package/src/events/logBlockEvent.ts +1 -0
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -25
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -2
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
- package/src/protocols/protocol-v2/walletSession.ts +68 -12
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +3 -0
- package/src/types/api/deviceUnlock.ts +2 -5
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +36 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -34
- package/src/types/api/openWalletSession.ts +2 -0
- package/src/types/api/protocolV2.ts +3 -156
- package/src/types/api/sessionCache.ts +9 -4
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +5 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from '@onekeyfe/hd-transport';
|
|
7
7
|
|
|
8
8
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
9
|
+
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
9
10
|
import { validateParams } from './helpers/paramsValidator';
|
|
10
11
|
import {
|
|
11
12
|
LoggerNames,
|
|
@@ -28,10 +29,17 @@ import {
|
|
|
28
29
|
getProtocolV2UnknownErrorText,
|
|
29
30
|
isProtocolV2DeviceDisconnectedError,
|
|
30
31
|
} from './protocol-v2/helpers';
|
|
32
|
+
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
33
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
34
|
+
import { assertFirmwareUpdatePreparedPlanBinding } from './firmware/FirmwareUpdatePreparedPlan';
|
|
31
35
|
|
|
32
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
FirmwareArtifactReference,
|
|
38
|
+
FirmwareUpdateV4Params,
|
|
39
|
+
FirmwareUpdateV4Target,
|
|
40
|
+
} from '../types/api/firmwareUpdate';
|
|
33
41
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
34
|
-
import type {
|
|
42
|
+
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
35
43
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
36
44
|
import type {
|
|
37
45
|
Features,
|
|
@@ -39,6 +47,7 @@ import type {
|
|
|
39
47
|
IProtocolV2FirmwareComponent,
|
|
40
48
|
IVersionArray,
|
|
41
49
|
} from '../types';
|
|
50
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
42
51
|
|
|
43
52
|
const Log = getLogger(LoggerNames.Method);
|
|
44
53
|
|
|
@@ -82,11 +91,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
82
91
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
83
92
|
};
|
|
84
93
|
|
|
85
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
86
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
87
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
94
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
91
95
|
target_id: number | string;
|
|
92
96
|
status?: number | string;
|
|
@@ -100,17 +104,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
100
104
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
101
105
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
102
106
|
};
|
|
103
|
-
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
104
|
-
path: string;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
108
|
-
filePath: string;
|
|
109
|
-
processedSize?: number;
|
|
110
|
-
totalSize?: number;
|
|
111
|
-
onTransferredBytes?: (transferredBytes: number) => void;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
107
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
115
108
|
type ProtocolV2ResourceBundleBinary = {
|
|
116
109
|
name: string;
|
|
@@ -133,6 +126,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
133
126
|
kind: 'bootloader' | 'firmware';
|
|
134
127
|
};
|
|
135
128
|
|
|
129
|
+
type ProtocolV2InstallSource = {
|
|
130
|
+
fileName: string;
|
|
131
|
+
source: FirmwareByteSource;
|
|
132
|
+
targetId: number;
|
|
133
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type ProtocolV2ResourceBundleSource = {
|
|
137
|
+
name: string;
|
|
138
|
+
source: FirmwareByteSource;
|
|
139
|
+
devicePath: string;
|
|
140
|
+
version?: IVersionArray;
|
|
141
|
+
payloadHash?: string;
|
|
142
|
+
headerHash?: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type ProtocolV2ExecutionPhaseKind =
|
|
146
|
+
| 'resource-sync'
|
|
147
|
+
| 'bootloader-install'
|
|
148
|
+
| 'bootloader-verify'
|
|
149
|
+
| 'component-install'
|
|
150
|
+
| 'final-verify';
|
|
151
|
+
|
|
152
|
+
type ProtocolV2ExecutionPhase = {
|
|
153
|
+
kind: ProtocolV2ExecutionPhaseKind;
|
|
154
|
+
installSources: ProtocolV2InstallSource[];
|
|
155
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
156
|
+
};
|
|
157
|
+
|
|
136
158
|
type ProtocolV2OkppHeader = {
|
|
137
159
|
type: string;
|
|
138
160
|
version: IVersionArray;
|
|
@@ -364,22 +386,19 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
|
|
|
364
386
|
};
|
|
365
387
|
|
|
366
388
|
export const assertProtocolV2ReconnectIdentity = (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
options: { allowMissingActual?: boolean } = {}
|
|
389
|
+
expectedSerialNumber?: string,
|
|
390
|
+
actualSerialNumber?: string
|
|
370
391
|
) => {
|
|
371
|
-
if (!
|
|
372
|
-
if (!actualDeviceId) {
|
|
373
|
-
if (options.allowMissingActual) return;
|
|
392
|
+
if (!expectedSerialNumber || !actualSerialNumber) {
|
|
374
393
|
throw ERRORS.TypedError(
|
|
375
394
|
HardwareErrorCode.DeviceNotFound,
|
|
376
|
-
'Protocol V2 reconnect identity unavailable'
|
|
395
|
+
'Protocol V2 reconnect physical identity unavailable'
|
|
377
396
|
);
|
|
378
397
|
}
|
|
379
|
-
if (
|
|
398
|
+
if (actualSerialNumber !== expectedSerialNumber) {
|
|
380
399
|
throw ERRORS.TypedError(
|
|
381
400
|
HardwareErrorCode.DeviceNotFound,
|
|
382
|
-
`Protocol V2 reconnect identity mismatch: expected ${
|
|
401
|
+
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
383
402
|
);
|
|
384
403
|
}
|
|
385
404
|
};
|
|
@@ -393,12 +412,20 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
393
412
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
394
413
|
*/
|
|
395
414
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
396
|
-
private
|
|
415
|
+
private protocolV2ExpectedSerialNumber?: string;
|
|
416
|
+
|
|
417
|
+
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
418
|
+
|
|
419
|
+
private protocolV2ExecutionInLoader = false;
|
|
420
|
+
|
|
421
|
+
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
422
|
+
|
|
423
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
397
424
|
|
|
398
425
|
init() {
|
|
399
426
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
400
427
|
this.requireDeviceMode = [];
|
|
401
|
-
this.unlockPolicy = '
|
|
428
|
+
this.unlockPolicy = 'unlock-before-run';
|
|
402
429
|
this.useDevicePassphraseState = false;
|
|
403
430
|
this.skipForceUpdateCheck = true;
|
|
404
431
|
|
|
@@ -432,10 +459,69 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
432
459
|
{ name: 'firmwareType', type: 'string' },
|
|
433
460
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
434
461
|
{ name: 'platform', type: 'string' },
|
|
462
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
435
463
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
436
464
|
]);
|
|
465
|
+
if (
|
|
466
|
+
payload.expectedDeviceId !== undefined &&
|
|
467
|
+
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
468
|
+
) {
|
|
469
|
+
throw ERRORS.TypedError(
|
|
470
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
471
|
+
'Protocol V2 expected device identity is invalid'
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const hostBinding =
|
|
475
|
+
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
476
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
477
|
+
: undefined;
|
|
478
|
+
if (hostBinding) {
|
|
479
|
+
if (!payload.expectedDeviceId) {
|
|
480
|
+
throw ERRORS.TypedError(
|
|
481
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
482
|
+
'Protocol V2 prepared update requires the expected device identity'
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
486
|
+
preparedPlan: payload.preparedPlan,
|
|
487
|
+
executor: 'v4',
|
|
488
|
+
platform: payload.platform,
|
|
489
|
+
deviceIdentity: payload.expectedDeviceId,
|
|
490
|
+
scopeTargets: [
|
|
491
|
+
'boot',
|
|
492
|
+
'app_v1',
|
|
493
|
+
'app_v2',
|
|
494
|
+
'coprocessor',
|
|
495
|
+
'resource',
|
|
496
|
+
'se01',
|
|
497
|
+
'se02',
|
|
498
|
+
'se03',
|
|
499
|
+
'se04',
|
|
500
|
+
],
|
|
501
|
+
bindings: [
|
|
502
|
+
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
503
|
+
artifact
|
|
504
|
+
? [
|
|
505
|
+
{
|
|
506
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
507
|
+
artifact,
|
|
508
|
+
},
|
|
509
|
+
]
|
|
510
|
+
: []
|
|
511
|
+
),
|
|
512
|
+
...(payload.resourceBundleArtifacts ?? []).map(
|
|
513
|
+
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
514
|
+
target: 'resource' as const,
|
|
515
|
+
logicalName: entry.name,
|
|
516
|
+
artifact: entry.artifact,
|
|
517
|
+
})
|
|
518
|
+
),
|
|
519
|
+
],
|
|
520
|
+
});
|
|
521
|
+
}
|
|
437
522
|
|
|
438
523
|
this.params = {
|
|
524
|
+
preparedPlan: payload.preparedPlan,
|
|
439
525
|
chunkSize: payload.chunkSize,
|
|
440
526
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
441
527
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -450,7 +536,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
450
536
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
451
537
|
firmwareType: payload.firmwareType,
|
|
452
538
|
targetsToUpdate: payload.targetsToUpdate,
|
|
539
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
453
540
|
platform: payload.platform,
|
|
541
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
542
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
543
|
+
componentArtifacts: payload.componentArtifacts,
|
|
544
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
454
545
|
};
|
|
455
546
|
}
|
|
456
547
|
|
|
@@ -483,10 +574,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
483
574
|
}
|
|
484
575
|
|
|
485
576
|
private async runProtocolV2() {
|
|
577
|
+
await this.captureProtocolV2PhysicalIdentity();
|
|
486
578
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
487
|
-
this.protocolV2ExpectedDeviceId = deviceFeatures.deviceId ?? undefined;
|
|
488
579
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
489
580
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
581
|
+
this.validateExpectedTargetVersions();
|
|
582
|
+
|
|
583
|
+
if (
|
|
584
|
+
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
585
|
+
this.params.resourceBundleArtifacts?.length
|
|
586
|
+
) {
|
|
587
|
+
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
588
|
+
}
|
|
490
589
|
|
|
491
590
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
492
591
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -497,6 +596,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
497
596
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
498
597
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
499
598
|
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
599
|
+
if (
|
|
600
|
+
this.params.artifactReader ||
|
|
601
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
602
|
+
) {
|
|
603
|
+
throw ERRORS.TypedError(
|
|
604
|
+
HardwareErrorCode.RuntimeError,
|
|
605
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
606
|
+
{
|
|
607
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
}
|
|
500
611
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
501
612
|
firmwareType,
|
|
502
613
|
deviceFeatures
|
|
@@ -505,10 +616,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
505
616
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
506
617
|
installItems = remoteBinaries.installItems;
|
|
507
618
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
619
|
+
const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
|
|
620
|
+
firmwareType,
|
|
621
|
+
deviceFeatures
|
|
622
|
+
);
|
|
623
|
+
resourceBundles = resourceDescriptors?.length
|
|
624
|
+
? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
|
|
625
|
+
: undefined;
|
|
512
626
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
513
627
|
} catch (err) {
|
|
514
628
|
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
@@ -521,22 +635,280 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
521
635
|
);
|
|
522
636
|
}
|
|
523
637
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
await this.executeProtocolV2Update({
|
|
638
|
+
return this.executeProtocolV2Update({
|
|
527
639
|
fwBinaryMap,
|
|
528
640
|
bootloaderBinary,
|
|
529
641
|
...(installItems ? { installItems } : undefined),
|
|
530
642
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
531
643
|
});
|
|
644
|
+
}
|
|
532
645
|
|
|
533
|
-
|
|
646
|
+
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
647
|
+
const source = await openFirmwareByteSource({
|
|
648
|
+
artifact,
|
|
649
|
+
reader: this.params.artifactReader,
|
|
650
|
+
});
|
|
651
|
+
if (!source) {
|
|
652
|
+
throw ERRORS.TypedError(
|
|
653
|
+
HardwareErrorCode.RuntimeError,
|
|
654
|
+
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
655
|
+
{
|
|
656
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
this.protocolV2PreparedSources.push(source);
|
|
661
|
+
return source;
|
|
662
|
+
}
|
|
534
663
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
664
|
+
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
665
|
+
const source = await openFirmwareByteSource({ binary });
|
|
666
|
+
if (!source) {
|
|
667
|
+
throw ERRORS.TypedError(
|
|
668
|
+
HardwareErrorCode.RuntimeError,
|
|
669
|
+
'Protocol V2 firmware binary is empty'
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
this.protocolV2PreparedSources.push(source);
|
|
673
|
+
return source;
|
|
674
|
+
}
|
|
538
675
|
|
|
539
|
-
|
|
676
|
+
private async closeProtocolV2PreparedSources() {
|
|
677
|
+
const sources = this.protocolV2PreparedSources.splice(0);
|
|
678
|
+
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private async prepareProtocolV2InstallSources(
|
|
682
|
+
firmwareType: EFirmwareType,
|
|
683
|
+
features: Features
|
|
684
|
+
): Promise<ProtocolV2InstallSource[]> {
|
|
685
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
686
|
+
if (!release) {
|
|
687
|
+
throw ERRORS.TypedError(
|
|
688
|
+
HardwareErrorCode.RuntimeError,
|
|
689
|
+
'Protocol V2 firmware release is unavailable'
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
693
|
+
const requestedTargets = new Set(
|
|
694
|
+
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
695
|
+
);
|
|
696
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
697
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
698
|
+
|
|
699
|
+
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
700
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
701
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
702
|
+
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
703
|
+
const artifact = componentArtifacts[updateTarget];
|
|
704
|
+
if (!artifact) {
|
|
705
|
+
throw ERRORS.TypedError(
|
|
706
|
+
HardwareErrorCode.RuntimeError,
|
|
707
|
+
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
708
|
+
{
|
|
709
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
710
|
+
artifactName: updateTarget,
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
installSources.push({
|
|
715
|
+
...target,
|
|
716
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
717
|
+
});
|
|
718
|
+
preparedTargets.add(updateTarget);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
723
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
724
|
+
);
|
|
725
|
+
if (unknownTarget) {
|
|
726
|
+
throw ERRORS.TypedError(
|
|
727
|
+
HardwareErrorCode.RuntimeError,
|
|
728
|
+
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
return installSources;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private async prepareProtocolV2ResourceSources(
|
|
735
|
+
firmwareType: EFirmwareType,
|
|
736
|
+
features: Features
|
|
737
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
738
|
+
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
739
|
+
const resourceRequested =
|
|
740
|
+
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
741
|
+
if (!resourceRequested) {
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
745
|
+
const descriptors = release?.resourceBundles ?? [];
|
|
746
|
+
const artifactByName = new Map(
|
|
747
|
+
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
748
|
+
);
|
|
749
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
750
|
+
for (const descriptor of descriptors) {
|
|
751
|
+
const artifact = artifactByName.get(descriptor.name);
|
|
752
|
+
if (!artifact) {
|
|
753
|
+
throw ERRORS.TypedError(
|
|
754
|
+
HardwareErrorCode.RuntimeError,
|
|
755
|
+
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
756
|
+
{
|
|
757
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
758
|
+
artifactName: descriptor.name,
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
sources.push({
|
|
763
|
+
name: descriptor.name,
|
|
764
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
765
|
+
devicePath: descriptor.devicePath,
|
|
766
|
+
version: descriptor.version,
|
|
767
|
+
payloadHash: descriptor.payloadHash,
|
|
768
|
+
headerHash: descriptor.headerHash,
|
|
769
|
+
});
|
|
770
|
+
artifactByName.delete(descriptor.name);
|
|
771
|
+
}
|
|
772
|
+
if (artifactByName.size > 0) {
|
|
773
|
+
throw ERRORS.TypedError(
|
|
774
|
+
HardwareErrorCode.RuntimeError,
|
|
775
|
+
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
return sources;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
782
|
+
try {
|
|
783
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
784
|
+
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
785
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
786
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
787
|
+
throw ERRORS.TypedError(
|
|
788
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
789
|
+
'No firmware to update'
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
793
|
+
|
|
794
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
795
|
+
installSources,
|
|
796
|
+
resourceSources,
|
|
797
|
+
});
|
|
798
|
+
} finally {
|
|
799
|
+
await this.closeProtocolV2PreparedSources();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private validateExpectedTargetVersions() {
|
|
804
|
+
const expected = this.params.expectedTargetVersions;
|
|
805
|
+
if (expected === undefined) return;
|
|
806
|
+
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
807
|
+
throw ERRORS.TypedError(
|
|
808
|
+
HardwareErrorCode.RuntimeError,
|
|
809
|
+
'Protocol V2 expected target versions are invalid'
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
for (const [target, version] of Object.entries(expected)) {
|
|
813
|
+
if (
|
|
814
|
+
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
815
|
+
target as FirmwareUpdateV4Target
|
|
816
|
+
) ||
|
|
817
|
+
typeof version !== 'string' ||
|
|
818
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
819
|
+
version.length > 64
|
|
820
|
+
) {
|
|
821
|
+
throw ERRORS.TypedError(
|
|
822
|
+
HardwareErrorCode.RuntimeError,
|
|
823
|
+
'Protocol V2 expected target version is invalid'
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
830
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
831
|
+
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
832
|
+
if (!version) return undefined;
|
|
833
|
+
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
834
|
+
if (
|
|
835
|
+
parts.length !== 3 ||
|
|
836
|
+
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
837
|
+
) {
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
844
|
+
const expected = this.params?.expectedTargetVersions;
|
|
845
|
+
if (!expected) return;
|
|
846
|
+
const features = this.protocolV2LatestFinalFeatures;
|
|
847
|
+
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
848
|
+
const applicationTargets = requestedTargets.filter(
|
|
849
|
+
target => target === 'app_v1' || target === 'app_v2'
|
|
850
|
+
);
|
|
851
|
+
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
852
|
+
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
853
|
+
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
854
|
+
se01: features?.se01Version ?? undefined,
|
|
855
|
+
se02: features?.se02Version ?? undefined,
|
|
856
|
+
se03: features?.se03Version ?? undefined,
|
|
857
|
+
se04: features?.se04Version ?? undefined,
|
|
858
|
+
};
|
|
859
|
+
if (features && applicationTargets.length === 1) {
|
|
860
|
+
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
861
|
+
}
|
|
862
|
+
const expectedEntries = (
|
|
863
|
+
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
864
|
+
).filter(([target]) => !targets || targets.includes(target));
|
|
865
|
+
for (const [target, expectedVersion] of expectedEntries) {
|
|
866
|
+
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
867
|
+
([, mappedTarget]) => mappedTarget === target
|
|
868
|
+
)?.[0];
|
|
869
|
+
const statusVersion =
|
|
870
|
+
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
871
|
+
const observedVersion =
|
|
872
|
+
statusVersion === undefined
|
|
873
|
+
? visibleVersions[target]
|
|
874
|
+
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
875
|
+
Math.floor(statusVersion / 0x100) % 0x100
|
|
876
|
+
}.${statusVersion % 0x100}`;
|
|
877
|
+
if (!observedVersion) {
|
|
878
|
+
throw ERRORS.TypedError(
|
|
879
|
+
HardwareErrorCode.RuntimeError,
|
|
880
|
+
`Protocol V2 target ${target} has no observable final version`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
if (observedVersion !== expectedVersion) {
|
|
884
|
+
throw ERRORS.TypedError(
|
|
885
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
886
|
+
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
893
|
+
if (this.params.targetsToUpdate?.length) {
|
|
894
|
+
return [...new Set(this.params.targetsToUpdate)];
|
|
895
|
+
}
|
|
896
|
+
const targets = new Set<FirmwareUpdateV4Target>();
|
|
897
|
+
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
898
|
+
targets.add(target as FirmwareUpdateV4Target)
|
|
899
|
+
);
|
|
900
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
901
|
+
targets.add('resource');
|
|
902
|
+
}
|
|
903
|
+
if (this.params.bootloaderBinary) targets.add('boot');
|
|
904
|
+
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
905
|
+
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
906
|
+
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
907
|
+
if (this.params.se01Binary) targets.add('se01');
|
|
908
|
+
if (this.params.se02Binary) targets.add('se02');
|
|
909
|
+
if (this.params.se03Binary) targets.add('se03');
|
|
910
|
+
if (this.params.se04Binary) targets.add('se04');
|
|
911
|
+
return Array.from(targets);
|
|
540
912
|
}
|
|
541
913
|
|
|
542
914
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -550,6 +922,38 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
550
922
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
551
923
|
}
|
|
552
924
|
|
|
925
|
+
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
926
|
+
const serialNumber = deviceInfo.hw?.serial_no?.trim();
|
|
927
|
+
return serialNumber || undefined;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
931
|
+
assertProtocolV2ReconnectIdentity(
|
|
932
|
+
this.protocolV2ExpectedSerialNumber,
|
|
933
|
+
this.getProtocolV2SerialNumber(deviceInfo)
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
private async requestProtocolV2PhysicalIdentity() {
|
|
938
|
+
return requestProtocolV2DeviceInfo({
|
|
939
|
+
commands: this.device.getCommands(),
|
|
940
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
private async captureProtocolV2PhysicalIdentity() {
|
|
945
|
+
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
946
|
+
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
947
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
948
|
+
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
private async verifyProtocolV2ReconnectIdentity() {
|
|
952
|
+
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
953
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
954
|
+
return deviceInfo;
|
|
955
|
+
}
|
|
956
|
+
|
|
553
957
|
private prepareBootloaderBinary(): ArrayBuffer | null {
|
|
554
958
|
return this.params.bootloaderBinary ?? null;
|
|
555
959
|
}
|
|
@@ -758,11 +1162,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
758
1162
|
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
759
1163
|
// Manual binaries are installed directly without comparison.
|
|
760
1164
|
if (this.params.resourceBundleFiles?.length) {
|
|
761
|
-
return this.params.resourceBundleFiles.map(file =>
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1165
|
+
return this.params.resourceBundleFiles.map((file, index) => {
|
|
1166
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
1167
|
+
file.devicePath,
|
|
1168
|
+
`resourceBundleFiles[${index}].devicePath`
|
|
1169
|
+
);
|
|
1170
|
+
return {
|
|
1171
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
1172
|
+
binary: file.binary,
|
|
1173
|
+
devicePath,
|
|
1174
|
+
};
|
|
1175
|
+
});
|
|
766
1176
|
}
|
|
767
1177
|
|
|
768
1178
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
@@ -773,10 +1183,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
773
1183
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
774
1184
|
if (!release?.resourceBundles?.length) return undefined;
|
|
775
1185
|
|
|
776
|
-
return release.resourceBundles.map(bundle => ({
|
|
1186
|
+
return release.resourceBundles.map((bundle, index) => ({
|
|
777
1187
|
name: bundle.name,
|
|
778
1188
|
binary: new ArrayBuffer(0),
|
|
779
|
-
devicePath:
|
|
1189
|
+
devicePath: validateProtocolV2FilesystemPath(
|
|
1190
|
+
bundle.devicePath,
|
|
1191
|
+
`resourceBundles[${index}].devicePath`
|
|
1192
|
+
),
|
|
780
1193
|
url: bundle.url,
|
|
781
1194
|
version: bundle.version,
|
|
782
1195
|
payloadHash: bundle.payloadHash,
|
|
@@ -784,98 +1197,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
784
1197
|
})) as ProtocolV2ResourceBundleBinary[];
|
|
785
1198
|
}
|
|
786
1199
|
|
|
787
|
-
private async
|
|
1200
|
+
private async materializeProtocolV2ResourceBundles(
|
|
788
1201
|
bundles: ProtocolV2ResourceBundleBinary[]
|
|
789
1202
|
): Promise<ProtocolV2ResourceBundleBinary[]> {
|
|
790
|
-
const prefetched: ProtocolV2ResourceBundleBinary[] = [];
|
|
791
1203
|
for (const bundle of bundles) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1204
|
+
if (bundle.binary.byteLength === 0 && bundle.url) {
|
|
1205
|
+
if (
|
|
1206
|
+
this.params.artifactReader ||
|
|
1207
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
1208
|
+
) {
|
|
1209
|
+
throw ERRORS.TypedError(
|
|
1210
|
+
HardwareErrorCode.RuntimeError,
|
|
1211
|
+
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1212
|
+
{
|
|
1213
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1214
|
+
artifactName: bundle.name,
|
|
1215
|
+
}
|
|
1216
|
+
);
|
|
796
1217
|
}
|
|
797
|
-
Log.log(`[FirmwareUpdateV4]
|
|
798
|
-
|
|
1218
|
+
Log.log(`[FirmwareUpdateV4] preloading RESC bundle ${bundle.name}`);
|
|
1219
|
+
bundle.binary = (await getSysResourceBinary(bundle.url)).binary;
|
|
799
1220
|
}
|
|
800
|
-
if (binary.byteLength === 0) {
|
|
801
|
-
throw
|
|
1221
|
+
if (bundle.binary.byteLength === 0) {
|
|
1222
|
+
throw ERRORS.TypedError(
|
|
1223
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1224
|
+
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1225
|
+
);
|
|
802
1226
|
}
|
|
803
|
-
prefetched.push({ ...bundle, binary });
|
|
804
1227
|
}
|
|
805
|
-
return
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* Synchronize RESC bundles to the device.
|
|
810
|
-
*
|
|
811
|
-
* Manual mode writes supplied binaries directly.
|
|
812
|
-
* Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
|
|
813
|
-
*/
|
|
814
|
-
private async syncProtocolV2ResourceBundles(
|
|
815
|
-
bundles: ProtocolV2ResourceBundleBinary[],
|
|
816
|
-
firmwareSize: number
|
|
817
|
-
): Promise<{ processedSize: number; totalSize: number }> {
|
|
818
|
-
const transferStartTime = Date.now();
|
|
819
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
820
|
-
|
|
821
|
-
const isRemoteMode = bundles.some(bundle => !!bundle.url);
|
|
822
|
-
let bundlesToSync = bundles;
|
|
823
|
-
|
|
824
|
-
// Remote binaries were prefetched before bootloader entry. Only device-local
|
|
825
|
-
// header comparison and transfer are allowed in this phase.
|
|
826
|
-
if (isRemoteMode) {
|
|
827
|
-
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
828
|
-
for (const bundle of bundles) {
|
|
829
|
-
// Compare the existing device header.
|
|
830
|
-
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
831
|
-
if (upToDate) {
|
|
832
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
833
|
-
} else {
|
|
834
|
-
filtered.push(bundle);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
bundlesToSync = filtered;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
if (bundlesToSync.length === 0) {
|
|
841
|
-
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
842
|
-
return { processedSize: 0, totalSize: firmwareSize };
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Write directly to the device through FileWrite.
|
|
846
|
-
let totalSize = 0;
|
|
847
|
-
for (const b of bundlesToSync) totalSize += b.binary.byteLength;
|
|
848
|
-
|
|
849
|
-
const transferTotalSize = totalSize + firmwareSize;
|
|
850
|
-
let processedSize = 0;
|
|
851
|
-
for (const bundle of bundlesToSync) {
|
|
852
|
-
Log.log(
|
|
853
|
-
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
854
|
-
);
|
|
855
|
-
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
856
|
-
payload: bundle.binary,
|
|
857
|
-
filePath: bundle.devicePath,
|
|
858
|
-
processedSize,
|
|
859
|
-
totalSize: transferTotalSize,
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
864
|
-
Log.log(
|
|
865
|
-
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
866
|
-
elapsedMs / 1000
|
|
867
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
868
|
-
);
|
|
869
|
-
return { processedSize, totalSize: transferTotalSize };
|
|
1228
|
+
return bundles;
|
|
870
1229
|
}
|
|
871
1230
|
|
|
872
1231
|
/**
|
|
873
1232
|
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
874
1233
|
*/
|
|
875
1234
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
876
|
-
bundle:
|
|
1235
|
+
bundle: Pick<
|
|
1236
|
+
ProtocolV2ResourceBundleBinary,
|
|
1237
|
+
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1238
|
+
>
|
|
877
1239
|
): Promise<boolean> {
|
|
878
|
-
if (this.params
|
|
1240
|
+
if (this.params?.forcedUpdateRes) return false;
|
|
879
1241
|
if (!bundle.version && !bundle.payloadHash) return false;
|
|
880
1242
|
|
|
881
1243
|
try {
|
|
@@ -913,6 +1275,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
913
1275
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
914
1276
|
if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
|
|
915
1277
|
Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
|
|
1278
|
+
this.protocolV2ExecutionInLoader = true;
|
|
916
1279
|
return false;
|
|
917
1280
|
}
|
|
918
1281
|
|
|
@@ -922,6 +1285,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
922
1285
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
923
1286
|
await wait(1000);
|
|
924
1287
|
await this.waitForProtocolV2BootloaderMode();
|
|
1288
|
+
this.protocolV2ExecutionInLoader = true;
|
|
925
1289
|
return true;
|
|
926
1290
|
} catch (error) {
|
|
927
1291
|
if (error instanceof HardwareError) {
|
|
@@ -946,19 +1310,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
946
1310
|
commands: this.device.getCommands(),
|
|
947
1311
|
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
948
1312
|
});
|
|
1313
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
949
1314
|
// This is a reconnect probe after a Bootloader reboot. Bootloader does not
|
|
950
1315
|
// support DeviceStatusGet, so the generic runtime-mode probe is invalid here.
|
|
951
1316
|
const features = this.device.updateProtocolV2Features(deviceInfo, null, 'bootloader');
|
|
952
|
-
assertProtocolV2ReconnectIdentity(
|
|
953
|
-
this.protocolV2ExpectedDeviceId,
|
|
954
|
-
features.deviceId ?? undefined,
|
|
955
|
-
{ allowMissingActual: !!features.bootloaderMode }
|
|
956
|
-
);
|
|
957
1317
|
if (features?.bootloaderMode) {
|
|
958
1318
|
return features;
|
|
959
1319
|
}
|
|
960
1320
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
961
1321
|
} catch (error) {
|
|
1322
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1323
|
+
throw error;
|
|
1324
|
+
}
|
|
962
1325
|
lastError = error;
|
|
963
1326
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
964
1327
|
}
|
|
@@ -1011,6 +1374,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1011
1374
|
return entries;
|
|
1012
1375
|
}
|
|
1013
1376
|
|
|
1377
|
+
private buildProtocolV2ExecutionPhases({
|
|
1378
|
+
installSources,
|
|
1379
|
+
resourceSources,
|
|
1380
|
+
}: {
|
|
1381
|
+
installSources: ProtocolV2InstallSource[];
|
|
1382
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1383
|
+
}): ProtocolV2ExecutionPhase[] {
|
|
1384
|
+
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1385
|
+
if (resourceSources.length > 0) {
|
|
1386
|
+
phases.push({
|
|
1387
|
+
kind: 'resource-sync',
|
|
1388
|
+
installSources: [],
|
|
1389
|
+
resourceSources,
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1393
|
+
if (bootloaderSources.length > 0) {
|
|
1394
|
+
phases.push(
|
|
1395
|
+
{
|
|
1396
|
+
kind: 'bootloader-install',
|
|
1397
|
+
installSources: bootloaderSources,
|
|
1398
|
+
resourceSources: [],
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
kind: 'bootloader-verify',
|
|
1402
|
+
installSources: [],
|
|
1403
|
+
resourceSources: [],
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1408
|
+
if (componentSources.length > 0) {
|
|
1409
|
+
phases.push({
|
|
1410
|
+
kind: 'component-install',
|
|
1411
|
+
installSources: componentSources,
|
|
1412
|
+
resourceSources: [],
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
phases.push({
|
|
1416
|
+
kind: 'final-verify',
|
|
1417
|
+
installSources: [],
|
|
1418
|
+
resourceSources: [],
|
|
1419
|
+
});
|
|
1420
|
+
return phases;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
private async executeProtocolV2Phases({
|
|
1424
|
+
installSources,
|
|
1425
|
+
resourceSources,
|
|
1426
|
+
}: {
|
|
1427
|
+
installSources: ProtocolV2InstallSource[];
|
|
1428
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1429
|
+
}) {
|
|
1430
|
+
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1431
|
+
installSources,
|
|
1432
|
+
resourceSources,
|
|
1433
|
+
});
|
|
1434
|
+
for (const phase of phases) {
|
|
1435
|
+
if (phase.kind === 'final-verify') {
|
|
1436
|
+
return this.completeProtocolV2FinalVerification();
|
|
1437
|
+
}
|
|
1438
|
+
if (phase.kind === 'resource-sync') {
|
|
1439
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1440
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1441
|
+
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1442
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1443
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1444
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1445
|
+
} else if (phase.kind === 'bootloader-verify') {
|
|
1446
|
+
await this.waitForProtocolV2FinalFeatures();
|
|
1447
|
+
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
throw ERRORS.TypedError(
|
|
1451
|
+
HardwareErrorCode.RuntimeError,
|
|
1452
|
+
'Protocol V2 execution has no final verification phase'
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
private async executeProtocolV2SourceUpdate({
|
|
1457
|
+
installSources,
|
|
1458
|
+
resourceSources,
|
|
1459
|
+
}: {
|
|
1460
|
+
installSources: ProtocolV2InstallSource[];
|
|
1461
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1462
|
+
}) {
|
|
1463
|
+
return this.executeProtocolV2Phases({
|
|
1464
|
+
installSources,
|
|
1465
|
+
resourceSources,
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1014
1469
|
private async executeProtocolV2Update({
|
|
1015
1470
|
fwBinaryMap,
|
|
1016
1471
|
bootloaderBinary,
|
|
@@ -1022,104 +1477,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1022
1477
|
installItems?: ProtocolV2InstallItem[];
|
|
1023
1478
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1024
1479
|
}) {
|
|
1025
|
-
const
|
|
1480
|
+
const memoryInstallItems =
|
|
1026
1481
|
installItems ??
|
|
1027
1482
|
this.buildProtocolV2InstallItems({
|
|
1028
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1029
1483
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1484
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1030
1485
|
});
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if (resourceBundles?.length) {
|
|
1040
|
-
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1041
|
-
resourceBundles,
|
|
1042
|
-
firmwareSize
|
|
1486
|
+
try {
|
|
1487
|
+
const installSources = await Promise.all(
|
|
1488
|
+
memoryInstallItems.map(async item => ({
|
|
1489
|
+
fileName: item.fileName,
|
|
1490
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1491
|
+
targetId: item.targetId,
|
|
1492
|
+
kind: item.kind,
|
|
1493
|
+
}))
|
|
1043
1494
|
);
|
|
1044
|
-
|
|
1045
|
-
|
|
1495
|
+
const resourceSources = await Promise.all(
|
|
1496
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1497
|
+
name: bundle.name,
|
|
1498
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1499
|
+
devicePath: bundle.devicePath,
|
|
1500
|
+
version: bundle.version,
|
|
1501
|
+
payloadHash: bundle.payloadHash,
|
|
1502
|
+
headerHash: bundle.headerHash,
|
|
1503
|
+
}))
|
|
1504
|
+
);
|
|
1505
|
+
return await this.executeProtocolV2Phases({
|
|
1506
|
+
installSources,
|
|
1507
|
+
resourceSources,
|
|
1508
|
+
});
|
|
1509
|
+
} finally {
|
|
1510
|
+
await this.closeProtocolV2PreparedSources();
|
|
1046
1511
|
}
|
|
1512
|
+
}
|
|
1047
1513
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1514
|
+
private async executeProtocolV2TransferPhase({
|
|
1515
|
+
installSources,
|
|
1516
|
+
resourceSources,
|
|
1517
|
+
}: ProtocolV2ExecutionPhase) {
|
|
1518
|
+
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1519
|
+
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1520
|
+
for (const resource of resourceSources) {
|
|
1521
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1522
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1523
|
+
} else {
|
|
1524
|
+
resourcesToSync.push(resource);
|
|
1525
|
+
totalSize += resource.source.size;
|
|
1053
1526
|
}
|
|
1054
|
-
return;
|
|
1055
1527
|
}
|
|
1056
|
-
let transferredSize = processedSize;
|
|
1057
|
-
const transferStartTime = Date.now();
|
|
1058
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1059
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1060
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1061
|
-
transferredSize = bytes;
|
|
1062
|
-
};
|
|
1063
|
-
Log.log(
|
|
1064
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1065
|
-
);
|
|
1066
|
-
|
|
1067
|
-
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1068
1528
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
processedSize
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
onTransferredBytes,
|
|
1081
|
-
});
|
|
1082
|
-
transferredSize = processedSize;
|
|
1083
|
-
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1084
|
-
|
|
1085
|
-
stagedInstallTargets.push({
|
|
1086
|
-
...item,
|
|
1087
|
-
path: filePath,
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1529
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1530
|
+
let processedSize = 0;
|
|
1531
|
+
for (const resource of resourcesToSync) {
|
|
1532
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1533
|
+
source: resource.source,
|
|
1534
|
+
filePath: resource.devicePath,
|
|
1535
|
+
processedSize,
|
|
1536
|
+
totalSize,
|
|
1537
|
+
});
|
|
1538
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
1539
|
+
}
|
|
1090
1540
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1541
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
1542
|
+
for (const item of installSources) {
|
|
1543
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1544
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1545
|
+
source: item.source,
|
|
1546
|
+
filePath,
|
|
1547
|
+
processedSize,
|
|
1548
|
+
totalSize,
|
|
1549
|
+
});
|
|
1550
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1551
|
+
stagedInstallTargets.push({
|
|
1552
|
+
targetId: item.targetId,
|
|
1553
|
+
path: filePath,
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1094
1556
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
);
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1103
|
-
Log.warn(
|
|
1104
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1105
|
-
elapsedMs / 1000
|
|
1106
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1107
|
-
);
|
|
1108
|
-
throw error;
|
|
1557
|
+
if (totalSize > 0) {
|
|
1558
|
+
this.postProgressMessage(100, 'transferData');
|
|
1559
|
+
}
|
|
1560
|
+
if (stagedInstallTargets.length === 0) {
|
|
1561
|
+
return;
|
|
1109
1562
|
}
|
|
1110
1563
|
|
|
1111
1564
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1112
|
-
|
|
1113
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1565
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1114
1566
|
target_id: item.targetId,
|
|
1115
1567
|
path: item.path,
|
|
1116
1568
|
}));
|
|
1117
|
-
|
|
1118
|
-
await this.
|
|
1119
|
-
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1569
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1570
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
1120
1571
|
}
|
|
1121
1572
|
|
|
1122
|
-
private
|
|
1573
|
+
private async protocolV2SourceUpdateProcess({
|
|
1574
|
+
source,
|
|
1575
|
+
filePath,
|
|
1576
|
+
processedSize,
|
|
1577
|
+
totalSize,
|
|
1578
|
+
}: {
|
|
1579
|
+
source: FirmwareByteSource;
|
|
1580
|
+
filePath: string;
|
|
1581
|
+
processedSize: number;
|
|
1582
|
+
totalSize: number;
|
|
1583
|
+
}) {
|
|
1584
|
+
let lastError: unknown;
|
|
1585
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1586
|
+
try {
|
|
1587
|
+
await writeFirmwareByteSource({
|
|
1588
|
+
source,
|
|
1589
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1590
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
1591
|
+
const chunkEnd = sourceOffset + length;
|
|
1592
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1593
|
+
processedSize + sourceOffset,
|
|
1594
|
+
processedSize + chunkEnd,
|
|
1595
|
+
totalSize
|
|
1596
|
+
);
|
|
1597
|
+
const response = await this.fileWriteChunk(
|
|
1598
|
+
filePath,
|
|
1599
|
+
source.size,
|
|
1600
|
+
sourceOffset,
|
|
1601
|
+
data,
|
|
1602
|
+
first,
|
|
1603
|
+
progress
|
|
1604
|
+
);
|
|
1605
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
1606
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1607
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1608
|
+
throw ERRORS.TypedError(
|
|
1609
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1610
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
this.postProgressMessage(progress, 'transferData');
|
|
1614
|
+
return length;
|
|
1615
|
+
},
|
|
1616
|
+
});
|
|
1617
|
+
return processedSize + source.size;
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
lastError = error;
|
|
1620
|
+
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1621
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
throw ERRORS.TypedError(
|
|
1626
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1627
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1123
1632
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1124
1633
|
}
|
|
1125
1634
|
|
|
@@ -1138,7 +1647,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1138
1647
|
|
|
1139
1648
|
private assertProtocolV2TargetStatus(
|
|
1140
1649
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1141
|
-
expectedTargetIds: Set<number
|
|
1650
|
+
expectedTargetIds: Set<number>,
|
|
1651
|
+
expectedPaths = new Map<number, string>()
|
|
1142
1652
|
) {
|
|
1143
1653
|
Log.log(
|
|
1144
1654
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1169,10 +1679,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1169
1679
|
const matchingTargets = statusTargets.filter(target =>
|
|
1170
1680
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1171
1681
|
);
|
|
1682
|
+
const seenTargetIds = new Set<number>();
|
|
1683
|
+
for (const target of matchingTargets) {
|
|
1684
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1685
|
+
if (
|
|
1686
|
+
targetId === undefined ||
|
|
1687
|
+
seenTargetIds.has(targetId) ||
|
|
1688
|
+
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1689
|
+
) {
|
|
1690
|
+
throw ERRORS.TypedError(
|
|
1691
|
+
HardwareErrorCode.RuntimeError,
|
|
1692
|
+
`Protocol V2 install status conflicts with target ${target.target_id}`
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
seenTargetIds.add(targetId);
|
|
1696
|
+
}
|
|
1172
1697
|
const completedTargets = matchingTargets.filter(target =>
|
|
1173
1698
|
isProtocolV2TargetStatusFinished(target.status)
|
|
1174
1699
|
);
|
|
1175
1700
|
if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1701
|
+
for (const target of completedTargets) {
|
|
1702
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1703
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1704
|
+
if (targetId === undefined) {
|
|
1705
|
+
throw ERRORS.TypedError(
|
|
1706
|
+
HardwareErrorCode.RuntimeError,
|
|
1707
|
+
'Protocol V2 completed target identity is invalid'
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1711
|
+
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1712
|
+
if (payloadVersion !== expectedVersion) {
|
|
1713
|
+
throw ERRORS.TypedError(
|
|
1714
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1715
|
+
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (payloadVersion !== undefined) {
|
|
1720
|
+
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1176
1723
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1177
1724
|
return true;
|
|
1178
1725
|
}
|
|
@@ -1197,12 +1744,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1197
1744
|
targets: Array<{ target_id: number; path: string }>
|
|
1198
1745
|
) {
|
|
1199
1746
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1747
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1200
1748
|
const startTime = Date.now();
|
|
1201
1749
|
let lastError: unknown;
|
|
1202
1750
|
|
|
1203
1751
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1204
1752
|
try {
|
|
1205
1753
|
await this.reconnectProtocolV2Device();
|
|
1754
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
1206
1755
|
try {
|
|
1207
1756
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1208
1757
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1219,7 +1768,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1219
1768
|
if (
|
|
1220
1769
|
this.assertProtocolV2TargetStatus(
|
|
1221
1770
|
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1222
|
-
expectedTargetIds
|
|
1771
|
+
expectedTargetIds,
|
|
1772
|
+
expectedPaths
|
|
1223
1773
|
)
|
|
1224
1774
|
) {
|
|
1225
1775
|
return;
|
|
@@ -1236,10 +1786,34 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1236
1786
|
// device already rebooted into App, this endpoint error signals that the
|
|
1237
1787
|
// install phase ended; the later phase performs the final readiness check.
|
|
1238
1788
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1789
|
+
try {
|
|
1790
|
+
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1791
|
+
commands: this.device.getCommands(),
|
|
1792
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1793
|
+
});
|
|
1794
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1795
|
+
const observed = await this.device.probeProtocolV2RuntimeState(
|
|
1796
|
+
deviceInfo,
|
|
1797
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1798
|
+
);
|
|
1799
|
+
if (observed.mode === 'normal' && !observed.bootloaderMode) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
lastError = new Error(
|
|
1803
|
+
'Protocol V2 status endpoint is unavailable outside normal mode'
|
|
1804
|
+
);
|
|
1805
|
+
} catch (probeError) {
|
|
1806
|
+
lastError = probeError;
|
|
1807
|
+
}
|
|
1808
|
+
throw ERRORS.TypedError(
|
|
1809
|
+
HardwareErrorCode.RuntimeError,
|
|
1810
|
+
`Protocol V2 firmware install status is unavailable: ${this.normalizeErrorMessage(
|
|
1811
|
+
lastError
|
|
1812
|
+
)}`,
|
|
1813
|
+
{
|
|
1814
|
+
firmwareUpdateCode: 'FirmwareInstallStatusUnavailable',
|
|
1815
|
+
}
|
|
1241
1816
|
);
|
|
1242
|
-
return;
|
|
1243
1817
|
}
|
|
1244
1818
|
lastError = error;
|
|
1245
1819
|
Log.log(
|
|
@@ -1249,9 +1823,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1249
1823
|
}
|
|
1250
1824
|
} catch (error) {
|
|
1251
1825
|
lastError = error;
|
|
1826
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1827
|
+
throw error;
|
|
1828
|
+
}
|
|
1252
1829
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1253
1830
|
throw error;
|
|
1254
1831
|
}
|
|
1832
|
+
if (
|
|
1833
|
+
error instanceof HardwareError &&
|
|
1834
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1835
|
+
) {
|
|
1836
|
+
throw error;
|
|
1837
|
+
}
|
|
1255
1838
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1256
1839
|
}
|
|
1257
1840
|
await wait(1000);
|
|
@@ -1265,9 +1848,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1265
1848
|
|
|
1266
1849
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1267
1850
|
await this.reconnectProtocolV2Device();
|
|
1851
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
1268
1852
|
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1269
1853
|
// repeating it after an automatic App reboot is idempotent.
|
|
1270
1854
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1855
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1271
1856
|
}
|
|
1272
1857
|
|
|
1273
1858
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -1278,6 +1863,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1278
1863
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1279
1864
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1280
1865
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1866
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1281
1867
|
if (firmwareVersion === '0.0.0') {
|
|
1282
1868
|
Log.warn(
|
|
1283
1869
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1291,6 +1877,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1291
1877
|
};
|
|
1292
1878
|
}
|
|
1293
1879
|
|
|
1880
|
+
private async completeProtocolV2FinalVerification() {
|
|
1881
|
+
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1882
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1883
|
+
}
|
|
1884
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1885
|
+
this.assertExpectedProtocolV2Versions();
|
|
1886
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1887
|
+
DevicePool.resetState();
|
|
1888
|
+
return versions;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1294
1891
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1295
1892
|
const startTime = Date.now();
|
|
1296
1893
|
let lastError: unknown;
|
|
@@ -1304,14 +1901,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1304
1901
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
1305
1902
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1306
1903
|
});
|
|
1904
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1307
1905
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
1308
1906
|
deviceInfo,
|
|
1309
1907
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1310
1908
|
);
|
|
1311
|
-
assertProtocolV2ReconnectIdentity(
|
|
1312
|
-
this.protocolV2ExpectedDeviceId,
|
|
1313
|
-
features.deviceId ?? undefined
|
|
1314
|
-
);
|
|
1315
1909
|
if (features.mode !== 'normal' || features.bootloaderMode) {
|
|
1316
1910
|
throw ERRORS.TypedError(
|
|
1317
1911
|
HardwareErrorCode.DeviceNotFound,
|
|
@@ -1320,9 +1914,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1320
1914
|
}
|
|
1321
1915
|
return features;
|
|
1322
1916
|
} catch (error) {
|
|
1323
|
-
|
|
1324
|
-
// fixed by polling. A temporarily missing identity may still become ready.
|
|
1325
|
-
if (this.isProtocolV2ReconnectIdentityMismatch(error)) {
|
|
1917
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1326
1918
|
throw error;
|
|
1327
1919
|
}
|
|
1328
1920
|
lastError = error;
|
|
@@ -1362,13 +1954,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1362
1954
|
await this.ensureProtocolV2DeviceAcquired();
|
|
1363
1955
|
this.device.commands.disposed = false;
|
|
1364
1956
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1365
|
-
assertProtocolV2ReconnectIdentity(
|
|
1366
|
-
this.protocolV2ExpectedDeviceId,
|
|
1367
|
-
this.device.getCurrentDeviceId(),
|
|
1368
|
-
// Cached identity may be absent immediately after acquire. Reject only an
|
|
1369
|
-
// existing mismatch; the final refresh performs strict identity validation.
|
|
1370
|
-
{ allowMissingActual: true }
|
|
1371
|
-
);
|
|
1372
1957
|
return;
|
|
1373
1958
|
}
|
|
1374
1959
|
|
|
@@ -1389,13 +1974,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1389
1974
|
await this.ensureProtocolV2DeviceAcquired();
|
|
1390
1975
|
this.device.commands.disposed = false;
|
|
1391
1976
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1392
|
-
assertProtocolV2ReconnectIdentity(
|
|
1393
|
-
this.protocolV2ExpectedDeviceId,
|
|
1394
|
-
this.device.getCurrentDeviceId(),
|
|
1395
|
-
// Cached identity may be absent immediately after acquire. Reject only an
|
|
1396
|
-
// existing mismatch; the final refresh performs strict identity validation.
|
|
1397
|
-
{ allowMissingActual: true }
|
|
1398
|
-
);
|
|
1399
1977
|
}
|
|
1400
1978
|
|
|
1401
1979
|
/**
|
|
@@ -1412,98 +1990,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1412
1990
|
});
|
|
1413
1991
|
}
|
|
1414
1992
|
|
|
1415
|
-
private
|
|
1416
|
-
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect identity
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1420
|
-
let lastError: unknown;
|
|
1421
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1422
|
-
try {
|
|
1423
|
-
return await this.protocolV2WriteWholeFile(params);
|
|
1424
|
-
} catch (error) {
|
|
1425
|
-
lastError = error;
|
|
1426
|
-
Log.error(
|
|
1427
|
-
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1428
|
-
error
|
|
1429
|
-
);
|
|
1430
|
-
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1431
|
-
break;
|
|
1432
|
-
}
|
|
1433
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
throw ERRORS.TypedError(
|
|
1438
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1439
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1440
|
-
);
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
private async protocolV2WriteWholeFile({
|
|
1444
|
-
payload,
|
|
1445
|
-
filePath,
|
|
1446
|
-
processedSize,
|
|
1447
|
-
totalSize,
|
|
1448
|
-
onTransferredBytes,
|
|
1449
|
-
}: ProtocolV2FileTransferParams) {
|
|
1450
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1451
|
-
let offset = 0;
|
|
1452
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1453
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1454
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1455
|
-
}
|
|
1456
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1457
|
-
};
|
|
1458
|
-
|
|
1459
|
-
while (offset < payload.byteLength) {
|
|
1460
|
-
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1461
|
-
const chunkLength = chunkEnd - offset;
|
|
1462
|
-
const chunk = payload.slice(offset, chunkEnd);
|
|
1463
|
-
const overwrite = offset === 0;
|
|
1464
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1465
|
-
(processedSize ?? 0) + offset,
|
|
1466
|
-
(processedSize ?? 0) + chunkEnd,
|
|
1467
|
-
totalSize ?? payload.byteLength
|
|
1468
|
-
);
|
|
1469
|
-
|
|
1470
|
-
const writeRes = await this.fileWriteChunk(
|
|
1471
|
-
filePath,
|
|
1472
|
-
payload.byteLength,
|
|
1473
|
-
offset,
|
|
1474
|
-
chunk,
|
|
1475
|
-
overwrite,
|
|
1476
|
-
progress
|
|
1477
|
-
);
|
|
1478
|
-
const rawProcessedByte = writeRes.message.processed_byte;
|
|
1479
|
-
const processedByte = Number(rawProcessedByte);
|
|
1480
|
-
const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
|
|
1481
|
-
if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
|
|
1482
|
-
throw ERRORS.TypedError(
|
|
1483
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1484
|
-
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
offset = nextOffset;
|
|
1488
|
-
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1489
|
-
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
private getProtocolV2FirmwareTransferTransport() {
|
|
1496
|
-
const env = DataManager.getSettings('env');
|
|
1497
|
-
if (env && DataManager.isBleConnect(env)) {
|
|
1498
|
-
return 'BLE';
|
|
1499
|
-
}
|
|
1500
|
-
if (
|
|
1501
|
-
env &&
|
|
1502
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1503
|
-
) {
|
|
1504
|
-
return 'WebUSB';
|
|
1505
|
-
}
|
|
1506
|
-
return env ?? 'unknown';
|
|
1993
|
+
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
1994
|
+
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1507
1995
|
}
|
|
1508
1996
|
|
|
1509
1997
|
private async fileWriteChunk(
|