@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.41
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__/AllNetworkGetAddressBase.tracing.test.ts +0 -78
- package/__tests__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -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 +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +12 -132
- package/__tests__/homescreen.test.ts +1 -41
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +18 -259
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -86
- package/__tests__/protocol-v2.test.ts +632 -333
- package/__tests__/public-pro2-api-boundary.test.ts +0 -1
- package/dist/api/BaseMethod.d.ts +1 -3
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.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 +22 -5
- 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/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +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/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- 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 +0 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +0 -2
- package/dist/core/deviceEventRegistration.d.ts.map +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 +4 -22
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +0 -4
- 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 +2 -27
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +181 -88
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3162 -1614
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -4
- 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 +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +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 +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +10 -3
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +0 -3
- package/dist/types/api/protocolV2.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 +0 -2
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +0 -4
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts +0 -10
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +10 -8
- package/src/api/CheckAllFirmwareRelease.ts +51 -6
- package/src/api/FirmwareUpdateV2.ts +298 -120
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +748 -281
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -54
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/device/DeviceUpdateBootloader.ts +122 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +57 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +745 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +4 -11
- package/src/api/index.ts +0 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
- package/src/core/deviceEventRegistration.ts +0 -4
- package/src/core/index.ts +75 -118
- package/src/data/messages/messages-protocol-v2.json +0 -10
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +21 -193
- package/src/events/device.ts +0 -4
- package/src/events/logBlockEvent.ts +0 -1
- package/src/events/ui-request.ts +2 -32
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -3
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/features.ts +2 -9
- package/src/protocols/protocol-v2/lockedError.ts +10 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +5 -31
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
- package/src/protocols/protocol-v2/walletSession.ts +18 -93
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -2
- package/src/types/api/protocolV2.ts +0 -13
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +0 -4
- package/src/utils/homescreen.ts +6 -32
- package/src/utils/index.ts +1 -6
- package/src/utils/pro2Wallpaper.ts +8 -35
- package/__tests__/deviceUploadNft.test.ts +0 -187
- package/__tests__/pro2Nft.test.ts +0 -108
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -56
- package/__tests__/uiProgressThrottle.test.ts +0 -74
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -30
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
- package/dist/utils/pro2Nft.d.ts +0 -29
- package/dist/utils/pro2Nft.d.ts.map +0 -1
- package/dist/utils/uiProgressThrottle.d.ts +0 -3
- package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +0 -166
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -117
- package/src/utils/pro2Nft.ts +0 -91
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getLogger,
|
|
20
20
|
} from '../utils';
|
|
21
21
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
22
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
22
23
|
import { DataManager } from '../data-manager';
|
|
23
24
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
24
25
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -31,11 +32,20 @@ import {
|
|
|
31
32
|
getProtocolV2UnknownErrorText,
|
|
32
33
|
isProtocolV2DeviceDisconnectedError,
|
|
33
34
|
} from './protocol-v2/helpers';
|
|
35
|
+
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
36
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
37
|
+
import {
|
|
38
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
39
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
40
|
+
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
34
41
|
|
|
35
|
-
import type {
|
|
42
|
+
import type {
|
|
43
|
+
FirmwareArtifactReference,
|
|
44
|
+
FirmwareUpdateV4Params,
|
|
45
|
+
FirmwareUpdateV4Target,
|
|
46
|
+
} from '../types/api/firmwareUpdate';
|
|
36
47
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
37
48
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
38
|
-
import type { PROTO } from '../constants';
|
|
39
49
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
40
50
|
import type {
|
|
41
51
|
Features,
|
|
@@ -43,6 +53,7 @@ import type {
|
|
|
43
53
|
IProtocolV2FirmwareComponent,
|
|
44
54
|
IVersionArray,
|
|
45
55
|
} from '../types';
|
|
56
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
46
57
|
|
|
47
58
|
const Log = getLogger(LoggerNames.Method);
|
|
48
59
|
|
|
@@ -86,11 +97,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
86
97
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
87
98
|
};
|
|
88
99
|
|
|
89
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
90
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
91
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
100
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
95
101
|
target_id: number | string;
|
|
96
102
|
status?: number | string;
|
|
@@ -104,17 +110,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
104
110
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
105
111
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
106
112
|
};
|
|
107
|
-
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
108
|
-
path: string;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
112
|
-
filePath: string;
|
|
113
|
-
processedSize?: number;
|
|
114
|
-
totalSize?: number;
|
|
115
|
-
onTransferredBytes?: (transferredBytes: number) => void;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
113
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
119
114
|
type ProtocolV2ResourceBundleBinary = {
|
|
120
115
|
name: string;
|
|
@@ -137,6 +132,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
137
132
|
kind: 'bootloader' | 'firmware';
|
|
138
133
|
};
|
|
139
134
|
|
|
135
|
+
type ProtocolV2InstallSource = {
|
|
136
|
+
fileName: string;
|
|
137
|
+
source: FirmwareByteSource;
|
|
138
|
+
targetId: number;
|
|
139
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type ProtocolV2ResourceBundleSource = {
|
|
143
|
+
name: string;
|
|
144
|
+
source: FirmwareByteSource;
|
|
145
|
+
devicePath: string;
|
|
146
|
+
version?: IVersionArray;
|
|
147
|
+
payloadHash?: string;
|
|
148
|
+
headerHash?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
type ProtocolV2ExecutionPhaseKind =
|
|
152
|
+
| 'resource-sync'
|
|
153
|
+
| 'bootloader-install'
|
|
154
|
+
| 'bootloader-verify'
|
|
155
|
+
| 'component-install'
|
|
156
|
+
| 'final-verify';
|
|
157
|
+
|
|
158
|
+
type ProtocolV2ExecutionPhase = {
|
|
159
|
+
kind: ProtocolV2ExecutionPhaseKind;
|
|
160
|
+
installSources: ProtocolV2InstallSource[];
|
|
161
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
162
|
+
};
|
|
163
|
+
|
|
140
164
|
type ProtocolV2OkppHeader = {
|
|
141
165
|
type: string;
|
|
142
166
|
version: IVersionArray;
|
|
@@ -409,6 +433,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
409
433
|
return ['V2'] as const;
|
|
410
434
|
}
|
|
411
435
|
|
|
436
|
+
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
437
|
+
|
|
438
|
+
private protocolV2ExecutionInLoader = false;
|
|
439
|
+
|
|
440
|
+
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
441
|
+
|
|
442
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
443
|
+
|
|
444
|
+
private protocolV2FinalStatusVerified = false;
|
|
445
|
+
|
|
412
446
|
init() {
|
|
413
447
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
414
448
|
this.requireDeviceMode = [];
|
|
@@ -446,10 +480,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
446
480
|
{ name: 'firmwareType', type: 'string' },
|
|
447
481
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
448
482
|
{ name: 'platform', type: 'string' },
|
|
483
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
449
484
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
450
485
|
]);
|
|
486
|
+
if (
|
|
487
|
+
payload.expectedDeviceId !== undefined &&
|
|
488
|
+
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
489
|
+
) {
|
|
490
|
+
throw ERRORS.TypedError(
|
|
491
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
492
|
+
'Protocol V2 expected device identity is invalid'
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
const hostBinding =
|
|
496
|
+
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
497
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
498
|
+
: undefined;
|
|
499
|
+
if (hostBinding) {
|
|
500
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
501
|
+
preparedPlan: payload.preparedPlan,
|
|
502
|
+
executor: 'v4',
|
|
503
|
+
platform: payload.platform,
|
|
504
|
+
scopeTargets: [
|
|
505
|
+
'boot',
|
|
506
|
+
'app_v1',
|
|
507
|
+
'app_v2',
|
|
508
|
+
'coprocessor',
|
|
509
|
+
'resource',
|
|
510
|
+
'se01',
|
|
511
|
+
'se02',
|
|
512
|
+
'se03',
|
|
513
|
+
'se04',
|
|
514
|
+
],
|
|
515
|
+
bindings: [
|
|
516
|
+
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
517
|
+
artifact
|
|
518
|
+
? [
|
|
519
|
+
{
|
|
520
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
521
|
+
artifact,
|
|
522
|
+
},
|
|
523
|
+
]
|
|
524
|
+
: []
|
|
525
|
+
),
|
|
526
|
+
...(payload.resourceBundleArtifacts ?? []).map(
|
|
527
|
+
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
528
|
+
target: 'resource' as const,
|
|
529
|
+
logicalName: entry.name,
|
|
530
|
+
artifact: entry.artifact,
|
|
531
|
+
})
|
|
532
|
+
),
|
|
533
|
+
],
|
|
534
|
+
});
|
|
535
|
+
}
|
|
451
536
|
|
|
452
537
|
this.params = {
|
|
538
|
+
preparedPlan: payload.preparedPlan,
|
|
453
539
|
chunkSize: payload.chunkSize,
|
|
454
540
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
455
541
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -464,7 +550,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
464
550
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
465
551
|
firmwareType: payload.firmwareType,
|
|
466
552
|
targetsToUpdate: payload.targetsToUpdate,
|
|
553
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
467
554
|
platform: payload.platform,
|
|
555
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
556
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
557
|
+
componentArtifacts: payload.componentArtifacts,
|
|
558
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
468
559
|
};
|
|
469
560
|
}
|
|
470
561
|
|
|
@@ -498,6 +589,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
498
589
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
499
590
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
500
591
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
592
|
+
this.validateExpectedTargetVersions();
|
|
593
|
+
|
|
594
|
+
if (
|
|
595
|
+
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
596
|
+
this.params.resourceBundleArtifacts?.length
|
|
597
|
+
) {
|
|
598
|
+
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
599
|
+
}
|
|
501
600
|
|
|
502
601
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
503
602
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -508,6 +607,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
508
607
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
509
608
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
510
609
|
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
610
|
+
if (
|
|
611
|
+
this.params.artifactReader ||
|
|
612
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
613
|
+
) {
|
|
614
|
+
throw ERRORS.TypedError(
|
|
615
|
+
HardwareErrorCode.RuntimeError,
|
|
616
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
617
|
+
{
|
|
618
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
}
|
|
511
622
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
512
623
|
firmwareType,
|
|
513
624
|
deviceFeatures
|
|
@@ -516,13 +627,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
516
627
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
517
628
|
installItems = remoteBinaries.installItems;
|
|
518
629
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
630
|
+
const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
|
|
631
|
+
firmwareType,
|
|
632
|
+
deviceFeatures
|
|
633
|
+
);
|
|
634
|
+
resourceBundles = resourceDescriptors?.length
|
|
635
|
+
? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
|
|
636
|
+
: undefined;
|
|
523
637
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
524
638
|
} catch (err) {
|
|
525
|
-
throw
|
|
639
|
+
throw normalizeFirmwarePreparationError(err);
|
|
526
640
|
}
|
|
527
641
|
|
|
528
642
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
@@ -532,22 +646,289 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
532
646
|
);
|
|
533
647
|
}
|
|
534
648
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
await this.executeProtocolV2Update({
|
|
649
|
+
return this.executeProtocolV2Update({
|
|
538
650
|
fwBinaryMap,
|
|
539
651
|
bootloaderBinary,
|
|
540
652
|
...(installItems ? { installItems } : undefined),
|
|
541
653
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
542
654
|
});
|
|
655
|
+
}
|
|
543
656
|
|
|
544
|
-
|
|
657
|
+
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
658
|
+
const source = await openFirmwareByteSource({
|
|
659
|
+
artifact,
|
|
660
|
+
reader: this.params.artifactReader,
|
|
661
|
+
});
|
|
662
|
+
if (!source) {
|
|
663
|
+
throw ERRORS.TypedError(
|
|
664
|
+
HardwareErrorCode.RuntimeError,
|
|
665
|
+
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
666
|
+
{
|
|
667
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
this.protocolV2PreparedSources.push(source);
|
|
672
|
+
return source;
|
|
673
|
+
}
|
|
545
674
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
675
|
+
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
676
|
+
const source = await openFirmwareByteSource({ binary });
|
|
677
|
+
if (!source) {
|
|
678
|
+
throw ERRORS.TypedError(
|
|
679
|
+
HardwareErrorCode.RuntimeError,
|
|
680
|
+
'Protocol V2 firmware binary is empty'
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
this.protocolV2PreparedSources.push(source);
|
|
684
|
+
return source;
|
|
685
|
+
}
|
|
549
686
|
|
|
550
|
-
|
|
687
|
+
private async closeProtocolV2PreparedSources() {
|
|
688
|
+
const sources = this.protocolV2PreparedSources.splice(0);
|
|
689
|
+
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
private async prepareProtocolV2InstallSources(
|
|
693
|
+
firmwareType: EFirmwareType,
|
|
694
|
+
features: Features
|
|
695
|
+
): Promise<ProtocolV2InstallSource[]> {
|
|
696
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
697
|
+
if (!release) {
|
|
698
|
+
throw ERRORS.TypedError(
|
|
699
|
+
HardwareErrorCode.RuntimeError,
|
|
700
|
+
'Protocol V2 firmware release is unavailable'
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
704
|
+
const requestedTargets = new Set(
|
|
705
|
+
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
706
|
+
);
|
|
707
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
708
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
709
|
+
|
|
710
|
+
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
711
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
712
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
713
|
+
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
714
|
+
const artifact = componentArtifacts[updateTarget];
|
|
715
|
+
if (!artifact) {
|
|
716
|
+
throw ERRORS.TypedError(
|
|
717
|
+
HardwareErrorCode.RuntimeError,
|
|
718
|
+
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
719
|
+
{
|
|
720
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
721
|
+
artifactName: updateTarget,
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
installSources.push({
|
|
726
|
+
...target,
|
|
727
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
728
|
+
});
|
|
729
|
+
preparedTargets.add(updateTarget);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
734
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
735
|
+
);
|
|
736
|
+
if (unknownTarget) {
|
|
737
|
+
throw ERRORS.TypedError(
|
|
738
|
+
HardwareErrorCode.RuntimeError,
|
|
739
|
+
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
return installSources;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private async prepareProtocolV2ResourceSources(
|
|
746
|
+
firmwareType: EFirmwareType,
|
|
747
|
+
features: Features
|
|
748
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
749
|
+
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
750
|
+
const resourceRequested =
|
|
751
|
+
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
752
|
+
if (!resourceRequested) {
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
756
|
+
const descriptors = release?.resourceBundles ?? [];
|
|
757
|
+
const artifactByName = new Map(
|
|
758
|
+
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
759
|
+
);
|
|
760
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
761
|
+
for (const descriptor of descriptors) {
|
|
762
|
+
const artifact = artifactByName.get(descriptor.name);
|
|
763
|
+
if (!artifact) {
|
|
764
|
+
throw ERRORS.TypedError(
|
|
765
|
+
HardwareErrorCode.RuntimeError,
|
|
766
|
+
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
767
|
+
{
|
|
768
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
769
|
+
artifactName: descriptor.name,
|
|
770
|
+
}
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
774
|
+
descriptor.devicePath,
|
|
775
|
+
'resourceBundles[].devicePath'
|
|
776
|
+
);
|
|
777
|
+
sources.push({
|
|
778
|
+
name: descriptor.name,
|
|
779
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
780
|
+
devicePath,
|
|
781
|
+
version: descriptor.version,
|
|
782
|
+
payloadHash: descriptor.payloadHash,
|
|
783
|
+
headerHash: descriptor.headerHash,
|
|
784
|
+
});
|
|
785
|
+
artifactByName.delete(descriptor.name);
|
|
786
|
+
}
|
|
787
|
+
if (artifactByName.size > 0) {
|
|
788
|
+
throw ERRORS.TypedError(
|
|
789
|
+
HardwareErrorCode.RuntimeError,
|
|
790
|
+
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return sources;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
797
|
+
try {
|
|
798
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
799
|
+
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
800
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
801
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
802
|
+
throw ERRORS.TypedError(
|
|
803
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
804
|
+
'No firmware to update'
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
808
|
+
|
|
809
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
810
|
+
installSources,
|
|
811
|
+
resourceSources,
|
|
812
|
+
});
|
|
813
|
+
} finally {
|
|
814
|
+
await this.closeProtocolV2PreparedSources();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private validateExpectedTargetVersions() {
|
|
819
|
+
const expected = this.params.expectedTargetVersions;
|
|
820
|
+
if (expected === undefined) return;
|
|
821
|
+
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
822
|
+
throw ERRORS.TypedError(
|
|
823
|
+
HardwareErrorCode.RuntimeError,
|
|
824
|
+
'Protocol V2 expected target versions are invalid'
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
for (const [target, version] of Object.entries(expected)) {
|
|
828
|
+
if (
|
|
829
|
+
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
830
|
+
target as FirmwareUpdateV4Target
|
|
831
|
+
) ||
|
|
832
|
+
typeof version !== 'string' ||
|
|
833
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
834
|
+
version.length > 64
|
|
835
|
+
) {
|
|
836
|
+
throw ERRORS.TypedError(
|
|
837
|
+
HardwareErrorCode.RuntimeError,
|
|
838
|
+
'Protocol V2 expected target version is invalid'
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
845
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
846
|
+
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
847
|
+
if (!version) return undefined;
|
|
848
|
+
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
849
|
+
if (
|
|
850
|
+
parts.length !== 3 ||
|
|
851
|
+
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
852
|
+
) {
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
859
|
+
const expected = this.params?.expectedTargetVersions;
|
|
860
|
+
if (!expected) return;
|
|
861
|
+
const features = this.protocolV2LatestFinalFeatures;
|
|
862
|
+
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
863
|
+
const applicationTargets = requestedTargets.filter(
|
|
864
|
+
target => target === 'app_v1' || target === 'app_v2'
|
|
865
|
+
);
|
|
866
|
+
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
867
|
+
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
868
|
+
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
869
|
+
se01: features?.se01Version ?? undefined,
|
|
870
|
+
se02: features?.se02Version ?? undefined,
|
|
871
|
+
se03: features?.se03Version ?? undefined,
|
|
872
|
+
se04: features?.se04Version ?? undefined,
|
|
873
|
+
};
|
|
874
|
+
if (features && applicationTargets.length === 1) {
|
|
875
|
+
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
876
|
+
}
|
|
877
|
+
const expectedEntries = (
|
|
878
|
+
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
879
|
+
).filter(([target]) => !targets || targets.includes(target));
|
|
880
|
+
for (const [target, expectedVersion] of expectedEntries) {
|
|
881
|
+
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
882
|
+
([, mappedTarget]) => mappedTarget === target
|
|
883
|
+
)?.[0];
|
|
884
|
+
const statusVersion =
|
|
885
|
+
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
886
|
+
const observedVersion =
|
|
887
|
+
statusVersion === undefined
|
|
888
|
+
? visibleVersions[target]
|
|
889
|
+
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
890
|
+
Math.floor(statusVersion / 0x100) % 0x100
|
|
891
|
+
}.${statusVersion % 0x100}`;
|
|
892
|
+
if (!observedVersion) {
|
|
893
|
+
if (this.protocolV2FinalStatusVerified) {
|
|
894
|
+
Log.warn(
|
|
895
|
+
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
896
|
+
);
|
|
897
|
+
} else {
|
|
898
|
+
throw ERRORS.TypedError(
|
|
899
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
900
|
+
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
} else if (observedVersion !== expectedVersion) {
|
|
904
|
+
throw ERRORS.TypedError(
|
|
905
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
906
|
+
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
913
|
+
if (this.params.targetsToUpdate?.length) {
|
|
914
|
+
return [...new Set(this.params.targetsToUpdate)];
|
|
915
|
+
}
|
|
916
|
+
const targets = new Set<FirmwareUpdateV4Target>();
|
|
917
|
+
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
918
|
+
targets.add(target as FirmwareUpdateV4Target)
|
|
919
|
+
);
|
|
920
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
921
|
+
targets.add('resource');
|
|
922
|
+
}
|
|
923
|
+
if (this.params.bootloaderBinary) targets.add('boot');
|
|
924
|
+
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
925
|
+
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
926
|
+
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
927
|
+
if (this.params.se01Binary) targets.add('se01');
|
|
928
|
+
if (this.params.se02Binary) targets.add('se02');
|
|
929
|
+
if (this.params.se03Binary) targets.add('se03');
|
|
930
|
+
if (this.params.se04Binary) targets.add('se04');
|
|
931
|
+
return Array.from(targets);
|
|
551
932
|
}
|
|
552
933
|
|
|
553
934
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -583,7 +964,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
583
964
|
private async captureProtocolV2PhysicalIdentity() {
|
|
584
965
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
585
966
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
586
|
-
|
|
967
|
+
if (this.params?.preparedPlan) {
|
|
968
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
969
|
+
preparedPlan: this.params.preparedPlan,
|
|
970
|
+
deviceIdentity: serialNumber,
|
|
971
|
+
});
|
|
972
|
+
} else if (this.params?.expectedDeviceId) {
|
|
973
|
+
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
974
|
+
} else {
|
|
975
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
976
|
+
}
|
|
587
977
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
588
978
|
}
|
|
589
979
|
|
|
@@ -854,23 +1244,42 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
854
1244
|
})) as ProtocolV2ResourceBundleBinary[];
|
|
855
1245
|
}
|
|
856
1246
|
|
|
857
|
-
private async
|
|
1247
|
+
private async materializeProtocolV2ResourceBundles(
|
|
858
1248
|
bundles: ProtocolV2ResourceBundleBinary[]
|
|
859
1249
|
): Promise<ProtocolV2ResourceBundleBinary[]> {
|
|
860
|
-
const prefetched: ProtocolV2ResourceBundleBinary[] = [];
|
|
861
1250
|
for (const bundle of bundles) {
|
|
862
1251
|
let { binary } = bundle;
|
|
863
1252
|
let downloadedFromRemote = false;
|
|
864
1253
|
if (binary.byteLength === 0) {
|
|
865
1254
|
if (!bundle.url) {
|
|
866
|
-
throw
|
|
1255
|
+
throw ERRORS.TypedError(
|
|
1256
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1257
|
+
`Missing Protocol V2 RESC bundle binary: ${bundle.name}`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
if (
|
|
1261
|
+
this.params.artifactReader ||
|
|
1262
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
1263
|
+
) {
|
|
1264
|
+
throw ERRORS.TypedError(
|
|
1265
|
+
HardwareErrorCode.RuntimeError,
|
|
1266
|
+
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1267
|
+
{
|
|
1268
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1269
|
+
artifactName: bundle.name,
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
867
1272
|
}
|
|
868
1273
|
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
869
1274
|
({ binary } = await getSysResourceBinary(bundle.url));
|
|
1275
|
+
bundle.binary = binary;
|
|
870
1276
|
downloadedFromRemote = true;
|
|
871
1277
|
}
|
|
872
|
-
if (binary.byteLength === 0) {
|
|
873
|
-
throw
|
|
1278
|
+
if (bundle.binary.byteLength === 0) {
|
|
1279
|
+
throw ERRORS.TypedError(
|
|
1280
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1281
|
+
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1282
|
+
);
|
|
874
1283
|
}
|
|
875
1284
|
if (downloadedFromRemote) {
|
|
876
1285
|
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
@@ -889,82 +1298,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
889
1298
|
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
890
1299
|
}
|
|
891
1300
|
}
|
|
892
|
-
prefetched.push({ ...bundle, binary });
|
|
893
1301
|
}
|
|
894
|
-
return
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* Synchronize RESC bundles to the device.
|
|
899
|
-
*
|
|
900
|
-
* Manual mode writes supplied binaries directly.
|
|
901
|
-
* Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
|
|
902
|
-
*/
|
|
903
|
-
private async syncProtocolV2ResourceBundles(
|
|
904
|
-
bundles: ProtocolV2ResourceBundleBinary[],
|
|
905
|
-
firmwareSize: number
|
|
906
|
-
): Promise<{ processedSize: number; totalSize: number }> {
|
|
907
|
-
const transferStartTime = Date.now();
|
|
908
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
909
|
-
|
|
910
|
-
const isRemoteMode = bundles.some(bundle => !!bundle.url);
|
|
911
|
-
let bundlesToSync = bundles;
|
|
912
|
-
|
|
913
|
-
// Remote binaries were prefetched before bootloader entry. Only device-local
|
|
914
|
-
// header comparison and transfer are allowed in this phase.
|
|
915
|
-
if (isRemoteMode) {
|
|
916
|
-
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
917
|
-
for (const bundle of bundles) {
|
|
918
|
-
// Compare the existing device header.
|
|
919
|
-
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
920
|
-
if (upToDate) {
|
|
921
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
922
|
-
} else {
|
|
923
|
-
filtered.push(bundle);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
bundlesToSync = filtered;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
if (bundlesToSync.length === 0) {
|
|
930
|
-
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
931
|
-
return { processedSize: 0, totalSize: firmwareSize };
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Write directly to the device through FileWrite.
|
|
935
|
-
let totalSize = 0;
|
|
936
|
-
for (const b of bundlesToSync) totalSize += b.binary.byteLength;
|
|
937
|
-
|
|
938
|
-
const transferTotalSize = totalSize + firmwareSize;
|
|
939
|
-
let processedSize = 0;
|
|
940
|
-
for (const bundle of bundlesToSync) {
|
|
941
|
-
Log.log(
|
|
942
|
-
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
943
|
-
);
|
|
944
|
-
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
945
|
-
payload: bundle.binary,
|
|
946
|
-
filePath: bundle.devicePath,
|
|
947
|
-
processedSize,
|
|
948
|
-
totalSize: transferTotalSize,
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
953
|
-
Log.log(
|
|
954
|
-
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
955
|
-
elapsedMs / 1000
|
|
956
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
957
|
-
);
|
|
958
|
-
return { processedSize, totalSize: transferTotalSize };
|
|
1302
|
+
return bundles;
|
|
959
1303
|
}
|
|
960
1304
|
|
|
961
1305
|
/**
|
|
962
1306
|
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
963
1307
|
*/
|
|
964
1308
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
965
|
-
bundle:
|
|
1309
|
+
bundle: Pick<
|
|
1310
|
+
ProtocolV2ResourceBundleBinary,
|
|
1311
|
+
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1312
|
+
>
|
|
966
1313
|
): Promise<boolean> {
|
|
967
|
-
if (this.params
|
|
1314
|
+
if (this.params?.forcedUpdateRes) return false;
|
|
968
1315
|
if (!bundle.version && !bundle.payloadHash) return false;
|
|
969
1316
|
|
|
970
1317
|
try {
|
|
@@ -1015,10 +1362,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1015
1362
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1016
1363
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1017
1364
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1365
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1018
1366
|
return false;
|
|
1019
1367
|
}
|
|
1020
1368
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1021
1369
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1370
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1022
1371
|
return false;
|
|
1023
1372
|
}
|
|
1024
1373
|
|
|
@@ -1028,6 +1377,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1028
1377
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1029
1378
|
await wait(1000);
|
|
1030
1379
|
await this.waitForProtocolV2BootloaderMode();
|
|
1380
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1031
1381
|
return true;
|
|
1032
1382
|
} catch (error) {
|
|
1033
1383
|
if (error instanceof HardwareError) {
|
|
@@ -1117,6 +1467,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1117
1467
|
return entries;
|
|
1118
1468
|
}
|
|
1119
1469
|
|
|
1470
|
+
private buildProtocolV2ExecutionPhases({
|
|
1471
|
+
installSources,
|
|
1472
|
+
resourceSources,
|
|
1473
|
+
}: {
|
|
1474
|
+
installSources: ProtocolV2InstallSource[];
|
|
1475
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1476
|
+
}): ProtocolV2ExecutionPhase[] {
|
|
1477
|
+
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1478
|
+
if (resourceSources.length > 0) {
|
|
1479
|
+
phases.push({
|
|
1480
|
+
kind: 'resource-sync',
|
|
1481
|
+
installSources: [],
|
|
1482
|
+
resourceSources,
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1486
|
+
if (bootloaderSources.length > 0) {
|
|
1487
|
+
phases.push(
|
|
1488
|
+
{
|
|
1489
|
+
kind: 'bootloader-install',
|
|
1490
|
+
installSources: bootloaderSources,
|
|
1491
|
+
resourceSources: [],
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
kind: 'bootloader-verify',
|
|
1495
|
+
installSources: [],
|
|
1496
|
+
resourceSources: [],
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1501
|
+
if (componentSources.length > 0) {
|
|
1502
|
+
phases.push({
|
|
1503
|
+
kind: 'component-install',
|
|
1504
|
+
installSources: componentSources,
|
|
1505
|
+
resourceSources: [],
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
phases.push({
|
|
1509
|
+
kind: 'final-verify',
|
|
1510
|
+
installSources: [],
|
|
1511
|
+
resourceSources: [],
|
|
1512
|
+
});
|
|
1513
|
+
return phases;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
private async executeProtocolV2Phases({
|
|
1517
|
+
installSources,
|
|
1518
|
+
resourceSources,
|
|
1519
|
+
}: {
|
|
1520
|
+
installSources: ProtocolV2InstallSource[];
|
|
1521
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1522
|
+
}) {
|
|
1523
|
+
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1524
|
+
installSources,
|
|
1525
|
+
resourceSources,
|
|
1526
|
+
});
|
|
1527
|
+
for (const phase of phases) {
|
|
1528
|
+
if (phase.kind === 'final-verify') {
|
|
1529
|
+
return this.completeProtocolV2FinalVerification();
|
|
1530
|
+
}
|
|
1531
|
+
if (phase.kind === 'resource-sync') {
|
|
1532
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1533
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1534
|
+
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1535
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1536
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1537
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1538
|
+
} else if (phase.kind === 'bootloader-verify') {
|
|
1539
|
+
await this.waitForProtocolV2FinalFeatures();
|
|
1540
|
+
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
throw ERRORS.TypedError(
|
|
1544
|
+
HardwareErrorCode.RuntimeError,
|
|
1545
|
+
'Protocol V2 execution has no final verification phase'
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
private async executeProtocolV2SourceUpdate({
|
|
1550
|
+
installSources,
|
|
1551
|
+
resourceSources,
|
|
1552
|
+
}: {
|
|
1553
|
+
installSources: ProtocolV2InstallSource[];
|
|
1554
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1555
|
+
}) {
|
|
1556
|
+
return this.executeProtocolV2Phases({
|
|
1557
|
+
installSources,
|
|
1558
|
+
resourceSources,
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1120
1562
|
private async executeProtocolV2Update({
|
|
1121
1563
|
fwBinaryMap,
|
|
1122
1564
|
bootloaderBinary,
|
|
@@ -1128,104 +1570,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1128
1570
|
installItems?: ProtocolV2InstallItem[];
|
|
1129
1571
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1130
1572
|
}) {
|
|
1131
|
-
const
|
|
1573
|
+
const memoryInstallItems =
|
|
1132
1574
|
installItems ??
|
|
1133
1575
|
this.buildProtocolV2InstallItems({
|
|
1134
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1135
1576
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1577
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1136
1578
|
});
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1147
|
-
resourceBundles
|
|
1148
|
-
|
|
1579
|
+
try {
|
|
1580
|
+
const installSources = await Promise.all(
|
|
1581
|
+
memoryInstallItems.map(async item => ({
|
|
1582
|
+
fileName: item.fileName,
|
|
1583
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1584
|
+
targetId: item.targetId,
|
|
1585
|
+
kind: item.kind,
|
|
1586
|
+
}))
|
|
1587
|
+
);
|
|
1588
|
+
const resourceSources = await Promise.all(
|
|
1589
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1590
|
+
name: bundle.name,
|
|
1591
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1592
|
+
devicePath: bundle.devicePath,
|
|
1593
|
+
version: bundle.version,
|
|
1594
|
+
payloadHash: bundle.payloadHash,
|
|
1595
|
+
headerHash: bundle.headerHash,
|
|
1596
|
+
}))
|
|
1149
1597
|
);
|
|
1150
|
-
|
|
1151
|
-
|
|
1598
|
+
return await this.executeProtocolV2Phases({
|
|
1599
|
+
installSources,
|
|
1600
|
+
resourceSources,
|
|
1601
|
+
});
|
|
1602
|
+
} finally {
|
|
1603
|
+
await this.closeProtocolV2PreparedSources();
|
|
1152
1604
|
}
|
|
1605
|
+
}
|
|
1153
1606
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1607
|
+
private async executeProtocolV2TransferPhase({
|
|
1608
|
+
installSources,
|
|
1609
|
+
resourceSources,
|
|
1610
|
+
}: ProtocolV2ExecutionPhase) {
|
|
1611
|
+
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1612
|
+
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1613
|
+
for (const resource of resourceSources) {
|
|
1614
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1615
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1616
|
+
} else {
|
|
1617
|
+
resourcesToSync.push(resource);
|
|
1618
|
+
totalSize += resource.source.size;
|
|
1159
1619
|
}
|
|
1160
|
-
return;
|
|
1161
1620
|
}
|
|
1162
|
-
let transferredSize = processedSize;
|
|
1163
|
-
const transferStartTime = Date.now();
|
|
1164
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1165
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1166
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1167
|
-
transferredSize = bytes;
|
|
1168
|
-
};
|
|
1169
|
-
Log.log(
|
|
1170
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1171
|
-
);
|
|
1172
1621
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
processedSize,
|
|
1185
|
-
totalSize,
|
|
1186
|
-
onTransferredBytes,
|
|
1187
|
-
});
|
|
1188
|
-
transferredSize = processedSize;
|
|
1189
|
-
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1190
|
-
|
|
1191
|
-
stagedInstallTargets.push({
|
|
1192
|
-
...item,
|
|
1193
|
-
path: filePath,
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1622
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1623
|
+
let processedSize = 0;
|
|
1624
|
+
for (const resource of resourcesToSync) {
|
|
1625
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1626
|
+
source: resource.source,
|
|
1627
|
+
filePath: resource.devicePath,
|
|
1628
|
+
processedSize,
|
|
1629
|
+
totalSize,
|
|
1630
|
+
});
|
|
1631
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
1632
|
+
}
|
|
1196
1633
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1634
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
1635
|
+
for (const item of installSources) {
|
|
1636
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1637
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1638
|
+
source: item.source,
|
|
1639
|
+
filePath,
|
|
1640
|
+
processedSize,
|
|
1641
|
+
totalSize,
|
|
1642
|
+
});
|
|
1643
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1644
|
+
stagedInstallTargets.push({
|
|
1645
|
+
targetId: item.targetId,
|
|
1646
|
+
path: filePath,
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1200
1649
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
);
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1209
|
-
Log.warn(
|
|
1210
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1211
|
-
elapsedMs / 1000
|
|
1212
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1213
|
-
);
|
|
1214
|
-
throw error;
|
|
1650
|
+
if (totalSize > 0) {
|
|
1651
|
+
this.postProgressMessage(100, 'transferData');
|
|
1652
|
+
}
|
|
1653
|
+
if (stagedInstallTargets.length === 0) {
|
|
1654
|
+
return;
|
|
1215
1655
|
}
|
|
1216
1656
|
|
|
1217
1657
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1218
|
-
|
|
1219
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1658
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1220
1659
|
target_id: item.targetId,
|
|
1221
1660
|
path: item.path,
|
|
1222
1661
|
}));
|
|
1223
|
-
|
|
1224
|
-
await this.
|
|
1225
|
-
|
|
1662
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1663
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
private async protocolV2SourceUpdateProcess({
|
|
1667
|
+
source,
|
|
1668
|
+
filePath,
|
|
1669
|
+
processedSize,
|
|
1670
|
+
totalSize,
|
|
1671
|
+
}: {
|
|
1672
|
+
source: FirmwareByteSource;
|
|
1673
|
+
filePath: string;
|
|
1674
|
+
processedSize: number;
|
|
1675
|
+
totalSize: number;
|
|
1676
|
+
}) {
|
|
1677
|
+
let lastError: unknown;
|
|
1678
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1679
|
+
try {
|
|
1680
|
+
await writeFirmwareByteSource({
|
|
1681
|
+
source,
|
|
1682
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1683
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
1684
|
+
const chunkEnd = sourceOffset + length;
|
|
1685
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1686
|
+
processedSize + sourceOffset,
|
|
1687
|
+
processedSize + chunkEnd,
|
|
1688
|
+
totalSize
|
|
1689
|
+
);
|
|
1690
|
+
const response = await this.fileWriteChunk(
|
|
1691
|
+
filePath,
|
|
1692
|
+
source.size,
|
|
1693
|
+
sourceOffset,
|
|
1694
|
+
data,
|
|
1695
|
+
first,
|
|
1696
|
+
progress
|
|
1697
|
+
);
|
|
1698
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
1699
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1700
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1701
|
+
throw ERRORS.TypedError(
|
|
1702
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1703
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
this.postProgressMessage(progress, 'transferData');
|
|
1707
|
+
return length;
|
|
1708
|
+
},
|
|
1709
|
+
});
|
|
1710
|
+
return processedSize + source.size;
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
lastError = error;
|
|
1713
|
+
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1714
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
throw ERRORS.TypedError(
|
|
1719
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1720
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1721
|
+
);
|
|
1226
1722
|
}
|
|
1227
1723
|
|
|
1228
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1724
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1229
1725
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1230
1726
|
}
|
|
1231
1727
|
|
|
@@ -1244,7 +1740,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1244
1740
|
|
|
1245
1741
|
private assertProtocolV2TargetStatus(
|
|
1246
1742
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1247
|
-
expectedTargetIds: Set<number
|
|
1743
|
+
expectedTargetIds: Set<number>,
|
|
1744
|
+
expectedPaths = new Map<number, string>()
|
|
1248
1745
|
) {
|
|
1249
1746
|
Log.log(
|
|
1250
1747
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1275,17 +1772,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1275
1772
|
const matchingTargets = statusTargets.filter(target =>
|
|
1276
1773
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1277
1774
|
);
|
|
1775
|
+
const seenTargetIds = new Set<number>();
|
|
1776
|
+
for (const target of matchingTargets) {
|
|
1777
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1778
|
+
if (
|
|
1779
|
+
targetId === undefined ||
|
|
1780
|
+
seenTargetIds.has(targetId) ||
|
|
1781
|
+
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1782
|
+
) {
|
|
1783
|
+
throw ERRORS.TypedError(
|
|
1784
|
+
HardwareErrorCode.RuntimeError,
|
|
1785
|
+
`Protocol V2 install status conflicts with target ${target.target_id}`
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
seenTargetIds.add(targetId);
|
|
1789
|
+
}
|
|
1790
|
+
const completedTargets = matchingTargets.filter(target =>
|
|
1791
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
1792
|
+
);
|
|
1278
1793
|
const completedTargetIds = new Set<number>();
|
|
1279
|
-
|
|
1794
|
+
completedTargets.forEach(target => {
|
|
1280
1795
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1281
|
-
if (targetId !== undefined
|
|
1796
|
+
if (targetId !== undefined) {
|
|
1282
1797
|
completedTargetIds.add(targetId);
|
|
1283
1798
|
}
|
|
1284
1799
|
});
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1800
|
+
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1801
|
+
for (const target of completedTargets) {
|
|
1802
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1803
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1804
|
+
if (targetId === undefined) {
|
|
1805
|
+
throw ERRORS.TypedError(
|
|
1806
|
+
HardwareErrorCode.RuntimeError,
|
|
1807
|
+
'Protocol V2 completed target identity is invalid'
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1811
|
+
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1812
|
+
if (payloadVersion !== expectedVersion) {
|
|
1813
|
+
throw ERRORS.TypedError(
|
|
1814
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1815
|
+
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
if (payloadVersion !== undefined) {
|
|
1820
|
+
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1289
1823
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1290
1824
|
return true;
|
|
1291
1825
|
}
|
|
@@ -1309,7 +1843,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1309
1843
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1310
1844
|
targets: Array<{ target_id: number; path: string }>
|
|
1311
1845
|
) {
|
|
1846
|
+
this.protocolV2FinalStatusVerified = false;
|
|
1312
1847
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1848
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1313
1849
|
const startTime = Date.now();
|
|
1314
1850
|
let lastError: unknown;
|
|
1315
1851
|
|
|
@@ -1333,9 +1869,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1333
1869
|
if (
|
|
1334
1870
|
this.assertProtocolV2TargetStatus(
|
|
1335
1871
|
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1336
|
-
expectedTargetIds
|
|
1872
|
+
expectedTargetIds,
|
|
1873
|
+
expectedPaths
|
|
1337
1874
|
)
|
|
1338
1875
|
) {
|
|
1876
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1339
1877
|
return;
|
|
1340
1878
|
}
|
|
1341
1879
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
@@ -1374,6 +1912,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1374
1912
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1375
1913
|
throw error;
|
|
1376
1914
|
}
|
|
1915
|
+
if (
|
|
1916
|
+
error instanceof HardwareError &&
|
|
1917
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1918
|
+
) {
|
|
1919
|
+
throw error;
|
|
1920
|
+
}
|
|
1377
1921
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1378
1922
|
}
|
|
1379
1923
|
await wait(1000);
|
|
@@ -1391,6 +1935,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1391
1935
|
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1392
1936
|
// repeating it after an automatic App reboot is idempotent.
|
|
1393
1937
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1938
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1394
1939
|
}
|
|
1395
1940
|
|
|
1396
1941
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1409,6 +1954,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1409
1954
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1410
1955
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1411
1956
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1957
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1412
1958
|
if (firmwareVersion === '0.0.0') {
|
|
1413
1959
|
Log.warn(
|
|
1414
1960
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1422,6 +1968,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1422
1968
|
};
|
|
1423
1969
|
}
|
|
1424
1970
|
|
|
1971
|
+
private async completeProtocolV2FinalVerification() {
|
|
1972
|
+
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1973
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1974
|
+
}
|
|
1975
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1976
|
+
this.assertExpectedProtocolV2Versions();
|
|
1977
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1978
|
+
DevicePool.resetState();
|
|
1979
|
+
return versions;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1425
1982
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1426
1983
|
const startTime = Date.now();
|
|
1427
1984
|
let lastError: unknown;
|
|
@@ -1528,96 +2085,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1528
2085
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1529
2086
|
}
|
|
1530
2087
|
|
|
1531
|
-
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1532
|
-
let lastError: unknown;
|
|
1533
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1534
|
-
try {
|
|
1535
|
-
return await this.protocolV2WriteWholeFile(params);
|
|
1536
|
-
} catch (error) {
|
|
1537
|
-
lastError = error;
|
|
1538
|
-
Log.error(
|
|
1539
|
-
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1540
|
-
error
|
|
1541
|
-
);
|
|
1542
|
-
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1543
|
-
break;
|
|
1544
|
-
}
|
|
1545
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
throw ERRORS.TypedError(
|
|
1550
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1551
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
private async protocolV2WriteWholeFile({
|
|
1556
|
-
payload,
|
|
1557
|
-
filePath,
|
|
1558
|
-
processedSize,
|
|
1559
|
-
totalSize,
|
|
1560
|
-
onTransferredBytes,
|
|
1561
|
-
}: ProtocolV2FileTransferParams) {
|
|
1562
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1563
|
-
let offset = 0;
|
|
1564
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1565
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1566
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1567
|
-
}
|
|
1568
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1569
|
-
};
|
|
1570
|
-
|
|
1571
|
-
while (offset < payload.byteLength) {
|
|
1572
|
-
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1573
|
-
const chunkLength = chunkEnd - offset;
|
|
1574
|
-
const chunk = payload.slice(offset, chunkEnd);
|
|
1575
|
-
const overwrite = offset === 0;
|
|
1576
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1577
|
-
(processedSize ?? 0) + offset,
|
|
1578
|
-
(processedSize ?? 0) + chunkEnd,
|
|
1579
|
-
totalSize ?? payload.byteLength
|
|
1580
|
-
);
|
|
1581
|
-
|
|
1582
|
-
const writeRes = await this.fileWriteChunk(
|
|
1583
|
-
filePath,
|
|
1584
|
-
payload.byteLength,
|
|
1585
|
-
offset,
|
|
1586
|
-
chunk,
|
|
1587
|
-
overwrite,
|
|
1588
|
-
progress
|
|
1589
|
-
);
|
|
1590
|
-
const rawProcessedByte = writeRes.message.processed_byte;
|
|
1591
|
-
const processedByte = Number(rawProcessedByte);
|
|
1592
|
-
const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
|
|
1593
|
-
if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
|
|
1594
|
-
throw ERRORS.TypedError(
|
|
1595
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1596
|
-
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
offset = nextOffset;
|
|
1600
|
-
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1601
|
-
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
private getProtocolV2FirmwareTransferTransport() {
|
|
1608
|
-
const env = DataManager.getSettings('env');
|
|
1609
|
-
if (env && DataManager.isBleConnect(env)) {
|
|
1610
|
-
return 'BLE';
|
|
1611
|
-
}
|
|
1612
|
-
if (
|
|
1613
|
-
env &&
|
|
1614
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1615
|
-
) {
|
|
1616
|
-
return 'WebUSB';
|
|
1617
|
-
}
|
|
1618
|
-
return env ?? 'unknown';
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
2088
|
private async fileWriteChunk(
|
|
1622
2089
|
filePath: string,
|
|
1623
2090
|
totalFileSize: number,
|