@onekeyfe/hd-core 1.2.0-alpha.23 → 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 +35 -0
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-lifecycle-events.test.ts +59 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +30 -39
- package/__tests__/open-wallet-session.test.ts +192 -129
- package/__tests__/protocol-v2-ping.test.ts +28 -0
- package/__tests__/protocol-v2.test.ts +338 -376
- 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/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/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.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +89 -273
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +783 -2443
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.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/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 -23
- package/src/api/FirmwareUpdateV2.ts +120 -292
- package/src/api/FirmwareUpdateV3.ts +55 -256
- package/src/api/FirmwareUpdateV4.ts +297 -757
- package/src/api/OpenWalletSession.ts +23 -86
- package/src/api/device/DeviceUpdateBootloader.ts +6 -122
- 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 +39 -7
- package/src/data/messages/messages-protocol-v2.json +33 -1
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +7 -9
- package/src/device/DeviceCommands.ts +19 -0
- package/src/index.ts +0 -5
- package/src/inject.ts +4 -22
- package/src/lowLevelInject.ts +1 -5
- package/src/protocols/protocol-v2/unlockRetry.ts +20 -3
- package/src/protocols/protocol-v2/walletSession.ts +26 -8
- 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/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 +15 -3
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -86
- 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 -213
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- 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 -23
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
- 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 -392
- 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,20 +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 {
|
|
35
|
-
assertFirmwareUpdatePreparedPlanBinding,
|
|
36
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
37
|
-
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
38
33
|
|
|
39
|
-
import type {
|
|
40
|
-
FirmwareArtifactReference,
|
|
41
|
-
FirmwareUpdateV4Params,
|
|
42
|
-
FirmwareUpdateV4Target,
|
|
43
|
-
} from '../types/api/firmwareUpdate';
|
|
34
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
44
35
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
45
36
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
37
|
+
import type { PROTO } from '../constants';
|
|
46
38
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
47
39
|
import type {
|
|
48
40
|
Features,
|
|
@@ -50,7 +42,6 @@ import type {
|
|
|
50
42
|
IProtocolV2FirmwareComponent,
|
|
51
43
|
IVersionArray,
|
|
52
44
|
} from '../types';
|
|
53
|
-
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
54
45
|
|
|
55
46
|
const Log = getLogger(LoggerNames.Method);
|
|
56
47
|
|
|
@@ -94,6 +85,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
94
85
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
95
86
|
};
|
|
96
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
|
+
|
|
97
93
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
98
94
|
target_id: number | string;
|
|
99
95
|
status?: number | string;
|
|
@@ -107,6 +103,17 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
107
103
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
108
104
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
109
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
|
+
|
|
110
117
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
111
118
|
type ProtocolV2ResourceBundleBinary = {
|
|
112
119
|
name: string;
|
|
@@ -129,35 +136,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
129
136
|
kind: 'bootloader' | 'firmware';
|
|
130
137
|
};
|
|
131
138
|
|
|
132
|
-
type ProtocolV2InstallSource = {
|
|
133
|
-
fileName: string;
|
|
134
|
-
source: FirmwareByteSource;
|
|
135
|
-
targetId: number;
|
|
136
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
type ProtocolV2ResourceBundleSource = {
|
|
140
|
-
name: string;
|
|
141
|
-
source: FirmwareByteSource;
|
|
142
|
-
devicePath: string;
|
|
143
|
-
version?: IVersionArray;
|
|
144
|
-
payloadHash?: string;
|
|
145
|
-
headerHash?: string;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
type ProtocolV2ExecutionPhaseKind =
|
|
149
|
-
| 'resource-sync'
|
|
150
|
-
| 'bootloader-install'
|
|
151
|
-
| 'bootloader-verify'
|
|
152
|
-
| 'component-install'
|
|
153
|
-
| 'final-verify';
|
|
154
|
-
|
|
155
|
-
type ProtocolV2ExecutionPhase = {
|
|
156
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
157
|
-
installSources: ProtocolV2InstallSource[];
|
|
158
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
159
|
-
};
|
|
160
|
-
|
|
161
139
|
type ProtocolV2OkppHeader = {
|
|
162
140
|
type: string;
|
|
163
141
|
version: IVersionArray;
|
|
@@ -393,7 +371,10 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
393
371
|
actualSerialNumber?: string
|
|
394
372
|
) => {
|
|
395
373
|
if (!expectedSerialNumber || !actualSerialNumber) {
|
|
396
|
-
|
|
374
|
+
throw ERRORS.TypedError(
|
|
375
|
+
HardwareErrorCode.DeviceNotFound,
|
|
376
|
+
'Protocol V2 reconnect physical identity is unavailable'
|
|
377
|
+
);
|
|
397
378
|
}
|
|
398
379
|
if (actualSerialNumber !== expectedSerialNumber) {
|
|
399
380
|
throw ERRORS.TypedError(
|
|
@@ -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,62 +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
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
480
|
-
preparedPlan: payload.preparedPlan,
|
|
481
|
-
executor: 'v4',
|
|
482
|
-
platform: payload.platform,
|
|
483
|
-
scopeTargets: [
|
|
484
|
-
'boot',
|
|
485
|
-
'app_v1',
|
|
486
|
-
'app_v2',
|
|
487
|
-
'coprocessor',
|
|
488
|
-
'resource',
|
|
489
|
-
'se01',
|
|
490
|
-
'se02',
|
|
491
|
-
'se03',
|
|
492
|
-
'se04',
|
|
493
|
-
],
|
|
494
|
-
bindings: [
|
|
495
|
-
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
496
|
-
artifact
|
|
497
|
-
? [
|
|
498
|
-
{
|
|
499
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
500
|
-
artifact,
|
|
501
|
-
},
|
|
502
|
-
]
|
|
503
|
-
: []
|
|
504
|
-
),
|
|
505
|
-
...(payload.resourceBundleArtifacts ?? []).map(
|
|
506
|
-
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
507
|
-
target: 'resource' as const,
|
|
508
|
-
logicalName: entry.name,
|
|
509
|
-
artifact: entry.artifact,
|
|
510
|
-
})
|
|
511
|
-
),
|
|
512
|
-
],
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
437
|
|
|
516
438
|
this.params = {
|
|
517
|
-
preparedPlan: payload.preparedPlan,
|
|
518
439
|
chunkSize: payload.chunkSize,
|
|
519
440
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
520
441
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -529,22 +450,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
529
450
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
530
451
|
firmwareType: payload.firmwareType,
|
|
531
452
|
targetsToUpdate: payload.targetsToUpdate,
|
|
532
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
533
453
|
platform: payload.platform,
|
|
534
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
535
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
536
|
-
componentArtifacts: payload.componentArtifacts,
|
|
537
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
538
454
|
};
|
|
539
455
|
}
|
|
540
456
|
|
|
541
|
-
private getProtocolV2FirmwareChunkSize() {
|
|
457
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
|
|
542
458
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
543
459
|
const env = DataManager.getSettings('env');
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
+
}
|
|
548
468
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
549
469
|
return maxChunkSize;
|
|
550
470
|
}
|
|
@@ -571,14 +491,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
571
491
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
572
492
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
573
493
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
574
|
-
this.validateExpectedTargetVersions();
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
578
|
-
this.params.resourceBundleArtifacts?.length
|
|
579
|
-
) {
|
|
580
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
581
|
-
}
|
|
582
494
|
|
|
583
495
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
584
496
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -589,18 +501,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
589
501
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
590
502
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
591
503
|
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
592
|
-
if (
|
|
593
|
-
this.params.artifactReader ||
|
|
594
|
-
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
595
|
-
) {
|
|
596
|
-
throw ERRORS.TypedError(
|
|
597
|
-
HardwareErrorCode.RuntimeError,
|
|
598
|
-
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
599
|
-
{
|
|
600
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
601
|
-
}
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
504
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
605
505
|
firmwareType,
|
|
606
506
|
deviceFeatures
|
|
@@ -609,13 +509,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
609
509
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
610
510
|
installItems = remoteBinaries.installItems;
|
|
611
511
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
resourceBundles = resourceDescriptors?.length
|
|
617
|
-
? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
|
|
618
|
-
: undefined;
|
|
512
|
+
resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
|
|
513
|
+
if (resourceBundles?.length) {
|
|
514
|
+
resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
|
|
515
|
+
}
|
|
619
516
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
620
517
|
} catch (err) {
|
|
621
518
|
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
@@ -628,282 +525,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
628
525
|
);
|
|
629
526
|
}
|
|
630
527
|
|
|
631
|
-
|
|
528
|
+
await this.enterProtocolV2BootloaderMode();
|
|
529
|
+
|
|
530
|
+
await this.executeProtocolV2Update({
|
|
632
531
|
fwBinaryMap,
|
|
633
532
|
bootloaderBinary,
|
|
634
533
|
...(installItems ? { installItems } : undefined),
|
|
635
534
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
636
535
|
});
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
640
|
-
const source = await openFirmwareByteSource({
|
|
641
|
-
artifact,
|
|
642
|
-
reader: this.params.artifactReader,
|
|
643
|
-
});
|
|
644
|
-
if (!source) {
|
|
645
|
-
throw ERRORS.TypedError(
|
|
646
|
-
HardwareErrorCode.RuntimeError,
|
|
647
|
-
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
648
|
-
{
|
|
649
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
650
|
-
}
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
this.protocolV2PreparedSources.push(source);
|
|
654
|
-
return source;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
658
|
-
const source = await openFirmwareByteSource({ binary });
|
|
659
|
-
if (!source) {
|
|
660
|
-
throw ERRORS.TypedError(
|
|
661
|
-
HardwareErrorCode.RuntimeError,
|
|
662
|
-
'Protocol V2 firmware binary is empty'
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
this.protocolV2PreparedSources.push(source);
|
|
666
|
-
return source;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
private async closeProtocolV2PreparedSources() {
|
|
670
|
-
const sources = this.protocolV2PreparedSources.splice(0);
|
|
671
|
-
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
private async prepareProtocolV2InstallSources(
|
|
675
|
-
firmwareType: EFirmwareType,
|
|
676
|
-
features: Features
|
|
677
|
-
): Promise<ProtocolV2InstallSource[]> {
|
|
678
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
679
|
-
if (!release) {
|
|
680
|
-
throw ERRORS.TypedError(
|
|
681
|
-
HardwareErrorCode.RuntimeError,
|
|
682
|
-
'Protocol V2 firmware release is unavailable'
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
686
|
-
const requestedTargets = new Set(
|
|
687
|
-
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
688
|
-
);
|
|
689
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
690
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
691
|
-
|
|
692
|
-
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
693
|
-
const target = this.getRemoteComponentTarget(key, component);
|
|
694
|
-
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
695
|
-
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
696
|
-
const artifact = componentArtifacts[updateTarget];
|
|
697
|
-
if (!artifact) {
|
|
698
|
-
throw ERRORS.TypedError(
|
|
699
|
-
HardwareErrorCode.RuntimeError,
|
|
700
|
-
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
701
|
-
{
|
|
702
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
703
|
-
artifactName: updateTarget,
|
|
704
|
-
}
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
installSources.push({
|
|
708
|
-
...target,
|
|
709
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
710
|
-
});
|
|
711
|
-
preparedTargets.add(updateTarget);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
536
|
|
|
715
|
-
|
|
716
|
-
target => target !== 'resource' && !preparedTargets.has(target)
|
|
717
|
-
);
|
|
718
|
-
if (unknownTarget) {
|
|
719
|
-
throw ERRORS.TypedError(
|
|
720
|
-
HardwareErrorCode.RuntimeError,
|
|
721
|
-
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
return installSources;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
private async prepareProtocolV2ResourceSources(
|
|
728
|
-
firmwareType: EFirmwareType,
|
|
729
|
-
features: Features
|
|
730
|
-
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
731
|
-
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
732
|
-
const resourceRequested =
|
|
733
|
-
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
734
|
-
if (!resourceRequested) {
|
|
735
|
-
return [];
|
|
736
|
-
}
|
|
737
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
738
|
-
const descriptors = release?.resourceBundles ?? [];
|
|
739
|
-
const artifactByName = new Map(
|
|
740
|
-
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
741
|
-
);
|
|
742
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
743
|
-
for (const descriptor of descriptors) {
|
|
744
|
-
const artifact = artifactByName.get(descriptor.name);
|
|
745
|
-
if (!artifact) {
|
|
746
|
-
throw ERRORS.TypedError(
|
|
747
|
-
HardwareErrorCode.RuntimeError,
|
|
748
|
-
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
749
|
-
{
|
|
750
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
751
|
-
artifactName: descriptor.name,
|
|
752
|
-
}
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
756
|
-
descriptor.devicePath,
|
|
757
|
-
'resourceBundles[].devicePath'
|
|
758
|
-
);
|
|
759
|
-
sources.push({
|
|
760
|
-
name: descriptor.name,
|
|
761
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
762
|
-
devicePath,
|
|
763
|
-
version: descriptor.version,
|
|
764
|
-
payloadHash: descriptor.payloadHash,
|
|
765
|
-
headerHash: descriptor.headerHash,
|
|
766
|
-
});
|
|
767
|
-
artifactByName.delete(descriptor.name);
|
|
768
|
-
}
|
|
769
|
-
if (artifactByName.size > 0) {
|
|
770
|
-
throw ERRORS.TypedError(
|
|
771
|
-
HardwareErrorCode.RuntimeError,
|
|
772
|
-
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
return sources;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
779
|
-
try {
|
|
780
|
-
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
781
|
-
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
782
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
783
|
-
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
784
|
-
throw ERRORS.TypedError(
|
|
785
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
786
|
-
'No firmware to update'
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
790
|
-
|
|
791
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
792
|
-
installSources,
|
|
793
|
-
resourceSources,
|
|
794
|
-
});
|
|
795
|
-
} finally {
|
|
796
|
-
await this.closeProtocolV2PreparedSources();
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
private validateExpectedTargetVersions() {
|
|
801
|
-
const expected = this.params.expectedTargetVersions;
|
|
802
|
-
if (expected === undefined) return;
|
|
803
|
-
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
804
|
-
throw ERRORS.TypedError(
|
|
805
|
-
HardwareErrorCode.RuntimeError,
|
|
806
|
-
'Protocol V2 expected target versions are invalid'
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
|
-
for (const [target, version] of Object.entries(expected)) {
|
|
810
|
-
if (
|
|
811
|
-
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
812
|
-
target as FirmwareUpdateV4Target
|
|
813
|
-
) ||
|
|
814
|
-
typeof version !== 'string' ||
|
|
815
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
816
|
-
version.length > 64
|
|
817
|
-
) {
|
|
818
|
-
throw ERRORS.TypedError(
|
|
819
|
-
HardwareErrorCode.RuntimeError,
|
|
820
|
-
'Protocol V2 expected target version is invalid'
|
|
821
|
-
);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
537
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
825
538
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (!version) return undefined;
|
|
830
|
-
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
831
|
-
if (
|
|
832
|
-
parts.length !== 3 ||
|
|
833
|
-
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
834
|
-
) {
|
|
835
|
-
return undefined;
|
|
836
|
-
}
|
|
837
|
-
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
841
|
-
const expected = this.params?.expectedTargetVersions;
|
|
842
|
-
if (!expected) return;
|
|
843
|
-
const features = this.protocolV2LatestFinalFeatures;
|
|
844
|
-
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
845
|
-
const applicationTargets = requestedTargets.filter(
|
|
846
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
847
|
-
);
|
|
848
|
-
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
849
|
-
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
850
|
-
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
851
|
-
se01: features?.se01Version ?? undefined,
|
|
852
|
-
se02: features?.se02Version ?? undefined,
|
|
853
|
-
se03: features?.se03Version ?? undefined,
|
|
854
|
-
se04: features?.se04Version ?? undefined,
|
|
855
|
-
};
|
|
856
|
-
if (features && applicationTargets.length === 1) {
|
|
857
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
858
|
-
}
|
|
859
|
-
const expectedEntries = (
|
|
860
|
-
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
861
|
-
).filter(([target]) => !targets || targets.includes(target));
|
|
862
|
-
for (const [target, expectedVersion] of expectedEntries) {
|
|
863
|
-
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
864
|
-
([, mappedTarget]) => mappedTarget === target
|
|
865
|
-
)?.[0];
|
|
866
|
-
const statusVersion =
|
|
867
|
-
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
868
|
-
const observedVersion =
|
|
869
|
-
statusVersion === undefined
|
|
870
|
-
? visibleVersions[target]
|
|
871
|
-
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
872
|
-
Math.floor(statusVersion / 0x100) % 0x100
|
|
873
|
-
}.${statusVersion % 0x100}`;
|
|
874
|
-
if (!observedVersion) {
|
|
875
|
-
Log.warn(
|
|
876
|
-
`Protocol V2 target ${target} has no observable final version; skipping version comparison`
|
|
877
|
-
);
|
|
878
|
-
} else if (observedVersion !== expectedVersion) {
|
|
879
|
-
throw ERRORS.TypedError(
|
|
880
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
881
|
-
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
882
|
-
);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
539
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
540
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
541
|
+
DevicePool.resetState();
|
|
886
542
|
|
|
887
|
-
|
|
888
|
-
if (this.params.targetsToUpdate?.length) {
|
|
889
|
-
return [...new Set(this.params.targetsToUpdate)];
|
|
890
|
-
}
|
|
891
|
-
const targets = new Set<FirmwareUpdateV4Target>();
|
|
892
|
-
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
893
|
-
targets.add(target as FirmwareUpdateV4Target)
|
|
894
|
-
);
|
|
895
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
896
|
-
targets.add('resource');
|
|
897
|
-
}
|
|
898
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
899
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
900
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
901
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
902
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
903
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
904
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
905
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
906
|
-
return Array.from(targets);
|
|
543
|
+
return versions;
|
|
907
544
|
}
|
|
908
545
|
|
|
909
546
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -939,14 +576,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
939
576
|
private async captureProtocolV2PhysicalIdentity() {
|
|
940
577
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
941
578
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
942
|
-
|
|
943
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
944
|
-
preparedPlan: this.params.preparedPlan,
|
|
945
|
-
deviceIdentity: serialNumber,
|
|
946
|
-
});
|
|
947
|
-
} else {
|
|
948
|
-
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
949
|
-
}
|
|
579
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
950
580
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
951
581
|
}
|
|
952
582
|
|
|
@@ -1047,7 +677,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1047
677
|
return null;
|
|
1048
678
|
}
|
|
1049
679
|
|
|
1050
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
680
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1051
681
|
const chunks: Uint8Array[] = [];
|
|
1052
682
|
let offset = 0;
|
|
1053
683
|
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
@@ -1199,47 +829,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1199
829
|
})) as ProtocolV2ResourceBundleBinary[];
|
|
1200
830
|
}
|
|
1201
831
|
|
|
1202
|
-
private async
|
|
832
|
+
private async prefetchProtocolV2ResourceBundles(
|
|
1203
833
|
bundles: ProtocolV2ResourceBundleBinary[]
|
|
1204
834
|
): Promise<ProtocolV2ResourceBundleBinary[]> {
|
|
835
|
+
const prefetched: ProtocolV2ResourceBundleBinary[] = [];
|
|
1205
836
|
for (const bundle of bundles) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
) {
|
|
1211
|
-
throw ERRORS.TypedError(
|
|
1212
|
-
HardwareErrorCode.RuntimeError,
|
|
1213
|
-
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1214
|
-
{
|
|
1215
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1216
|
-
artifactName: bundle.name,
|
|
1217
|
-
}
|
|
1218
|
-
);
|
|
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}`);
|
|
1219
841
|
}
|
|
1220
|
-
Log.log(`[FirmwareUpdateV4]
|
|
1221
|
-
|
|
842
|
+
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
843
|
+
({ binary } = await getSysResourceBinary(bundle.url));
|
|
1222
844
|
}
|
|
1223
|
-
if (
|
|
1224
|
-
throw
|
|
1225
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1226
|
-
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1227
|
-
);
|
|
845
|
+
if (binary.byteLength === 0) {
|
|
846
|
+
throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
|
|
1228
847
|
}
|
|
848
|
+
prefetched.push({ ...bundle, binary });
|
|
1229
849
|
}
|
|
1230
|
-
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 };
|
|
1231
915
|
}
|
|
1232
916
|
|
|
1233
917
|
/**
|
|
1234
918
|
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
1235
919
|
*/
|
|
1236
920
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
1237
|
-
bundle:
|
|
1238
|
-
ProtocolV2ResourceBundleBinary,
|
|
1239
|
-
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1240
|
-
>
|
|
921
|
+
bundle: ProtocolV2ResourceBundleBinary
|
|
1241
922
|
): Promise<boolean> {
|
|
1242
|
-
if (this.params
|
|
923
|
+
if (this.params.forcedUpdateRes) return false;
|
|
1243
924
|
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1244
925
|
|
|
1245
926
|
try {
|
|
@@ -1277,7 +958,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1277
958
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1278
959
|
if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
|
|
1279
960
|
Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
|
|
1280
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1281
961
|
return false;
|
|
1282
962
|
}
|
|
1283
963
|
|
|
@@ -1287,7 +967,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1287
967
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1288
968
|
await wait(1000);
|
|
1289
969
|
await this.waitForProtocolV2BootloaderMode();
|
|
1290
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1291
970
|
return true;
|
|
1292
971
|
} catch (error) {
|
|
1293
972
|
if (error instanceof HardwareError) {
|
|
@@ -1376,98 +1055,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1376
1055
|
return entries;
|
|
1377
1056
|
}
|
|
1378
1057
|
|
|
1379
|
-
private buildProtocolV2ExecutionPhases({
|
|
1380
|
-
installSources,
|
|
1381
|
-
resourceSources,
|
|
1382
|
-
}: {
|
|
1383
|
-
installSources: ProtocolV2InstallSource[];
|
|
1384
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1385
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
1386
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1387
|
-
if (resourceSources.length > 0) {
|
|
1388
|
-
phases.push({
|
|
1389
|
-
kind: 'resource-sync',
|
|
1390
|
-
installSources: [],
|
|
1391
|
-
resourceSources,
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1395
|
-
if (bootloaderSources.length > 0) {
|
|
1396
|
-
phases.push(
|
|
1397
|
-
{
|
|
1398
|
-
kind: 'bootloader-install',
|
|
1399
|
-
installSources: bootloaderSources,
|
|
1400
|
-
resourceSources: [],
|
|
1401
|
-
},
|
|
1402
|
-
{
|
|
1403
|
-
kind: 'bootloader-verify',
|
|
1404
|
-
installSources: [],
|
|
1405
|
-
resourceSources: [],
|
|
1406
|
-
}
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1410
|
-
if (componentSources.length > 0) {
|
|
1411
|
-
phases.push({
|
|
1412
|
-
kind: 'component-install',
|
|
1413
|
-
installSources: componentSources,
|
|
1414
|
-
resourceSources: [],
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
phases.push({
|
|
1418
|
-
kind: 'final-verify',
|
|
1419
|
-
installSources: [],
|
|
1420
|
-
resourceSources: [],
|
|
1421
|
-
});
|
|
1422
|
-
return phases;
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
private async executeProtocolV2Phases({
|
|
1426
|
-
installSources,
|
|
1427
|
-
resourceSources,
|
|
1428
|
-
}: {
|
|
1429
|
-
installSources: ProtocolV2InstallSource[];
|
|
1430
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1431
|
-
}) {
|
|
1432
|
-
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1433
|
-
installSources,
|
|
1434
|
-
resourceSources,
|
|
1435
|
-
});
|
|
1436
|
-
for (const phase of phases) {
|
|
1437
|
-
if (phase.kind === 'final-verify') {
|
|
1438
|
-
return this.completeProtocolV2FinalVerification();
|
|
1439
|
-
}
|
|
1440
|
-
if (phase.kind === 'resource-sync') {
|
|
1441
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1442
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1443
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1444
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1445
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1446
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1447
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
1448
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
1449
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
throw ERRORS.TypedError(
|
|
1453
|
-
HardwareErrorCode.RuntimeError,
|
|
1454
|
-
'Protocol V2 execution has no final verification phase'
|
|
1455
|
-
);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
private async executeProtocolV2SourceUpdate({
|
|
1459
|
-
installSources,
|
|
1460
|
-
resourceSources,
|
|
1461
|
-
}: {
|
|
1462
|
-
installSources: ProtocolV2InstallSource[];
|
|
1463
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1464
|
-
}) {
|
|
1465
|
-
return this.executeProtocolV2Phases({
|
|
1466
|
-
installSources,
|
|
1467
|
-
resourceSources,
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
1058
|
private async executeProtocolV2Update({
|
|
1472
1059
|
fwBinaryMap,
|
|
1473
1060
|
bootloaderBinary,
|
|
@@ -1479,158 +1066,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1479
1066
|
installItems?: ProtocolV2InstallItem[];
|
|
1480
1067
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1481
1068
|
}) {
|
|
1482
|
-
const
|
|
1069
|
+
const orderedInstallItems =
|
|
1483
1070
|
installItems ??
|
|
1484
1071
|
this.buildProtocolV2InstallItems({
|
|
1485
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1486
1072
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1073
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1487
1074
|
});
|
|
1488
|
-
try {
|
|
1489
|
-
const installSources = await Promise.all(
|
|
1490
|
-
memoryInstallItems.map(async item => ({
|
|
1491
|
-
fileName: item.fileName,
|
|
1492
|
-
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1493
|
-
targetId: item.targetId,
|
|
1494
|
-
kind: item.kind,
|
|
1495
|
-
}))
|
|
1496
|
-
);
|
|
1497
|
-
const resourceSources = await Promise.all(
|
|
1498
|
-
(resourceBundles ?? []).map(async bundle => ({
|
|
1499
|
-
name: bundle.name,
|
|
1500
|
-
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1501
|
-
devicePath: bundle.devicePath,
|
|
1502
|
-
version: bundle.version,
|
|
1503
|
-
payloadHash: bundle.payloadHash,
|
|
1504
|
-
headerHash: bundle.headerHash,
|
|
1505
|
-
}))
|
|
1506
|
-
);
|
|
1507
|
-
return await this.executeProtocolV2Phases({
|
|
1508
|
-
installSources,
|
|
1509
|
-
resourceSources,
|
|
1510
|
-
});
|
|
1511
|
-
} finally {
|
|
1512
|
-
await this.closeProtocolV2PreparedSources();
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
1075
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
resourceSources,
|
|
1519
|
-
}: ProtocolV2ExecutionPhase) {
|
|
1520
|
-
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1521
|
-
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1522
|
-
for (const resource of resourceSources) {
|
|
1523
|
-
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1524
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1525
|
-
} else {
|
|
1526
|
-
resourcesToSync.push(resource);
|
|
1527
|
-
totalSize += resource.source.size;
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1076
|
+
let firmwareSize = 0;
|
|
1077
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1530
1078
|
|
|
1531
1079
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1080
|
+
|
|
1532
1081
|
let processedSize = 0;
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
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;
|
|
1541
1090
|
}
|
|
1542
1091
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
totalSize,
|
|
1551
|
-
});
|
|
1552
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1553
|
-
stagedInstallTargets.push({
|
|
1554
|
-
targetId: item.targetId,
|
|
1555
|
-
path: filePath,
|
|
1556
|
-
});
|
|
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;
|
|
1557
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
|
+
);
|
|
1558
1110
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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;
|
|
1564
1153
|
}
|
|
1565
1154
|
|
|
1566
1155
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1567
|
-
|
|
1156
|
+
|
|
1157
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1568
1158
|
target_id: item.targetId,
|
|
1569
1159
|
path: item.path,
|
|
1570
1160
|
}));
|
|
1571
|
-
|
|
1572
|
-
await this.
|
|
1161
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1162
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1163
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1573
1164
|
}
|
|
1574
1165
|
|
|
1575
|
-
private
|
|
1576
|
-
source,
|
|
1577
|
-
filePath,
|
|
1578
|
-
processedSize,
|
|
1579
|
-
totalSize,
|
|
1580
|
-
}: {
|
|
1581
|
-
source: FirmwareByteSource;
|
|
1582
|
-
filePath: string;
|
|
1583
|
-
processedSize: number;
|
|
1584
|
-
totalSize: number;
|
|
1585
|
-
}) {
|
|
1586
|
-
let lastError: unknown;
|
|
1587
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1588
|
-
try {
|
|
1589
|
-
await writeFirmwareByteSource({
|
|
1590
|
-
source,
|
|
1591
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1592
|
-
write: async ({ data, sourceOffset, length, first }) => {
|
|
1593
|
-
const chunkEnd = sourceOffset + length;
|
|
1594
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1595
|
-
processedSize + sourceOffset,
|
|
1596
|
-
processedSize + chunkEnd,
|
|
1597
|
-
totalSize
|
|
1598
|
-
);
|
|
1599
|
-
const response = await this.fileWriteChunk(
|
|
1600
|
-
filePath,
|
|
1601
|
-
source.size,
|
|
1602
|
-
sourceOffset,
|
|
1603
|
-
data,
|
|
1604
|
-
first,
|
|
1605
|
-
progress
|
|
1606
|
-
);
|
|
1607
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
1608
|
-
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1609
|
-
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1610
|
-
throw ERRORS.TypedError(
|
|
1611
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1612
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
this.postProgressMessage(progress, 'transferData');
|
|
1616
|
-
return length;
|
|
1617
|
-
},
|
|
1618
|
-
});
|
|
1619
|
-
return processedSize + source.size;
|
|
1620
|
-
} catch (error) {
|
|
1621
|
-
lastError = error;
|
|
1622
|
-
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1623
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
throw ERRORS.TypedError(
|
|
1628
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1629
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1166
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1634
1167
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1635
1168
|
}
|
|
1636
1169
|
|
|
@@ -1649,8 +1182,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1649
1182
|
|
|
1650
1183
|
private assertProtocolV2TargetStatus(
|
|
1651
1184
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1652
|
-
expectedTargetIds: Set<number
|
|
1653
|
-
expectedPaths = new Map<number, string>()
|
|
1185
|
+
expectedTargetIds: Set<number>
|
|
1654
1186
|
) {
|
|
1655
1187
|
Log.log(
|
|
1656
1188
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1681,47 +1213,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1681
1213
|
const matchingTargets = statusTargets.filter(target =>
|
|
1682
1214
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1683
1215
|
);
|
|
1684
|
-
const seenTargetIds = new Set<number>();
|
|
1685
|
-
for (const target of matchingTargets) {
|
|
1686
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1687
|
-
if (
|
|
1688
|
-
targetId === undefined ||
|
|
1689
|
-
seenTargetIds.has(targetId) ||
|
|
1690
|
-
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1691
|
-
) {
|
|
1692
|
-
throw ERRORS.TypedError(
|
|
1693
|
-
HardwareErrorCode.RuntimeError,
|
|
1694
|
-
`Protocol V2 install status conflicts with target ${target.target_id}`
|
|
1695
|
-
);
|
|
1696
|
-
}
|
|
1697
|
-
seenTargetIds.add(targetId);
|
|
1698
|
-
}
|
|
1699
1216
|
const completedTargets = matchingTargets.filter(target =>
|
|
1700
1217
|
isProtocolV2TargetStatusFinished(target.status)
|
|
1701
1218
|
);
|
|
1702
1219
|
if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1703
|
-
for (const target of completedTargets) {
|
|
1704
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1705
|
-
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1706
|
-
if (targetId === undefined) {
|
|
1707
|
-
throw ERRORS.TypedError(
|
|
1708
|
-
HardwareErrorCode.RuntimeError,
|
|
1709
|
-
'Protocol V2 completed target identity is invalid'
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1713
|
-
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1714
|
-
if (payloadVersion !== expectedVersion) {
|
|
1715
|
-
throw ERRORS.TypedError(
|
|
1716
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1717
|
-
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1718
|
-
);
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
if (payloadVersion !== undefined) {
|
|
1722
|
-
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
1220
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1726
1221
|
return true;
|
|
1727
1222
|
}
|
|
@@ -1746,7 +1241,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1746
1241
|
targets: Array<{ target_id: number; path: string }>
|
|
1747
1242
|
) {
|
|
1748
1243
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1749
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1750
1244
|
const startTime = Date.now();
|
|
1751
1245
|
let lastError: unknown;
|
|
1752
1246
|
|
|
@@ -1770,8 +1264,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1770
1264
|
if (
|
|
1771
1265
|
this.assertProtocolV2TargetStatus(
|
|
1772
1266
|
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1773
|
-
expectedTargetIds
|
|
1774
|
-
expectedPaths
|
|
1267
|
+
expectedTargetIds
|
|
1775
1268
|
)
|
|
1776
1269
|
) {
|
|
1777
1270
|
return;
|
|
@@ -1788,34 +1281,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1788
1281
|
// device already rebooted into App, this endpoint error signals that the
|
|
1789
1282
|
// install phase ended; the later phase performs the final readiness check.
|
|
1790
1283
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
commands: this.device.getCommands(),
|
|
1794
|
-
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1795
|
-
});
|
|
1796
|
-
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1797
|
-
const observed = await this.device.probeProtocolV2RuntimeState(
|
|
1798
|
-
deviceInfo,
|
|
1799
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1800
|
-
);
|
|
1801
|
-
if (observed.mode === 'normal' && !observed.bootloaderMode) {
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
lastError = new Error(
|
|
1805
|
-
'Protocol V2 status endpoint is unavailable outside normal mode'
|
|
1806
|
-
);
|
|
1807
|
-
} catch (probeError) {
|
|
1808
|
-
lastError = probeError;
|
|
1809
|
-
}
|
|
1810
|
-
throw ERRORS.TypedError(
|
|
1811
|
-
HardwareErrorCode.RuntimeError,
|
|
1812
|
-
`Protocol V2 firmware install status is unavailable: ${this.normalizeErrorMessage(
|
|
1813
|
-
lastError
|
|
1814
|
-
)}`,
|
|
1815
|
-
{
|
|
1816
|
-
firmwareUpdateCode: 'FirmwareInstallStatusUnavailable',
|
|
1817
|
-
}
|
|
1284
|
+
Log.log(
|
|
1285
|
+
'[FirmwareUpdateV4] firmware status endpoint unavailable after reboot; continue with normal-mode verification'
|
|
1818
1286
|
);
|
|
1287
|
+
return;
|
|
1819
1288
|
}
|
|
1820
1289
|
lastError = error;
|
|
1821
1290
|
Log.log(
|
|
@@ -1831,12 +1300,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1831
1300
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1832
1301
|
throw error;
|
|
1833
1302
|
}
|
|
1834
|
-
if (
|
|
1835
|
-
error instanceof HardwareError &&
|
|
1836
|
-
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1837
|
-
) {
|
|
1838
|
-
throw error;
|
|
1839
|
-
}
|
|
1840
1303
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1841
1304
|
}
|
|
1842
1305
|
await wait(1000);
|
|
@@ -1854,7 +1317,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1854
1317
|
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1855
1318
|
// repeating it after an automatic App reboot is idempotent.
|
|
1856
1319
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1857
|
-
this.protocolV2ExecutionInLoader = false;
|
|
1858
1320
|
}
|
|
1859
1321
|
|
|
1860
1322
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -1865,7 +1327,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1865
1327
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1866
1328
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1867
1329
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1868
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
1869
1330
|
if (firmwareVersion === '0.0.0') {
|
|
1870
1331
|
Log.warn(
|
|
1871
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.'
|
|
@@ -1879,17 +1340,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1879
1340
|
};
|
|
1880
1341
|
}
|
|
1881
1342
|
|
|
1882
|
-
private async completeProtocolV2FinalVerification() {
|
|
1883
|
-
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1884
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1885
|
-
}
|
|
1886
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1887
|
-
this.assertExpectedProtocolV2Versions();
|
|
1888
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1889
|
-
DevicePool.resetState();
|
|
1890
|
-
return versions;
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
1343
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1894
1344
|
const startTime = Date.now();
|
|
1895
1345
|
let lastError: unknown;
|
|
@@ -1996,6 +1446,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1996
1446
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1997
1447
|
}
|
|
1998
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
|
+
|
|
1999
1539
|
private async fileWriteChunk(
|
|
2000
1540
|
filePath: string,
|
|
2001
1541
|
totalFileSize: number,
|