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