@onekeyfe/hd-core 1.2.0-alpha.30 → 1.2.0-alpha.32
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__/check-all-firmware-release-protocol-v2.test.ts +251 -0
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-state-mapper.test.ts +10 -1
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/networkUtils.test.ts +20 -0
- package/__tests__/open-wallet-session.test.ts +19 -5
- package/__tests__/protocol-v2-firmware-targets.test.ts +18 -0
- package/__tests__/protocol-v2.test.ts +58 -240
- package/__tests__/public-device-state-api.test.ts +10 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +24 -1
- 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 +6 -22
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/PromptWebDeviceAccess.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/stellar/StellarSignTransaction.d.ts +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/DeviceStateMapper.d.ts.map +1 -1
- package/dist/index.d.ts +43 -179
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1048 -2606
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +25 -7
- 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 +2 -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 +3 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +9 -3
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/device.d.ts +6 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +1 -13
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/networkUtils.d.ts +1 -0
- package/dist/utils/networkUtils.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +368 -50
- package/src/api/FirmwareUpdateV2.ts +120 -298
- package/src/api/FirmwareUpdateV3.ts +56 -258
- package/src/api/FirmwareUpdateV4.ts +328 -765
- package/src/api/OpenWalletSession.ts +12 -8
- package/src/api/PromptWebDeviceAccess.ts +2 -6
- 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/data/messages/messages-protocol-v2.json +4 -0
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/DeviceStateMapper.ts +6 -0
- package/src/index.ts +0 -5
- package/src/inject.ts +2 -22
- package/src/lowLevelInject.ts +1 -5
- package/src/protocols/protocol-v2/walletSession.ts +2 -1
- package/src/topLevelInject.ts +1 -5
- package/src/types/api/checkAllFirmwareRelease.ts +46 -7
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +5 -18
- package/src/types/api/firmwareUpdate.ts +0 -76
- package/src/types/api/index.ts +6 -15
- package/src/types/api/openWalletSession.ts +12 -3
- package/src/types/device.ts +6 -0
- package/src/types/settings.ts +3 -13
- package/src/utils/networkUtils.ts +5 -3
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -174
- 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-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- 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/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.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 -25
- 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 -28
- 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/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
6
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
10
11
|
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
getLogger,
|
|
19
20
|
} from '../utils';
|
|
20
21
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
21
|
-
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
22
22
|
import { DataManager } from '../data-manager';
|
|
23
23
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
24
24
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -31,20 +31,11 @@ import {
|
|
|
31
31
|
getProtocolV2UnknownErrorText,
|
|
32
32
|
isProtocolV2DeviceDisconnectedError,
|
|
33
33
|
} from './protocol-v2/helpers';
|
|
34
|
-
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
35
|
-
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
36
|
-
import {
|
|
37
|
-
assertFirmwareUpdatePreparedPlanBinding,
|
|
38
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
39
|
-
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
40
34
|
|
|
41
|
-
import type {
|
|
42
|
-
FirmwareArtifactReference,
|
|
43
|
-
FirmwareUpdateV4Params,
|
|
44
|
-
FirmwareUpdateV4Target,
|
|
45
|
-
} from '../types/api/firmwareUpdate';
|
|
35
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
46
36
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
47
37
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
38
|
+
import type { PROTO } from '../constants';
|
|
48
39
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
49
40
|
import type {
|
|
50
41
|
Features,
|
|
@@ -52,7 +43,6 @@ import type {
|
|
|
52
43
|
IProtocolV2FirmwareComponent,
|
|
53
44
|
IVersionArray,
|
|
54
45
|
} from '../types';
|
|
55
|
-
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
56
46
|
|
|
57
47
|
const Log = getLogger(LoggerNames.Method);
|
|
58
48
|
|
|
@@ -96,6 +86,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
96
86
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
97
87
|
};
|
|
98
88
|
|
|
89
|
+
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
90
|
+
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
91
|
+
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
92
|
+
};
|
|
93
|
+
|
|
99
94
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
100
95
|
target_id: number | string;
|
|
101
96
|
status?: number | string;
|
|
@@ -109,6 +104,17 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
109
104
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
110
105
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
111
106
|
};
|
|
107
|
+
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
108
|
+
path: string;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
112
|
+
filePath: string;
|
|
113
|
+
processedSize?: number;
|
|
114
|
+
totalSize?: number;
|
|
115
|
+
onTransferredBytes?: (transferredBytes: number) => void;
|
|
116
|
+
};
|
|
117
|
+
|
|
112
118
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
113
119
|
type ProtocolV2ResourceBundleBinary = {
|
|
114
120
|
name: string;
|
|
@@ -131,35 +137,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
131
137
|
kind: 'bootloader' | 'firmware';
|
|
132
138
|
};
|
|
133
139
|
|
|
134
|
-
type ProtocolV2InstallSource = {
|
|
135
|
-
fileName: string;
|
|
136
|
-
source: FirmwareByteSource;
|
|
137
|
-
targetId: number;
|
|
138
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
type ProtocolV2ResourceBundleSource = {
|
|
142
|
-
name: string;
|
|
143
|
-
source: FirmwareByteSource;
|
|
144
|
-
devicePath: string;
|
|
145
|
-
version?: IVersionArray;
|
|
146
|
-
payloadHash?: string;
|
|
147
|
-
headerHash?: string;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
type ProtocolV2ExecutionPhaseKind =
|
|
151
|
-
| 'resource-sync'
|
|
152
|
-
| 'bootloader-install'
|
|
153
|
-
| 'bootloader-verify'
|
|
154
|
-
| 'component-install'
|
|
155
|
-
| 'final-verify';
|
|
156
|
-
|
|
157
|
-
type ProtocolV2ExecutionPhase = {
|
|
158
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
159
|
-
installSources: ProtocolV2InstallSource[];
|
|
160
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
161
|
-
};
|
|
162
|
-
|
|
163
140
|
type ProtocolV2OkppHeader = {
|
|
164
141
|
type: string;
|
|
165
142
|
version: IVersionArray;
|
|
@@ -390,6 +367,15 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
|
|
|
390
367
|
};
|
|
391
368
|
};
|
|
392
369
|
|
|
370
|
+
export const isProtocolV2FirmwareFingerprintValid = (
|
|
371
|
+
binary: ArrayBuffer | Uint8Array,
|
|
372
|
+
fingerprint: string | undefined
|
|
373
|
+
) => {
|
|
374
|
+
const expectedFingerprint = normalizeProtocolV2Hex(fingerprint);
|
|
375
|
+
if (!expectedFingerprint) return true;
|
|
376
|
+
return bytesToHex(sha256(toProtocolV2Bytes(binary))) === expectedFingerprint;
|
|
377
|
+
};
|
|
378
|
+
|
|
393
379
|
export const assertProtocolV2ReconnectIdentity = (
|
|
394
380
|
expectedSerialNumber?: string,
|
|
395
381
|
actualSerialNumber?: string
|
|
@@ -423,16 +409,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
423
409
|
return ['V2'] as const;
|
|
424
410
|
}
|
|
425
411
|
|
|
426
|
-
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
427
|
-
|
|
428
|
-
private protocolV2ExecutionInLoader = false;
|
|
429
|
-
|
|
430
|
-
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
431
|
-
|
|
432
|
-
private protocolV2LatestFinalFeatures?: Features;
|
|
433
|
-
|
|
434
|
-
private protocolV2FinalStatusVerified = false;
|
|
435
|
-
|
|
436
412
|
init() {
|
|
437
413
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
438
414
|
this.requireDeviceMode = [];
|
|
@@ -470,62 +446,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
470
446
|
{ name: 'firmwareType', type: 'string' },
|
|
471
447
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
472
448
|
{ name: 'platform', type: 'string' },
|
|
473
|
-
{ name: 'expectedDeviceId', type: 'string' },
|
|
474
449
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
475
450
|
]);
|
|
476
|
-
if (
|
|
477
|
-
payload.expectedDeviceId !== undefined &&
|
|
478
|
-
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
479
|
-
) {
|
|
480
|
-
throw ERRORS.TypedError(
|
|
481
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
482
|
-
'Protocol V2 expected device identity is invalid'
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
const hostBinding =
|
|
486
|
-
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
487
|
-
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
488
|
-
: undefined;
|
|
489
|
-
if (hostBinding) {
|
|
490
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
491
|
-
preparedPlan: payload.preparedPlan,
|
|
492
|
-
executor: 'v4',
|
|
493
|
-
platform: payload.platform,
|
|
494
|
-
scopeTargets: [
|
|
495
|
-
'boot',
|
|
496
|
-
'app_v1',
|
|
497
|
-
'app_v2',
|
|
498
|
-
'coprocessor',
|
|
499
|
-
'resource',
|
|
500
|
-
'se01',
|
|
501
|
-
'se02',
|
|
502
|
-
'se03',
|
|
503
|
-
'se04',
|
|
504
|
-
],
|
|
505
|
-
bindings: [
|
|
506
|
-
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
507
|
-
artifact
|
|
508
|
-
? [
|
|
509
|
-
{
|
|
510
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
511
|
-
artifact,
|
|
512
|
-
},
|
|
513
|
-
]
|
|
514
|
-
: []
|
|
515
|
-
),
|
|
516
|
-
...(payload.resourceBundleArtifacts ?? []).map(
|
|
517
|
-
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
518
|
-
target: 'resource' as const,
|
|
519
|
-
logicalName: entry.name,
|
|
520
|
-
artifact: entry.artifact,
|
|
521
|
-
})
|
|
522
|
-
),
|
|
523
|
-
],
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
451
|
|
|
527
452
|
this.params = {
|
|
528
|
-
preparedPlan: payload.preparedPlan,
|
|
529
453
|
chunkSize: payload.chunkSize,
|
|
530
454
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
531
455
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -540,12 +464,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
540
464
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
541
465
|
firmwareType: payload.firmwareType,
|
|
542
466
|
targetsToUpdate: payload.targetsToUpdate,
|
|
543
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
544
467
|
platform: payload.platform,
|
|
545
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
546
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
547
|
-
componentArtifacts: payload.componentArtifacts,
|
|
548
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
549
468
|
};
|
|
550
469
|
}
|
|
551
470
|
|
|
@@ -579,14 +498,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
579
498
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
580
499
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
581
500
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
582
|
-
this.validateExpectedTargetVersions();
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
586
|
-
this.params.resourceBundleArtifacts?.length
|
|
587
|
-
) {
|
|
588
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
589
|
-
}
|
|
590
501
|
|
|
591
502
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
592
503
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -597,18 +508,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
597
508
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
598
509
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
599
510
|
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
600
|
-
if (
|
|
601
|
-
this.params.artifactReader ||
|
|
602
|
-
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
603
|
-
) {
|
|
604
|
-
throw ERRORS.TypedError(
|
|
605
|
-
HardwareErrorCode.RuntimeError,
|
|
606
|
-
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
607
|
-
{
|
|
608
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
609
|
-
}
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
511
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
613
512
|
firmwareType,
|
|
614
513
|
deviceFeatures
|
|
@@ -617,16 +516,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
617
516
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
618
517
|
installItems = remoteBinaries.installItems;
|
|
619
518
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
resourceBundles = resourceDescriptors?.length
|
|
625
|
-
? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
|
|
626
|
-
: undefined;
|
|
519
|
+
resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
|
|
520
|
+
if (resourceBundles?.length) {
|
|
521
|
+
resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
|
|
522
|
+
}
|
|
627
523
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
628
524
|
} catch (err) {
|
|
629
|
-
throw
|
|
525
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
630
526
|
}
|
|
631
527
|
|
|
632
528
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
@@ -636,289 +532,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
636
532
|
);
|
|
637
533
|
}
|
|
638
534
|
|
|
639
|
-
|
|
535
|
+
await this.enterProtocolV2BootloaderMode();
|
|
536
|
+
|
|
537
|
+
await this.executeProtocolV2Update({
|
|
640
538
|
fwBinaryMap,
|
|
641
539
|
bootloaderBinary,
|
|
642
540
|
...(installItems ? { installItems } : undefined),
|
|
643
541
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
644
542
|
});
|
|
645
|
-
}
|
|
646
543
|
|
|
647
|
-
|
|
648
|
-
const source = await openFirmwareByteSource({
|
|
649
|
-
artifact,
|
|
650
|
-
reader: this.params.artifactReader,
|
|
651
|
-
});
|
|
652
|
-
if (!source) {
|
|
653
|
-
throw ERRORS.TypedError(
|
|
654
|
-
HardwareErrorCode.RuntimeError,
|
|
655
|
-
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
656
|
-
{
|
|
657
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
658
|
-
}
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
this.protocolV2PreparedSources.push(source);
|
|
662
|
-
return source;
|
|
663
|
-
}
|
|
544
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
664
545
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
throw ERRORS.TypedError(
|
|
669
|
-
HardwareErrorCode.RuntimeError,
|
|
670
|
-
'Protocol V2 firmware binary is empty'
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
this.protocolV2PreparedSources.push(source);
|
|
674
|
-
return source;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
private async closeProtocolV2PreparedSources() {
|
|
678
|
-
const sources = this.protocolV2PreparedSources.splice(0);
|
|
679
|
-
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
private async prepareProtocolV2InstallSources(
|
|
683
|
-
firmwareType: EFirmwareType,
|
|
684
|
-
features: Features
|
|
685
|
-
): Promise<ProtocolV2InstallSource[]> {
|
|
686
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
687
|
-
if (!release) {
|
|
688
|
-
throw ERRORS.TypedError(
|
|
689
|
-
HardwareErrorCode.RuntimeError,
|
|
690
|
-
'Protocol V2 firmware release is unavailable'
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
694
|
-
const requestedTargets = new Set(
|
|
695
|
-
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
696
|
-
);
|
|
697
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
698
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
699
|
-
|
|
700
|
-
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
701
|
-
const target = this.getRemoteComponentTarget(key, component);
|
|
702
|
-
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
703
|
-
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
704
|
-
const artifact = componentArtifacts[updateTarget];
|
|
705
|
-
if (!artifact) {
|
|
706
|
-
throw ERRORS.TypedError(
|
|
707
|
-
HardwareErrorCode.RuntimeError,
|
|
708
|
-
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
709
|
-
{
|
|
710
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
711
|
-
artifactName: updateTarget,
|
|
712
|
-
}
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
installSources.push({
|
|
716
|
-
...target,
|
|
717
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
718
|
-
});
|
|
719
|
-
preparedTargets.add(updateTarget);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const unknownTarget = Array.from(requestedTargets).find(
|
|
724
|
-
target => target !== 'resource' && !preparedTargets.has(target)
|
|
725
|
-
);
|
|
726
|
-
if (unknownTarget) {
|
|
727
|
-
throw ERRORS.TypedError(
|
|
728
|
-
HardwareErrorCode.RuntimeError,
|
|
729
|
-
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
return installSources;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
private async prepareProtocolV2ResourceSources(
|
|
736
|
-
firmwareType: EFirmwareType,
|
|
737
|
-
features: Features
|
|
738
|
-
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
739
|
-
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
740
|
-
const resourceRequested =
|
|
741
|
-
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
742
|
-
if (!resourceRequested) {
|
|
743
|
-
return [];
|
|
744
|
-
}
|
|
745
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
746
|
-
const descriptors = release?.resourceBundles ?? [];
|
|
747
|
-
const artifactByName = new Map(
|
|
748
|
-
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
749
|
-
);
|
|
750
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
751
|
-
for (const descriptor of descriptors) {
|
|
752
|
-
const artifact = artifactByName.get(descriptor.name);
|
|
753
|
-
if (!artifact) {
|
|
754
|
-
throw ERRORS.TypedError(
|
|
755
|
-
HardwareErrorCode.RuntimeError,
|
|
756
|
-
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
757
|
-
{
|
|
758
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
759
|
-
artifactName: descriptor.name,
|
|
760
|
-
}
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
764
|
-
descriptor.devicePath,
|
|
765
|
-
'resourceBundles[].devicePath'
|
|
766
|
-
);
|
|
767
|
-
sources.push({
|
|
768
|
-
name: descriptor.name,
|
|
769
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
770
|
-
devicePath,
|
|
771
|
-
version: descriptor.version,
|
|
772
|
-
payloadHash: descriptor.payloadHash,
|
|
773
|
-
headerHash: descriptor.headerHash,
|
|
774
|
-
});
|
|
775
|
-
artifactByName.delete(descriptor.name);
|
|
776
|
-
}
|
|
777
|
-
if (artifactByName.size > 0) {
|
|
778
|
-
throw ERRORS.TypedError(
|
|
779
|
-
HardwareErrorCode.RuntimeError,
|
|
780
|
-
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
return sources;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
787
|
-
try {
|
|
788
|
-
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
789
|
-
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
790
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
791
|
-
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
792
|
-
throw ERRORS.TypedError(
|
|
793
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
794
|
-
'No firmware to update'
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
798
|
-
|
|
799
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
800
|
-
installSources,
|
|
801
|
-
resourceSources,
|
|
802
|
-
});
|
|
803
|
-
} finally {
|
|
804
|
-
await this.closeProtocolV2PreparedSources();
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
private validateExpectedTargetVersions() {
|
|
809
|
-
const expected = this.params.expectedTargetVersions;
|
|
810
|
-
if (expected === undefined) return;
|
|
811
|
-
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
812
|
-
throw ERRORS.TypedError(
|
|
813
|
-
HardwareErrorCode.RuntimeError,
|
|
814
|
-
'Protocol V2 expected target versions are invalid'
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
for (const [target, version] of Object.entries(expected)) {
|
|
818
|
-
if (
|
|
819
|
-
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
820
|
-
target as FirmwareUpdateV4Target
|
|
821
|
-
) ||
|
|
822
|
-
typeof version !== 'string' ||
|
|
823
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
824
|
-
version.length > 64
|
|
825
|
-
) {
|
|
826
|
-
throw ERRORS.TypedError(
|
|
827
|
-
HardwareErrorCode.RuntimeError,
|
|
828
|
-
'Protocol V2 expected target version is invalid'
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
835
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
836
|
-
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
837
|
-
if (!version) return undefined;
|
|
838
|
-
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
839
|
-
if (
|
|
840
|
-
parts.length !== 3 ||
|
|
841
|
-
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
842
|
-
) {
|
|
843
|
-
return undefined;
|
|
844
|
-
}
|
|
845
|
-
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
849
|
-
const expected = this.params?.expectedTargetVersions;
|
|
850
|
-
if (!expected) return;
|
|
851
|
-
const features = this.protocolV2LatestFinalFeatures;
|
|
852
|
-
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
853
|
-
const applicationTargets = requestedTargets.filter(
|
|
854
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
855
|
-
);
|
|
856
|
-
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
857
|
-
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
858
|
-
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
859
|
-
se01: features?.se01Version ?? undefined,
|
|
860
|
-
se02: features?.se02Version ?? undefined,
|
|
861
|
-
se03: features?.se03Version ?? undefined,
|
|
862
|
-
se04: features?.se04Version ?? undefined,
|
|
863
|
-
};
|
|
864
|
-
if (features && applicationTargets.length === 1) {
|
|
865
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
866
|
-
}
|
|
867
|
-
const expectedEntries = (
|
|
868
|
-
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
869
|
-
).filter(([target]) => !targets || targets.includes(target));
|
|
870
|
-
for (const [target, expectedVersion] of expectedEntries) {
|
|
871
|
-
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
872
|
-
([, mappedTarget]) => mappedTarget === target
|
|
873
|
-
)?.[0];
|
|
874
|
-
const statusVersion =
|
|
875
|
-
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
876
|
-
const observedVersion =
|
|
877
|
-
statusVersion === undefined
|
|
878
|
-
? visibleVersions[target]
|
|
879
|
-
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
880
|
-
Math.floor(statusVersion / 0x100) % 0x100
|
|
881
|
-
}.${statusVersion % 0x100}`;
|
|
882
|
-
if (!observedVersion) {
|
|
883
|
-
if (this.protocolV2FinalStatusVerified) {
|
|
884
|
-
Log.warn(
|
|
885
|
-
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
886
|
-
);
|
|
887
|
-
} else {
|
|
888
|
-
throw ERRORS.TypedError(
|
|
889
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
890
|
-
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
} else if (observedVersion !== expectedVersion) {
|
|
894
|
-
throw ERRORS.TypedError(
|
|
895
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
896
|
-
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
546
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
547
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
548
|
+
DevicePool.resetState();
|
|
901
549
|
|
|
902
|
-
|
|
903
|
-
if (this.params.targetsToUpdate?.length) {
|
|
904
|
-
return [...new Set(this.params.targetsToUpdate)];
|
|
905
|
-
}
|
|
906
|
-
const targets = new Set<FirmwareUpdateV4Target>();
|
|
907
|
-
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
908
|
-
targets.add(target as FirmwareUpdateV4Target)
|
|
909
|
-
);
|
|
910
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
911
|
-
targets.add('resource');
|
|
912
|
-
}
|
|
913
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
914
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
915
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
916
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
917
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
918
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
919
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
920
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
921
|
-
return Array.from(targets);
|
|
550
|
+
return versions;
|
|
922
551
|
}
|
|
923
552
|
|
|
924
553
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -954,16 +583,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
954
583
|
private async captureProtocolV2PhysicalIdentity() {
|
|
955
584
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
956
585
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
957
|
-
|
|
958
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
959
|
-
preparedPlan: this.params.preparedPlan,
|
|
960
|
-
deviceIdentity: serialNumber,
|
|
961
|
-
});
|
|
962
|
-
} else if (this.params?.expectedDeviceId) {
|
|
963
|
-
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
964
|
-
} else {
|
|
965
|
-
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
966
|
-
}
|
|
586
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
967
587
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
968
588
|
}
|
|
969
589
|
|
|
@@ -1106,6 +726,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1106
726
|
}
|
|
1107
727
|
|
|
1108
728
|
const { binary } = await getSysResourceBinary(component.url);
|
|
729
|
+
if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
|
|
730
|
+
throw ERRORS.TypedError(
|
|
731
|
+
HardwareErrorCode.RuntimeError,
|
|
732
|
+
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
|
|
736
|
+
if (expectedPayloadHash) {
|
|
737
|
+
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
738
|
+
if (!header || header.payloadHash !== expectedPayloadHash) {
|
|
739
|
+
throw ERRORS.TypedError(
|
|
740
|
+
HardwareErrorCode.RuntimeError,
|
|
741
|
+
`Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
1109
745
|
return {
|
|
1110
746
|
...target,
|
|
1111
747
|
binary,
|
|
@@ -1216,47 +852,117 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1216
852
|
})) as ProtocolV2ResourceBundleBinary[];
|
|
1217
853
|
}
|
|
1218
854
|
|
|
1219
|
-
private async
|
|
855
|
+
private async prefetchProtocolV2ResourceBundles(
|
|
1220
856
|
bundles: ProtocolV2ResourceBundleBinary[]
|
|
1221
857
|
): Promise<ProtocolV2ResourceBundleBinary[]> {
|
|
858
|
+
const prefetched: ProtocolV2ResourceBundleBinary[] = [];
|
|
1222
859
|
for (const bundle of bundles) {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
throw ERRORS.TypedError(
|
|
1229
|
-
HardwareErrorCode.RuntimeError,
|
|
1230
|
-
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1231
|
-
{
|
|
1232
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1233
|
-
artifactName: bundle.name,
|
|
1234
|
-
}
|
|
1235
|
-
);
|
|
860
|
+
let { binary } = bundle;
|
|
861
|
+
let downloadedFromRemote = false;
|
|
862
|
+
if (binary.byteLength === 0) {
|
|
863
|
+
if (!bundle.url) {
|
|
864
|
+
throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
|
|
1236
865
|
}
|
|
1237
|
-
Log.log(`[FirmwareUpdateV4]
|
|
1238
|
-
|
|
866
|
+
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
867
|
+
({ binary } = await getSysResourceBinary(bundle.url));
|
|
868
|
+
downloadedFromRemote = true;
|
|
1239
869
|
}
|
|
1240
|
-
if (
|
|
1241
|
-
throw
|
|
1242
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1243
|
-
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1244
|
-
);
|
|
870
|
+
if (binary.byteLength === 0) {
|
|
871
|
+
throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
|
|
1245
872
|
}
|
|
873
|
+
if (downloadedFromRemote && (bundle.version || bundle.payloadHash || bundle.headerHash)) {
|
|
874
|
+
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
875
|
+
if (!header || header.type !== 'RESC') {
|
|
876
|
+
throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
|
|
877
|
+
}
|
|
878
|
+
if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
|
|
879
|
+
throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
|
|
880
|
+
}
|
|
881
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
882
|
+
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
|
|
883
|
+
throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
|
|
884
|
+
}
|
|
885
|
+
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
886
|
+
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
|
|
887
|
+
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
prefetched.push({ ...bundle, binary });
|
|
1246
891
|
}
|
|
1247
|
-
return
|
|
892
|
+
return prefetched;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Synchronize RESC bundles to the device.
|
|
897
|
+
*
|
|
898
|
+
* Manual mode writes supplied binaries directly.
|
|
899
|
+
* Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
|
|
900
|
+
*/
|
|
901
|
+
private async syncProtocolV2ResourceBundles(
|
|
902
|
+
bundles: ProtocolV2ResourceBundleBinary[],
|
|
903
|
+
firmwareSize: number
|
|
904
|
+
): Promise<{ processedSize: number; totalSize: number }> {
|
|
905
|
+
const transferStartTime = Date.now();
|
|
906
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
907
|
+
|
|
908
|
+
const isRemoteMode = bundles.some(bundle => !!bundle.url);
|
|
909
|
+
let bundlesToSync = bundles;
|
|
910
|
+
|
|
911
|
+
// Remote binaries were prefetched before bootloader entry. Only device-local
|
|
912
|
+
// header comparison and transfer are allowed in this phase.
|
|
913
|
+
if (isRemoteMode) {
|
|
914
|
+
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
915
|
+
for (const bundle of bundles) {
|
|
916
|
+
// Compare the existing device header.
|
|
917
|
+
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
918
|
+
if (upToDate) {
|
|
919
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
920
|
+
} else {
|
|
921
|
+
filtered.push(bundle);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
bundlesToSync = filtered;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (bundlesToSync.length === 0) {
|
|
928
|
+
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
929
|
+
return { processedSize: 0, totalSize: firmwareSize };
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Write directly to the device through FileWrite.
|
|
933
|
+
let totalSize = 0;
|
|
934
|
+
for (const b of bundlesToSync) totalSize += b.binary.byteLength;
|
|
935
|
+
|
|
936
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
937
|
+
let processedSize = 0;
|
|
938
|
+
for (const bundle of bundlesToSync) {
|
|
939
|
+
Log.log(
|
|
940
|
+
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
941
|
+
);
|
|
942
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
943
|
+
payload: bundle.binary,
|
|
944
|
+
filePath: bundle.devicePath,
|
|
945
|
+
processedSize,
|
|
946
|
+
totalSize: transferTotalSize,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
951
|
+
Log.log(
|
|
952
|
+
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
953
|
+
elapsedMs / 1000
|
|
954
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
955
|
+
);
|
|
956
|
+
return { processedSize, totalSize: transferTotalSize };
|
|
1248
957
|
}
|
|
1249
958
|
|
|
1250
959
|
/**
|
|
1251
960
|
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
1252
961
|
*/
|
|
1253
962
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
1254
|
-
bundle:
|
|
1255
|
-
ProtocolV2ResourceBundleBinary,
|
|
1256
|
-
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1257
|
-
>
|
|
963
|
+
bundle: ProtocolV2ResourceBundleBinary
|
|
1258
964
|
): Promise<boolean> {
|
|
1259
|
-
if (this.params
|
|
965
|
+
if (this.params.forcedUpdateRes) return false;
|
|
1260
966
|
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1261
967
|
|
|
1262
968
|
try {
|
|
@@ -1294,7 +1000,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1294
1000
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1295
1001
|
if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
|
|
1296
1002
|
Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
|
|
1297
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1298
1003
|
return false;
|
|
1299
1004
|
}
|
|
1300
1005
|
|
|
@@ -1304,7 +1009,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1304
1009
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1305
1010
|
await wait(1000);
|
|
1306
1011
|
await this.waitForProtocolV2BootloaderMode();
|
|
1307
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1308
1012
|
return true;
|
|
1309
1013
|
} catch (error) {
|
|
1310
1014
|
if (error instanceof HardwareError) {
|
|
@@ -1393,98 +1097,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1393
1097
|
return entries;
|
|
1394
1098
|
}
|
|
1395
1099
|
|
|
1396
|
-
private buildProtocolV2ExecutionPhases({
|
|
1397
|
-
installSources,
|
|
1398
|
-
resourceSources,
|
|
1399
|
-
}: {
|
|
1400
|
-
installSources: ProtocolV2InstallSource[];
|
|
1401
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1402
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
1403
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1404
|
-
if (resourceSources.length > 0) {
|
|
1405
|
-
phases.push({
|
|
1406
|
-
kind: 'resource-sync',
|
|
1407
|
-
installSources: [],
|
|
1408
|
-
resourceSources,
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1412
|
-
if (bootloaderSources.length > 0) {
|
|
1413
|
-
phases.push(
|
|
1414
|
-
{
|
|
1415
|
-
kind: 'bootloader-install',
|
|
1416
|
-
installSources: bootloaderSources,
|
|
1417
|
-
resourceSources: [],
|
|
1418
|
-
},
|
|
1419
|
-
{
|
|
1420
|
-
kind: 'bootloader-verify',
|
|
1421
|
-
installSources: [],
|
|
1422
|
-
resourceSources: [],
|
|
1423
|
-
}
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1427
|
-
if (componentSources.length > 0) {
|
|
1428
|
-
phases.push({
|
|
1429
|
-
kind: 'component-install',
|
|
1430
|
-
installSources: componentSources,
|
|
1431
|
-
resourceSources: [],
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
phases.push({
|
|
1435
|
-
kind: 'final-verify',
|
|
1436
|
-
installSources: [],
|
|
1437
|
-
resourceSources: [],
|
|
1438
|
-
});
|
|
1439
|
-
return phases;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
private async executeProtocolV2Phases({
|
|
1443
|
-
installSources,
|
|
1444
|
-
resourceSources,
|
|
1445
|
-
}: {
|
|
1446
|
-
installSources: ProtocolV2InstallSource[];
|
|
1447
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1448
|
-
}) {
|
|
1449
|
-
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1450
|
-
installSources,
|
|
1451
|
-
resourceSources,
|
|
1452
|
-
});
|
|
1453
|
-
for (const phase of phases) {
|
|
1454
|
-
if (phase.kind === 'final-verify') {
|
|
1455
|
-
return this.completeProtocolV2FinalVerification();
|
|
1456
|
-
}
|
|
1457
|
-
if (phase.kind === 'resource-sync') {
|
|
1458
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1459
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1460
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1461
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1462
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1463
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1464
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
1465
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
1466
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
throw ERRORS.TypedError(
|
|
1470
|
-
HardwareErrorCode.RuntimeError,
|
|
1471
|
-
'Protocol V2 execution has no final verification phase'
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
private async executeProtocolV2SourceUpdate({
|
|
1476
|
-
installSources,
|
|
1477
|
-
resourceSources,
|
|
1478
|
-
}: {
|
|
1479
|
-
installSources: ProtocolV2InstallSource[];
|
|
1480
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1481
|
-
}) {
|
|
1482
|
-
return this.executeProtocolV2Phases({
|
|
1483
|
-
installSources,
|
|
1484
|
-
resourceSources,
|
|
1485
|
-
});
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
1100
|
private async executeProtocolV2Update({
|
|
1489
1101
|
fwBinaryMap,
|
|
1490
1102
|
bootloaderBinary,
|
|
@@ -1496,158 +1108,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1496
1108
|
installItems?: ProtocolV2InstallItem[];
|
|
1497
1109
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1498
1110
|
}) {
|
|
1499
|
-
const
|
|
1111
|
+
const orderedInstallItems =
|
|
1500
1112
|
installItems ??
|
|
1501
1113
|
this.buildProtocolV2InstallItems({
|
|
1502
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1503
1114
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1115
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1504
1116
|
});
|
|
1505
|
-
try {
|
|
1506
|
-
const installSources = await Promise.all(
|
|
1507
|
-
memoryInstallItems.map(async item => ({
|
|
1508
|
-
fileName: item.fileName,
|
|
1509
|
-
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1510
|
-
targetId: item.targetId,
|
|
1511
|
-
kind: item.kind,
|
|
1512
|
-
}))
|
|
1513
|
-
);
|
|
1514
|
-
const resourceSources = await Promise.all(
|
|
1515
|
-
(resourceBundles ?? []).map(async bundle => ({
|
|
1516
|
-
name: bundle.name,
|
|
1517
|
-
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1518
|
-
devicePath: bundle.devicePath,
|
|
1519
|
-
version: bundle.version,
|
|
1520
|
-
payloadHash: bundle.payloadHash,
|
|
1521
|
-
headerHash: bundle.headerHash,
|
|
1522
|
-
}))
|
|
1523
|
-
);
|
|
1524
|
-
return await this.executeProtocolV2Phases({
|
|
1525
|
-
installSources,
|
|
1526
|
-
resourceSources,
|
|
1527
|
-
});
|
|
1528
|
-
} finally {
|
|
1529
|
-
await this.closeProtocolV2PreparedSources();
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
1117
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
resourceSources,
|
|
1536
|
-
}: ProtocolV2ExecutionPhase) {
|
|
1537
|
-
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1538
|
-
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1539
|
-
for (const resource of resourceSources) {
|
|
1540
|
-
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1541
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1542
|
-
} else {
|
|
1543
|
-
resourcesToSync.push(resource);
|
|
1544
|
-
totalSize += resource.source.size;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1118
|
+
let firmwareSize = 0;
|
|
1119
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1547
1120
|
|
|
1548
1121
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1122
|
+
|
|
1549
1123
|
let processedSize = 0;
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1124
|
+
let totalSize = firmwareSize;
|
|
1125
|
+
if (resourceBundles?.length) {
|
|
1126
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1127
|
+
resourceBundles,
|
|
1128
|
+
firmwareSize
|
|
1129
|
+
);
|
|
1130
|
+
processedSize = resourceTransfer.processedSize;
|
|
1131
|
+
totalSize = resourceTransfer.totalSize;
|
|
1558
1132
|
}
|
|
1559
1133
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
totalSize,
|
|
1568
|
-
});
|
|
1569
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1570
|
-
stagedInstallTargets.push({
|
|
1571
|
-
targetId: item.targetId,
|
|
1572
|
-
path: filePath,
|
|
1573
|
-
});
|
|
1134
|
+
// Skip staging and installation when the update contains RESC bundles only.
|
|
1135
|
+
if (orderedInstallItems.length === 0) {
|
|
1136
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
|
|
1137
|
+
if (totalSize > 0) {
|
|
1138
|
+
this.postProgressMessage(100, 'transferData');
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1574
1141
|
}
|
|
1142
|
+
let transferredSize = processedSize;
|
|
1143
|
+
const transferStartTime = Date.now();
|
|
1144
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1145
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1146
|
+
const onTransferredBytes = (bytes: number) => {
|
|
1147
|
+
transferredSize = bytes;
|
|
1148
|
+
};
|
|
1149
|
+
Log.log(
|
|
1150
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1151
|
+
);
|
|
1575
1152
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1153
|
+
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1154
|
+
|
|
1155
|
+
try {
|
|
1156
|
+
for (const item of orderedInstallItems) {
|
|
1157
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1158
|
+
Log.log(
|
|
1159
|
+
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1160
|
+
);
|
|
1161
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1162
|
+
payload: item.binary,
|
|
1163
|
+
filePath,
|
|
1164
|
+
processedSize,
|
|
1165
|
+
totalSize,
|
|
1166
|
+
onTransferredBytes,
|
|
1167
|
+
});
|
|
1168
|
+
transferredSize = processedSize;
|
|
1169
|
+
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1170
|
+
|
|
1171
|
+
stagedInstallTargets.push({
|
|
1172
|
+
...item,
|
|
1173
|
+
path: filePath,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (totalSize > 0) {
|
|
1178
|
+
this.postProgressMessage(100, 'transferData');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1182
|
+
Log.log(
|
|
1183
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1184
|
+
elapsedMs / 1000
|
|
1185
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1186
|
+
);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1189
|
+
Log.warn(
|
|
1190
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1191
|
+
elapsedMs / 1000
|
|
1192
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1193
|
+
);
|
|
1194
|
+
throw error;
|
|
1581
1195
|
}
|
|
1582
1196
|
|
|
1583
1197
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1584
|
-
|
|
1198
|
+
|
|
1199
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1585
1200
|
target_id: item.targetId,
|
|
1586
1201
|
path: item.path,
|
|
1587
1202
|
}));
|
|
1588
|
-
|
|
1589
|
-
await this.
|
|
1203
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1204
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1205
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1590
1206
|
}
|
|
1591
1207
|
|
|
1592
|
-
private
|
|
1593
|
-
source,
|
|
1594
|
-
filePath,
|
|
1595
|
-
processedSize,
|
|
1596
|
-
totalSize,
|
|
1597
|
-
}: {
|
|
1598
|
-
source: FirmwareByteSource;
|
|
1599
|
-
filePath: string;
|
|
1600
|
-
processedSize: number;
|
|
1601
|
-
totalSize: number;
|
|
1602
|
-
}) {
|
|
1603
|
-
let lastError: unknown;
|
|
1604
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1605
|
-
try {
|
|
1606
|
-
await writeFirmwareByteSource({
|
|
1607
|
-
source,
|
|
1608
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1609
|
-
write: async ({ data, sourceOffset, length, first }) => {
|
|
1610
|
-
const chunkEnd = sourceOffset + length;
|
|
1611
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1612
|
-
processedSize + sourceOffset,
|
|
1613
|
-
processedSize + chunkEnd,
|
|
1614
|
-
totalSize
|
|
1615
|
-
);
|
|
1616
|
-
const response = await this.fileWriteChunk(
|
|
1617
|
-
filePath,
|
|
1618
|
-
source.size,
|
|
1619
|
-
sourceOffset,
|
|
1620
|
-
data,
|
|
1621
|
-
first,
|
|
1622
|
-
progress
|
|
1623
|
-
);
|
|
1624
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
1625
|
-
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1626
|
-
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1627
|
-
throw ERRORS.TypedError(
|
|
1628
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1629
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
this.postProgressMessage(progress, 'transferData');
|
|
1633
|
-
return length;
|
|
1634
|
-
},
|
|
1635
|
-
});
|
|
1636
|
-
return processedSize + source.size;
|
|
1637
|
-
} catch (error) {
|
|
1638
|
-
lastError = error;
|
|
1639
|
-
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1640
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
throw ERRORS.TypedError(
|
|
1645
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1646
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1208
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1651
1209
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1652
1210
|
}
|
|
1653
1211
|
|
|
@@ -1666,8 +1224,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1666
1224
|
|
|
1667
1225
|
private assertProtocolV2TargetStatus(
|
|
1668
1226
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1669
|
-
expectedTargetIds: Set<number
|
|
1670
|
-
expectedPaths = new Map<number, string>()
|
|
1227
|
+
expectedTargetIds: Set<number>
|
|
1671
1228
|
) {
|
|
1672
1229
|
Log.log(
|
|
1673
1230
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1698,47 +1255,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1698
1255
|
const matchingTargets = statusTargets.filter(target =>
|
|
1699
1256
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1700
1257
|
);
|
|
1701
|
-
const seenTargetIds = new Set<number>();
|
|
1702
|
-
for (const target of matchingTargets) {
|
|
1703
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1704
|
-
if (
|
|
1705
|
-
targetId === undefined ||
|
|
1706
|
-
seenTargetIds.has(targetId) ||
|
|
1707
|
-
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1708
|
-
) {
|
|
1709
|
-
throw ERRORS.TypedError(
|
|
1710
|
-
HardwareErrorCode.RuntimeError,
|
|
1711
|
-
`Protocol V2 install status conflicts with target ${target.target_id}`
|
|
1712
|
-
);
|
|
1713
|
-
}
|
|
1714
|
-
seenTargetIds.add(targetId);
|
|
1715
|
-
}
|
|
1716
1258
|
const completedTargets = matchingTargets.filter(target =>
|
|
1717
1259
|
isProtocolV2TargetStatusFinished(target.status)
|
|
1718
1260
|
);
|
|
1719
1261
|
if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1720
|
-
for (const target of completedTargets) {
|
|
1721
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1722
|
-
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1723
|
-
if (targetId === undefined) {
|
|
1724
|
-
throw ERRORS.TypedError(
|
|
1725
|
-
HardwareErrorCode.RuntimeError,
|
|
1726
|
-
'Protocol V2 completed target identity is invalid'
|
|
1727
|
-
);
|
|
1728
|
-
}
|
|
1729
|
-
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1730
|
-
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1731
|
-
if (payloadVersion !== expectedVersion) {
|
|
1732
|
-
throw ERRORS.TypedError(
|
|
1733
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1734
|
-
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1735
|
-
);
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
if (payloadVersion !== undefined) {
|
|
1739
|
-
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
1262
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1743
1263
|
return true;
|
|
1744
1264
|
}
|
|
@@ -1762,9 +1282,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1762
1282
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1763
1283
|
targets: Array<{ target_id: number; path: string }>
|
|
1764
1284
|
) {
|
|
1765
|
-
this.protocolV2FinalStatusVerified = false;
|
|
1766
1285
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1767
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1768
1286
|
const startTime = Date.now();
|
|
1769
1287
|
let lastError: unknown;
|
|
1770
1288
|
|
|
@@ -1788,11 +1306,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1788
1306
|
if (
|
|
1789
1307
|
this.assertProtocolV2TargetStatus(
|
|
1790
1308
|
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1791
|
-
expectedTargetIds
|
|
1792
|
-
expectedPaths
|
|
1309
|
+
expectedTargetIds
|
|
1793
1310
|
)
|
|
1794
1311
|
) {
|
|
1795
|
-
this.protocolV2FinalStatusVerified = true;
|
|
1796
1312
|
return;
|
|
1797
1313
|
}
|
|
1798
1314
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
@@ -1807,34 +1323,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1807
1323
|
// device already rebooted into App, this endpoint error signals that the
|
|
1808
1324
|
// install phase ended; the later phase performs the final readiness check.
|
|
1809
1325
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
commands: this.device.getCommands(),
|
|
1813
|
-
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1814
|
-
});
|
|
1815
|
-
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1816
|
-
const observed = await this.device.probeProtocolV2RuntimeState(
|
|
1817
|
-
deviceInfo,
|
|
1818
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1819
|
-
);
|
|
1820
|
-
if (observed.mode === 'normal' && !observed.bootloaderMode) {
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
lastError = new Error(
|
|
1824
|
-
'Protocol V2 status endpoint is unavailable outside normal mode'
|
|
1825
|
-
);
|
|
1826
|
-
} catch (probeError) {
|
|
1827
|
-
lastError = probeError;
|
|
1828
|
-
}
|
|
1829
|
-
throw ERRORS.TypedError(
|
|
1830
|
-
HardwareErrorCode.RuntimeError,
|
|
1831
|
-
`Protocol V2 firmware install status is unavailable: ${this.normalizeErrorMessage(
|
|
1832
|
-
lastError
|
|
1833
|
-
)}`,
|
|
1834
|
-
{
|
|
1835
|
-
firmwareUpdateCode: 'FirmwareInstallStatusUnavailable',
|
|
1836
|
-
}
|
|
1326
|
+
Log.log(
|
|
1327
|
+
'[FirmwareUpdateV4] firmware status endpoint unavailable after reboot; continue with normal-mode verification'
|
|
1837
1328
|
);
|
|
1329
|
+
return;
|
|
1838
1330
|
}
|
|
1839
1331
|
lastError = error;
|
|
1840
1332
|
Log.log(
|
|
@@ -1850,12 +1342,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1850
1342
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1851
1343
|
throw error;
|
|
1852
1344
|
}
|
|
1853
|
-
if (
|
|
1854
|
-
error instanceof HardwareError &&
|
|
1855
|
-
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1856
|
-
) {
|
|
1857
|
-
throw error;
|
|
1858
|
-
}
|
|
1859
1345
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1860
1346
|
}
|
|
1861
1347
|
await wait(1000);
|
|
@@ -1873,7 +1359,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1873
1359
|
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1874
1360
|
// repeating it after an automatic App reboot is idempotent.
|
|
1875
1361
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1876
|
-
this.protocolV2ExecutionInLoader = false;
|
|
1877
1362
|
}
|
|
1878
1363
|
|
|
1879
1364
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -1884,7 +1369,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1884
1369
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1885
1370
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1886
1371
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1887
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
1888
1372
|
if (firmwareVersion === '0.0.0') {
|
|
1889
1373
|
Log.warn(
|
|
1890
1374
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1898,17 +1382,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1898
1382
|
};
|
|
1899
1383
|
}
|
|
1900
1384
|
|
|
1901
|
-
private async completeProtocolV2FinalVerification() {
|
|
1902
|
-
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1903
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1904
|
-
}
|
|
1905
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1906
|
-
this.assertExpectedProtocolV2Versions();
|
|
1907
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1908
|
-
DevicePool.resetState();
|
|
1909
|
-
return versions;
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
1385
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1913
1386
|
const startTime = Date.now();
|
|
1914
1387
|
let lastError: unknown;
|
|
@@ -2015,6 +1488,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2015
1488
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
2016
1489
|
}
|
|
2017
1490
|
|
|
1491
|
+
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1492
|
+
let lastError: unknown;
|
|
1493
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1494
|
+
try {
|
|
1495
|
+
return await this.protocolV2WriteWholeFile(params);
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
lastError = error;
|
|
1498
|
+
Log.error(
|
|
1499
|
+
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1500
|
+
error
|
|
1501
|
+
);
|
|
1502
|
+
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
throw ERRORS.TypedError(
|
|
1510
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1511
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
private async protocolV2WriteWholeFile({
|
|
1516
|
+
payload,
|
|
1517
|
+
filePath,
|
|
1518
|
+
processedSize,
|
|
1519
|
+
totalSize,
|
|
1520
|
+
onTransferredBytes,
|
|
1521
|
+
}: ProtocolV2FileTransferParams) {
|
|
1522
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1523
|
+
let offset = 0;
|
|
1524
|
+
const getUploadProgress = (fileOffset: number) => {
|
|
1525
|
+
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1526
|
+
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1527
|
+
}
|
|
1528
|
+
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1531
|
+
while (offset < payload.byteLength) {
|
|
1532
|
+
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1533
|
+
const chunkLength = chunkEnd - offset;
|
|
1534
|
+
const chunk = payload.slice(offset, chunkEnd);
|
|
1535
|
+
const overwrite = offset === 0;
|
|
1536
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1537
|
+
(processedSize ?? 0) + offset,
|
|
1538
|
+
(processedSize ?? 0) + chunkEnd,
|
|
1539
|
+
totalSize ?? payload.byteLength
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
const writeRes = await this.fileWriteChunk(
|
|
1543
|
+
filePath,
|
|
1544
|
+
payload.byteLength,
|
|
1545
|
+
offset,
|
|
1546
|
+
chunk,
|
|
1547
|
+
overwrite,
|
|
1548
|
+
progress
|
|
1549
|
+
);
|
|
1550
|
+
const rawProcessedByte = writeRes.message.processed_byte;
|
|
1551
|
+
const processedByte = Number(rawProcessedByte);
|
|
1552
|
+
const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
|
|
1553
|
+
if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
|
|
1554
|
+
throw ERRORS.TypedError(
|
|
1555
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1556
|
+
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
offset = nextOffset;
|
|
1560
|
+
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1561
|
+
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1568
|
+
const env = DataManager.getSettings('env');
|
|
1569
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1570
|
+
return 'BLE';
|
|
1571
|
+
}
|
|
1572
|
+
if (
|
|
1573
|
+
env &&
|
|
1574
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1575
|
+
) {
|
|
1576
|
+
return 'WebUSB';
|
|
1577
|
+
}
|
|
1578
|
+
return env ?? 'unknown';
|
|
1579
|
+
}
|
|
1580
|
+
|
|
2018
1581
|
private async fileWriteChunk(
|
|
2019
1582
|
filePath: string,
|
|
2020
1583
|
totalFileSize: number,
|