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