@onekeyfe/hd-core 1.2.0-alpha.73 → 1.2.0-alpha.75
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__/connectSettings.test.ts +5 -45
- package/__tests__/devicePoolBootloaderPath.test.ts +64 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/protocol-v2.test.ts +100 -405
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -10
- 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 -25
- package/dist/api/FirmwareUpdateV4.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 -8
- 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/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 +0 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -0
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/index.d.ts +2 -180
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1076 -3080
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -4
- 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 -68
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +1 -9
- package/dist/types/api/index.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/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +6 -57
- package/src/api/FirmwareUpdate.ts +1 -13
- package/src/api/FirmwareUpdateV2.ts +121 -296
- package/src/api/FirmwareUpdateV3.ts +57 -263
- package/src/api/FirmwareUpdateV4.ts +347 -875
- package/src/api/device/DeviceUpdateBootloader.ts +6 -125
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +1 -72
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +22 -140
- package/src/core/index.ts +16 -0
- package/src/data-manager/DataManager.ts +54 -62
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +3 -7
- package/src/device/DeviceConnector.ts +2 -2
- package/src/device/DevicePool.ts +15 -1
- package/src/index.ts +0 -5
- package/src/inject.ts +2 -22
- package/src/lowLevelInject.ts +1 -5
- package/src/topLevelInject.ts +1 -5
- package/src/types/api/checkAllFirmwareRelease.ts +0 -4
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +0 -18
- package/src/types/api/firmwareUpdate.ts +0 -75
- package/src/types/api/index.ts +0 -14
- package/src/types/settings.ts +0 -13
- package/src/utils/deviceFeaturesCompat.ts +0 -6
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/device-update-bootloader.test.ts +0 -87
- 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-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -231
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -63
- 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 -32
- 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 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
|
-
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
5
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
5
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
7
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
@@ -10,6 +9,7 @@ import { sha256 } from '@noble/hashes/sha256';
|
|
|
10
9
|
|
|
11
10
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
12
11
|
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
12
|
+
import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
|
|
13
13
|
import { validateParams } from './helpers/paramsValidator';
|
|
14
14
|
import {
|
|
15
15
|
LoggerNames,
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
getLogger,
|
|
22
22
|
} from '../utils';
|
|
23
23
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
24
|
-
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
25
24
|
import { DataManager } from '../data-manager';
|
|
26
25
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
27
26
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -40,20 +39,11 @@ import {
|
|
|
40
39
|
getProtocolV2UnknownErrorText,
|
|
41
40
|
isProtocolV2DeviceDisconnectedError,
|
|
42
41
|
} from './protocol-v2/helpers';
|
|
43
|
-
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
44
|
-
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
45
|
-
import {
|
|
46
|
-
assertFirmwareUpdatePreparedPlanBinding,
|
|
47
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
48
|
-
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
49
42
|
|
|
50
|
-
import type {
|
|
51
|
-
FirmwareArtifactReference,
|
|
52
|
-
FirmwareUpdateV4Params,
|
|
53
|
-
FirmwareUpdateV4Target,
|
|
54
|
-
} from '../types/api/firmwareUpdate';
|
|
43
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
55
44
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
56
45
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
46
|
+
import type { PROTO } from '../constants';
|
|
57
47
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
58
48
|
import type {
|
|
59
49
|
Features,
|
|
@@ -63,7 +53,6 @@ import type {
|
|
|
63
53
|
IProtocolV2ResourceFile,
|
|
64
54
|
IVersionArray,
|
|
65
55
|
} from '../types';
|
|
66
|
-
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
67
56
|
|
|
68
57
|
const Log = getLogger(LoggerNames.Method);
|
|
69
58
|
|
|
@@ -86,7 +75,7 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
86
75
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
87
76
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
88
77
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
89
|
-
const
|
|
78
|
+
const PROTOCOL_V2_PROGRESS_HEARTBEAT_INTERVAL = 1000;
|
|
90
79
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
91
80
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
92
81
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -131,6 +120,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
131
120
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
132
121
|
};
|
|
133
122
|
|
|
123
|
+
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
124
|
+
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
125
|
+
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
126
|
+
};
|
|
127
|
+
|
|
134
128
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
135
129
|
target_id: number | string;
|
|
136
130
|
status?: number | string;
|
|
@@ -144,6 +138,22 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
144
138
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
145
139
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
146
140
|
};
|
|
141
|
+
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
142
|
+
path: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
146
|
+
filePath: string;
|
|
147
|
+
processedSize?: number;
|
|
148
|
+
totalSize?: number;
|
|
149
|
+
onTransferredBytes?: (transferredBytes: number) => void;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
type ProtocolV2ProgressReportState = {
|
|
153
|
+
lastProgress?: number;
|
|
154
|
+
lastReportedAt?: number;
|
|
155
|
+
};
|
|
156
|
+
|
|
147
157
|
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
148
158
|
type ProtocolV2ResourceBundleBinary = {
|
|
149
159
|
name: string;
|
|
@@ -161,35 +171,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
161
171
|
kind: 'bootloader' | 'firmware';
|
|
162
172
|
};
|
|
163
173
|
|
|
164
|
-
type ProtocolV2InstallSource = {
|
|
165
|
-
fileName: string;
|
|
166
|
-
source: FirmwareByteSource;
|
|
167
|
-
targetId: number;
|
|
168
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
type ProtocolV2ResourceBundleSource = {
|
|
172
|
-
name: string;
|
|
173
|
-
source: FirmwareByteSource;
|
|
174
|
-
devicePath: string;
|
|
175
|
-
version?: IVersionArray;
|
|
176
|
-
payloadHash?: string;
|
|
177
|
-
headerHash?: string;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
type ProtocolV2ExecutionPhaseKind =
|
|
181
|
-
| 'resource-sync'
|
|
182
|
-
| 'bootloader-install'
|
|
183
|
-
| 'bootloader-verify'
|
|
184
|
-
| 'component-install'
|
|
185
|
-
| 'final-verify';
|
|
186
|
-
|
|
187
|
-
type ProtocolV2ExecutionPhase = {
|
|
188
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
189
|
-
installSources: ProtocolV2InstallSource[];
|
|
190
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
191
|
-
};
|
|
192
|
-
|
|
193
174
|
type ProtocolV2OkppHeader = {
|
|
194
175
|
type: string;
|
|
195
176
|
version: IVersionArray;
|
|
@@ -298,12 +279,6 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
298
279
|
);
|
|
299
280
|
};
|
|
300
281
|
|
|
301
|
-
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
302
|
-
error instanceof HardwareError &&
|
|
303
|
-
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
304
|
-
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
305
|
-
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
306
|
-
|
|
307
282
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
308
283
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
309
284
|
|
|
@@ -341,18 +316,6 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
341
316
|
|
|
342
317
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
343
318
|
|
|
344
|
-
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
345
|
-
if (!version) return undefined;
|
|
346
|
-
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
350
|
-
const currentNumber = versionArrayToNumber(current);
|
|
351
|
-
const targetNumber = versionArrayToNumber(target);
|
|
352
|
-
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
353
|
-
return currentNumber - targetNumber;
|
|
354
|
-
};
|
|
355
|
-
|
|
356
319
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
357
320
|
Array.from(bytes)
|
|
358
321
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -486,16 +449,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
486
449
|
return ['V2'] as const;
|
|
487
450
|
}
|
|
488
451
|
|
|
489
|
-
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
490
|
-
|
|
491
|
-
private protocolV2ExecutionInLoader = false;
|
|
492
|
-
|
|
493
|
-
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
494
|
-
|
|
495
|
-
private protocolV2LatestFinalFeatures?: Features;
|
|
496
|
-
|
|
497
|
-
private protocolV2FinalStatusVerified = false;
|
|
498
|
-
|
|
499
452
|
init() {
|
|
500
453
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
501
454
|
this.requireDeviceMode = [];
|
|
@@ -533,62 +486,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
533
486
|
{ name: 'firmwareType', type: 'string' },
|
|
534
487
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
535
488
|
{ name: 'platform', type: 'string' },
|
|
536
|
-
{ name: 'expectedDeviceId', type: 'string' },
|
|
537
489
|
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
538
490
|
]);
|
|
539
|
-
if (
|
|
540
|
-
payload.expectedDeviceId !== undefined &&
|
|
541
|
-
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
542
|
-
) {
|
|
543
|
-
throw ERRORS.TypedError(
|
|
544
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
545
|
-
'Protocol V2 expected device identity is invalid'
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
const hostBinding =
|
|
549
|
-
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
550
|
-
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
551
|
-
: undefined;
|
|
552
|
-
if (hostBinding) {
|
|
553
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
554
|
-
preparedPlan: payload.preparedPlan,
|
|
555
|
-
executor: 'v4',
|
|
556
|
-
platform: payload.platform,
|
|
557
|
-
scopeTargets: [
|
|
558
|
-
'boot',
|
|
559
|
-
'app_v1',
|
|
560
|
-
'app_v2',
|
|
561
|
-
'coprocessor',
|
|
562
|
-
'resource',
|
|
563
|
-
'se01',
|
|
564
|
-
'se02',
|
|
565
|
-
'se03',
|
|
566
|
-
'se04',
|
|
567
|
-
],
|
|
568
|
-
bindings: [
|
|
569
|
-
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
570
|
-
artifact
|
|
571
|
-
? [
|
|
572
|
-
{
|
|
573
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
574
|
-
artifact,
|
|
575
|
-
},
|
|
576
|
-
]
|
|
577
|
-
: []
|
|
578
|
-
),
|
|
579
|
-
...(payload.resourceBundleArtifacts ?? []).map(
|
|
580
|
-
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
581
|
-
target: 'resource' as const,
|
|
582
|
-
logicalName: entry.name,
|
|
583
|
-
artifact: entry.artifact,
|
|
584
|
-
})
|
|
585
|
-
),
|
|
586
|
-
],
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
491
|
|
|
590
492
|
this.params = {
|
|
591
|
-
preparedPlan: payload.preparedPlan,
|
|
592
493
|
chunkSize: payload.chunkSize,
|
|
593
494
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
594
495
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -603,29 +504,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
603
504
|
resourceFiles: payload.resourceFiles,
|
|
604
505
|
firmwareType: payload.firmwareType,
|
|
605
506
|
targetsToUpdate: payload.targetsToUpdate,
|
|
606
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
607
507
|
platform: payload.platform,
|
|
608
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
609
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
610
|
-
componentArtifacts: payload.componentArtifacts,
|
|
611
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
612
508
|
};
|
|
613
509
|
}
|
|
614
510
|
|
|
615
|
-
private getProtocolV2FirmwareChunkSize(
|
|
511
|
+
private getProtocolV2FirmwareChunkSize(filePath?: string) {
|
|
616
512
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
617
513
|
const env = DataManager.getSettings('env');
|
|
618
514
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
619
515
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
620
516
|
if (isBle) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
// Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
|
|
625
|
-
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
626
|
-
? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
|
|
627
|
-
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
628
|
-
}
|
|
517
|
+
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
518
|
+
? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
|
|
519
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
629
520
|
}
|
|
630
521
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
631
522
|
return maxChunkSize;
|
|
@@ -652,20 +543,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
652
543
|
assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
|
|
653
544
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
654
545
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (
|
|
658
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
659
|
-
this.params.resourceBundleArtifacts?.length
|
|
660
|
-
) {
|
|
661
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
662
|
-
}
|
|
663
|
-
const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
|
|
664
|
-
const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
665
|
-
const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
666
|
-
const needsRemoteResources = !hasExplicitResourceFiles && wantsStableResources;
|
|
667
|
-
const needsRemoteBootResources =
|
|
668
|
-
!hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
|
|
546
|
+
const needsRemoteResources =
|
|
547
|
+
!this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
|
|
669
548
|
|
|
670
549
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
671
550
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -677,19 +556,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
677
556
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
678
557
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
679
558
|
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
throw ERRORS.TypedError(
|
|
686
|
-
HardwareErrorCode.RuntimeError,
|
|
687
|
-
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
688
|
-
{
|
|
689
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
690
|
-
}
|
|
691
|
-
);
|
|
692
|
-
}
|
|
559
|
+
const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
|
|
560
|
+
const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
561
|
+
const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
562
|
+
const needsRemoteBootResources =
|
|
563
|
+
!hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
|
|
693
564
|
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
694
565
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
695
566
|
await DataManager.forceReloadData({
|
|
@@ -716,7 +587,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
716
587
|
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
717
588
|
throw err;
|
|
718
589
|
}
|
|
719
|
-
throw
|
|
590
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
720
591
|
}
|
|
721
592
|
|
|
722
593
|
if (
|
|
@@ -732,8 +603,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
732
603
|
);
|
|
733
604
|
}
|
|
734
605
|
|
|
606
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
607
|
+
|
|
735
608
|
if (needsRemoteResources) {
|
|
736
|
-
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
737
609
|
try {
|
|
738
610
|
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
739
611
|
resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
|
|
@@ -749,293 +621,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
749
621
|
);
|
|
750
622
|
}
|
|
751
623
|
}
|
|
752
|
-
throw
|
|
624
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
753
625
|
}
|
|
754
626
|
}
|
|
755
627
|
|
|
756
|
-
|
|
628
|
+
await this.executeProtocolV2Update({
|
|
757
629
|
fwBinaryMap,
|
|
758
630
|
bootloaderBinary,
|
|
759
631
|
...(installItems ? { installItems } : undefined),
|
|
760
632
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
761
633
|
});
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
765
|
-
const source = await openFirmwareByteSource({
|
|
766
|
-
artifact,
|
|
767
|
-
reader: this.params.artifactReader,
|
|
768
|
-
});
|
|
769
|
-
if (!source) {
|
|
770
|
-
throw ERRORS.TypedError(
|
|
771
|
-
HardwareErrorCode.RuntimeError,
|
|
772
|
-
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
773
|
-
{
|
|
774
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
775
|
-
}
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
this.protocolV2PreparedSources.push(source);
|
|
779
|
-
return source;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
783
|
-
const source = await openFirmwareByteSource({ binary });
|
|
784
|
-
if (!source) {
|
|
785
|
-
throw ERRORS.TypedError(
|
|
786
|
-
HardwareErrorCode.RuntimeError,
|
|
787
|
-
'Protocol V2 firmware binary is empty'
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
this.protocolV2PreparedSources.push(source);
|
|
791
|
-
return source;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
private async closeProtocolV2PreparedSources() {
|
|
795
|
-
const sources = this.protocolV2PreparedSources.splice(0);
|
|
796
|
-
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
private async prepareProtocolV2InstallSources(
|
|
800
|
-
firmwareType: EFirmwareType,
|
|
801
|
-
features: Features
|
|
802
|
-
): Promise<ProtocolV2InstallSource[]> {
|
|
803
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
804
|
-
if (!release) {
|
|
805
|
-
throw ERRORS.TypedError(
|
|
806
|
-
HardwareErrorCode.RuntimeError,
|
|
807
|
-
'Protocol V2 firmware release is unavailable'
|
|
808
|
-
);
|
|
809
|
-
}
|
|
810
|
-
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
811
|
-
const requestedTargets = new Set(
|
|
812
|
-
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
813
|
-
);
|
|
814
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
815
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
816
|
-
|
|
817
|
-
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
818
|
-
const target = this.getRemoteComponentTarget(key, component);
|
|
819
|
-
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
820
|
-
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
821
|
-
const artifact = componentArtifacts[updateTarget];
|
|
822
|
-
if (!artifact) {
|
|
823
|
-
throw ERRORS.TypedError(
|
|
824
|
-
HardwareErrorCode.RuntimeError,
|
|
825
|
-
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
826
|
-
{
|
|
827
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
828
|
-
artifactName: updateTarget,
|
|
829
|
-
}
|
|
830
|
-
);
|
|
831
|
-
}
|
|
832
|
-
installSources.push({
|
|
833
|
-
...target,
|
|
834
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
835
|
-
});
|
|
836
|
-
preparedTargets.add(updateTarget);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
634
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
);
|
|
843
|
-
if (unknownTarget) {
|
|
844
|
-
throw ERRORS.TypedError(
|
|
845
|
-
HardwareErrorCode.RuntimeError,
|
|
846
|
-
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
return installSources;
|
|
850
|
-
}
|
|
635
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
636
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
851
637
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
856
|
-
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
857
|
-
const resourceRequested =
|
|
858
|
-
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
859
|
-
if (!resourceRequested) {
|
|
860
|
-
return [];
|
|
861
|
-
}
|
|
862
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
863
|
-
const descriptors = release?.resourceBundles ?? [];
|
|
864
|
-
const artifactByName = new Map(
|
|
865
|
-
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
866
|
-
);
|
|
867
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
868
|
-
for (const descriptor of descriptors) {
|
|
869
|
-
const artifact = artifactByName.get(descriptor.name);
|
|
870
|
-
if (!artifact) {
|
|
871
|
-
throw ERRORS.TypedError(
|
|
872
|
-
HardwareErrorCode.RuntimeError,
|
|
873
|
-
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
874
|
-
{
|
|
875
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
876
|
-
artifactName: descriptor.name,
|
|
877
|
-
}
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
881
|
-
descriptor.devicePath,
|
|
882
|
-
'resourceBundles[].devicePath'
|
|
883
|
-
);
|
|
884
|
-
sources.push({
|
|
885
|
-
name: descriptor.name,
|
|
886
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
887
|
-
devicePath,
|
|
888
|
-
version: descriptor.version,
|
|
889
|
-
payloadHash: descriptor.payloadHash,
|
|
890
|
-
headerHash: descriptor.headerHash,
|
|
891
|
-
});
|
|
892
|
-
artifactByName.delete(descriptor.name);
|
|
893
|
-
}
|
|
894
|
-
if (artifactByName.size > 0) {
|
|
895
|
-
throw ERRORS.TypedError(
|
|
896
|
-
HardwareErrorCode.RuntimeError,
|
|
897
|
-
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
|
-
return sources;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
904
|
-
try {
|
|
905
|
-
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
906
|
-
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
907
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
908
|
-
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
909
|
-
throw ERRORS.TypedError(
|
|
910
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
911
|
-
'No firmware to update'
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
915
|
-
|
|
916
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
917
|
-
installSources,
|
|
918
|
-
resourceSources,
|
|
919
|
-
});
|
|
920
|
-
} finally {
|
|
921
|
-
await this.closeProtocolV2PreparedSources();
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
private validateExpectedTargetVersions() {
|
|
926
|
-
const expected = this.params.expectedTargetVersions;
|
|
927
|
-
if (expected === undefined) return;
|
|
928
|
-
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
929
|
-
throw ERRORS.TypedError(
|
|
930
|
-
HardwareErrorCode.RuntimeError,
|
|
931
|
-
'Protocol V2 expected target versions are invalid'
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
for (const [target, version] of Object.entries(expected)) {
|
|
935
|
-
if (
|
|
936
|
-
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
937
|
-
target as FirmwareUpdateV4Target
|
|
938
|
-
) ||
|
|
939
|
-
typeof version !== 'string' ||
|
|
940
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
941
|
-
version.length > 64
|
|
942
|
-
) {
|
|
943
|
-
throw ERRORS.TypedError(
|
|
944
|
-
HardwareErrorCode.RuntimeError,
|
|
945
|
-
'Protocol V2 expected target version is invalid'
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
952
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
953
|
-
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
954
|
-
if (!version) return undefined;
|
|
955
|
-
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
956
|
-
if (
|
|
957
|
-
parts.length !== 3 ||
|
|
958
|
-
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
959
|
-
) {
|
|
960
|
-
return undefined;
|
|
961
|
-
}
|
|
962
|
-
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
966
|
-
const expected = this.params?.expectedTargetVersions;
|
|
967
|
-
if (!expected) return;
|
|
968
|
-
const features = this.protocolV2LatestFinalFeatures;
|
|
969
|
-
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
970
|
-
const applicationTargets = requestedTargets.filter(
|
|
971
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
972
|
-
);
|
|
973
|
-
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
974
|
-
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
975
|
-
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
976
|
-
se01: features?.se01Version ?? undefined,
|
|
977
|
-
se02: features?.se02Version ?? undefined,
|
|
978
|
-
se03: features?.se03Version ?? undefined,
|
|
979
|
-
se04: features?.se04Version ?? undefined,
|
|
980
|
-
};
|
|
981
|
-
if (features && applicationTargets.length === 1) {
|
|
982
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
983
|
-
}
|
|
984
|
-
const expectedEntries = (
|
|
985
|
-
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
986
|
-
).filter(([target]) => !targets || targets.includes(target));
|
|
987
|
-
for (const [target, expectedVersion] of expectedEntries) {
|
|
988
|
-
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
989
|
-
([, mappedTarget]) => mappedTarget === target
|
|
990
|
-
)?.[0];
|
|
991
|
-
const statusVersion =
|
|
992
|
-
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
993
|
-
const observedVersion =
|
|
994
|
-
statusVersion === undefined
|
|
995
|
-
? visibleVersions[target]
|
|
996
|
-
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
997
|
-
Math.floor(statusVersion / 0x100) % 0x100
|
|
998
|
-
}.${statusVersion % 0x100}`;
|
|
999
|
-
if (!observedVersion) {
|
|
1000
|
-
if (this.protocolV2FinalStatusVerified) {
|
|
1001
|
-
Log.warn(
|
|
1002
|
-
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
1003
|
-
);
|
|
1004
|
-
} else {
|
|
1005
|
-
throw ERRORS.TypedError(
|
|
1006
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1007
|
-
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
} else if (observedVersion !== expectedVersion) {
|
|
1011
|
-
throw ERRORS.TypedError(
|
|
1012
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1013
|
-
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
638
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
639
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
640
|
+
DevicePool.resetState();
|
|
1018
641
|
|
|
1019
|
-
|
|
1020
|
-
if (this.params.targetsToUpdate?.length) {
|
|
1021
|
-
return [...new Set(this.params.targetsToUpdate)];
|
|
1022
|
-
}
|
|
1023
|
-
const targets = new Set<FirmwareUpdateV4Target>();
|
|
1024
|
-
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
1025
|
-
targets.add(target as FirmwareUpdateV4Target)
|
|
1026
|
-
);
|
|
1027
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
|
|
1028
|
-
targets.add('resource');
|
|
1029
|
-
}
|
|
1030
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1031
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1032
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
1033
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
1034
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
1035
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
1036
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
1037
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
1038
|
-
return Array.from(targets);
|
|
642
|
+
return versions;
|
|
1039
643
|
}
|
|
1040
644
|
|
|
1041
645
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -1079,17 +683,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1079
683
|
private async captureProtocolV2PhysicalIdentity() {
|
|
1080
684
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
1081
685
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
1082
|
-
const path = this.device.originalDescriptor
|
|
1083
|
-
|
|
1084
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1085
|
-
preparedPlan: this.params.preparedPlan,
|
|
1086
|
-
deviceIdentity: serialNumber,
|
|
1087
|
-
});
|
|
1088
|
-
} else if (this.params?.expectedDeviceId) {
|
|
1089
|
-
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
1090
|
-
} else {
|
|
1091
|
-
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1092
|
-
}
|
|
686
|
+
const path = this.device.originalDescriptor.path?.trim() || undefined;
|
|
687
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1093
688
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
1094
689
|
this.protocolV2ExpectedPath = path;
|
|
1095
690
|
}
|
|
@@ -1455,88 +1050,38 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1455
1050
|
};
|
|
1456
1051
|
}
|
|
1457
1052
|
|
|
1458
|
-
private
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1053
|
+
private async syncProtocolV2ResourceBundles(
|
|
1054
|
+
bundles: ProtocolV2ResourceBundleBinary[],
|
|
1055
|
+
firmwareSize: number
|
|
1056
|
+
): Promise<{ processedSize: number; totalSize: number }> {
|
|
1057
|
+
const transferStartTime = Date.now();
|
|
1058
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1463
1059
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1468
|
-
path: filePath,
|
|
1469
|
-
});
|
|
1470
|
-
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1471
|
-
if (
|
|
1472
|
-
!pathInfoRes.message?.exist ||
|
|
1473
|
-
pathInfoRes.message?.directory ||
|
|
1474
|
-
fileSize === undefined ||
|
|
1475
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1476
|
-
) {
|
|
1477
|
-
return null;
|
|
1478
|
-
}
|
|
1060
|
+
// Write directly to the device through FileWrite.
|
|
1061
|
+
let totalSize = 0;
|
|
1062
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
1479
1063
|
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
chunk_len: readLen,
|
|
1492
|
-
ui_percentage: undefined,
|
|
1064
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
1065
|
+
let processedSize = 0;
|
|
1066
|
+
for (const bundle of bundles) {
|
|
1067
|
+
Log.log(
|
|
1068
|
+
`[FirmwareUpdateV4] syncing resource ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
1069
|
+
);
|
|
1070
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1071
|
+
payload: bundle.binary,
|
|
1072
|
+
filePath: bundle.devicePath,
|
|
1073
|
+
processedSize,
|
|
1074
|
+
totalSize: transferTotalSize,
|
|
1493
1075
|
});
|
|
1494
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
1495
|
-
if (data.byteLength === 0) return null;
|
|
1496
|
-
chunks.push(data);
|
|
1497
|
-
offset += data.byteLength;
|
|
1498
1076
|
}
|
|
1499
1077
|
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
return
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
/** Compare a prepared okpkg header when its manifest supplies version or hash metadata. */
|
|
1510
|
-
private async isProtocolV2ResourceBundleUpToDate(
|
|
1511
|
-
bundle: Pick<
|
|
1512
|
-
ProtocolV2ResourceBundleSource,
|
|
1513
|
-
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1514
|
-
>
|
|
1515
|
-
): Promise<boolean> {
|
|
1516
|
-
if (this.params?.forcedUpdateRes) return false;
|
|
1517
|
-
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1518
|
-
|
|
1519
|
-
try {
|
|
1520
|
-
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1521
|
-
if (!header) return false;
|
|
1522
|
-
|
|
1523
|
-
if (bundle.version) {
|
|
1524
|
-
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
1525
|
-
if (cmp === undefined || cmp !== 0) return false;
|
|
1526
|
-
}
|
|
1527
|
-
if (bundle.payloadHash) {
|
|
1528
|
-
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1529
|
-
if (expected && header.payloadHash !== expected) return false;
|
|
1530
|
-
}
|
|
1531
|
-
if (bundle.headerHash) {
|
|
1532
|
-
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1533
|
-
if (expected && header.headerHash !== expected) return false;
|
|
1534
|
-
}
|
|
1535
|
-
return true;
|
|
1536
|
-
} catch (error) {
|
|
1537
|
-
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1538
|
-
return false;
|
|
1539
|
-
}
|
|
1078
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1079
|
+
Log.log(
|
|
1080
|
+
`[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1081
|
+
elapsedMs / 1000
|
|
1082
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1083
|
+
);
|
|
1084
|
+
return { processedSize, totalSize: transferTotalSize };
|
|
1540
1085
|
}
|
|
1541
1086
|
|
|
1542
1087
|
private isProtocolV2BootloaderMode() {
|
|
@@ -1568,12 +1113,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1568
1113
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1569
1114
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1570
1115
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1571
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1572
1116
|
return false;
|
|
1573
1117
|
}
|
|
1574
1118
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1575
1119
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1576
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1577
1120
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1578
1121
|
return false;
|
|
1579
1122
|
}
|
|
@@ -1583,7 +1126,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1583
1126
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1584
1127
|
await wait(1000);
|
|
1585
1128
|
await this.waitForProtocolV2BootloaderMode();
|
|
1586
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1587
1129
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1588
1130
|
return true;
|
|
1589
1131
|
} catch (error) {
|
|
@@ -1679,98 +1221,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1679
1221
|
return entries;
|
|
1680
1222
|
}
|
|
1681
1223
|
|
|
1682
|
-
private buildProtocolV2ExecutionPhases({
|
|
1683
|
-
installSources,
|
|
1684
|
-
resourceSources,
|
|
1685
|
-
}: {
|
|
1686
|
-
installSources: ProtocolV2InstallSource[];
|
|
1687
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1688
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
1689
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1690
|
-
if (resourceSources.length > 0) {
|
|
1691
|
-
phases.push({
|
|
1692
|
-
kind: 'resource-sync',
|
|
1693
|
-
installSources: [],
|
|
1694
|
-
resourceSources,
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1698
|
-
if (bootloaderSources.length > 0) {
|
|
1699
|
-
phases.push(
|
|
1700
|
-
{
|
|
1701
|
-
kind: 'bootloader-install',
|
|
1702
|
-
installSources: bootloaderSources,
|
|
1703
|
-
resourceSources: [],
|
|
1704
|
-
},
|
|
1705
|
-
{
|
|
1706
|
-
kind: 'bootloader-verify',
|
|
1707
|
-
installSources: [],
|
|
1708
|
-
resourceSources: [],
|
|
1709
|
-
}
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1713
|
-
if (componentSources.length > 0) {
|
|
1714
|
-
phases.push({
|
|
1715
|
-
kind: 'component-install',
|
|
1716
|
-
installSources: componentSources,
|
|
1717
|
-
resourceSources: [],
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
phases.push({
|
|
1721
|
-
kind: 'final-verify',
|
|
1722
|
-
installSources: [],
|
|
1723
|
-
resourceSources: [],
|
|
1724
|
-
});
|
|
1725
|
-
return phases;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
private async executeProtocolV2Phases({
|
|
1729
|
-
installSources,
|
|
1730
|
-
resourceSources,
|
|
1731
|
-
}: {
|
|
1732
|
-
installSources: ProtocolV2InstallSource[];
|
|
1733
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1734
|
-
}) {
|
|
1735
|
-
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1736
|
-
installSources,
|
|
1737
|
-
resourceSources,
|
|
1738
|
-
});
|
|
1739
|
-
for (const phase of phases) {
|
|
1740
|
-
if (phase.kind === 'final-verify') {
|
|
1741
|
-
return this.completeProtocolV2FinalVerification();
|
|
1742
|
-
}
|
|
1743
|
-
if (phase.kind === 'resource-sync') {
|
|
1744
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1745
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1746
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1747
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1748
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1749
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1750
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
1751
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
1752
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
throw ERRORS.TypedError(
|
|
1756
|
-
HardwareErrorCode.RuntimeError,
|
|
1757
|
-
'Protocol V2 execution has no final verification phase'
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
private async executeProtocolV2SourceUpdate({
|
|
1762
|
-
installSources,
|
|
1763
|
-
resourceSources,
|
|
1764
|
-
}: {
|
|
1765
|
-
installSources: ProtocolV2InstallSource[];
|
|
1766
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1767
|
-
}) {
|
|
1768
|
-
return this.executeProtocolV2Phases({
|
|
1769
|
-
installSources,
|
|
1770
|
-
resourceSources,
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
1224
|
private async executeProtocolV2Update({
|
|
1775
1225
|
fwBinaryMap,
|
|
1776
1226
|
bootloaderBinary,
|
|
@@ -1782,168 +1232,106 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1782
1232
|
installItems?: ProtocolV2InstallItem[];
|
|
1783
1233
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1784
1234
|
}) {
|
|
1785
|
-
const
|
|
1235
|
+
const orderedInstallItems =
|
|
1786
1236
|
installItems ??
|
|
1787
1237
|
this.buildProtocolV2InstallItems({
|
|
1788
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1789
1238
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1239
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1790
1240
|
});
|
|
1791
|
-
try {
|
|
1792
|
-
const installSources = await Promise.all(
|
|
1793
|
-
memoryInstallItems.map(async item => ({
|
|
1794
|
-
fileName: item.fileName,
|
|
1795
|
-
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1796
|
-
targetId: item.targetId,
|
|
1797
|
-
kind: item.kind,
|
|
1798
|
-
}))
|
|
1799
|
-
);
|
|
1800
|
-
const resourceSources = await Promise.all(
|
|
1801
|
-
(resourceBundles ?? []).map(async bundle => ({
|
|
1802
|
-
name: bundle.name,
|
|
1803
|
-
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1804
|
-
devicePath: bundle.devicePath,
|
|
1805
|
-
}))
|
|
1806
|
-
);
|
|
1807
|
-
return await this.executeProtocolV2Phases({
|
|
1808
|
-
installSources,
|
|
1809
|
-
resourceSources,
|
|
1810
|
-
});
|
|
1811
|
-
} finally {
|
|
1812
|
-
await this.closeProtocolV2PreparedSources();
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
1241
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
resourceSources,
|
|
1819
|
-
}: ProtocolV2ExecutionPhase) {
|
|
1820
|
-
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1821
|
-
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1822
|
-
for (const resource of resourceSources) {
|
|
1823
|
-
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1824
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1825
|
-
} else {
|
|
1826
|
-
resourcesToSync.push(resource);
|
|
1827
|
-
totalSize += resource.source.size;
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1242
|
+
let firmwareSize = 0;
|
|
1243
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1830
1244
|
|
|
1831
1245
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1246
|
+
|
|
1832
1247
|
let processedSize = 0;
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1248
|
+
let totalSize = firmwareSize;
|
|
1249
|
+
if (resourceBundles?.length) {
|
|
1250
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1251
|
+
resourceBundles,
|
|
1252
|
+
firmwareSize
|
|
1253
|
+
);
|
|
1254
|
+
processedSize = resourceTransfer.processedSize;
|
|
1255
|
+
totalSize = resourceTransfer.totalSize;
|
|
1841
1256
|
}
|
|
1842
1257
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
totalSize,
|
|
1851
|
-
});
|
|
1852
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1853
|
-
stagedInstallTargets.push({
|
|
1854
|
-
targetId: item.targetId,
|
|
1855
|
-
path: filePath,
|
|
1856
|
-
});
|
|
1258
|
+
// Skip staging and installation when the update contains resource files only.
|
|
1259
|
+
if (orderedInstallItems.length === 0) {
|
|
1260
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (resource files only)');
|
|
1261
|
+
if (totalSize > 0) {
|
|
1262
|
+
this.postProgressMessage(100, 'transferData');
|
|
1263
|
+
}
|
|
1264
|
+
return;
|
|
1857
1265
|
}
|
|
1266
|
+
let transferredSize = processedSize;
|
|
1267
|
+
const transferStartTime = Date.now();
|
|
1268
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1269
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize(
|
|
1270
|
+
this.getProtocolV2InstallItemStagingPath(orderedInstallItems[0])
|
|
1271
|
+
);
|
|
1272
|
+
const onTransferredBytes = (bytes: number) => {
|
|
1273
|
+
transferredSize = bytes;
|
|
1274
|
+
};
|
|
1275
|
+
Log.log(
|
|
1276
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1277
|
+
);
|
|
1858
1278
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1279
|
+
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
for (const item of orderedInstallItems) {
|
|
1283
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1284
|
+
Log.log(
|
|
1285
|
+
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1286
|
+
);
|
|
1287
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1288
|
+
payload: item.binary,
|
|
1289
|
+
filePath,
|
|
1290
|
+
processedSize,
|
|
1291
|
+
totalSize,
|
|
1292
|
+
onTransferredBytes,
|
|
1293
|
+
});
|
|
1294
|
+
transferredSize = processedSize;
|
|
1295
|
+
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1296
|
+
|
|
1297
|
+
stagedInstallTargets.push({
|
|
1298
|
+
...item,
|
|
1299
|
+
path: filePath,
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (totalSize > 0) {
|
|
1304
|
+
this.postProgressMessage(100, 'transferData');
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1308
|
+
Log.log(
|
|
1309
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1310
|
+
elapsedMs / 1000
|
|
1311
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1312
|
+
);
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1315
|
+
Log.warn(
|
|
1316
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1317
|
+
elapsedMs / 1000
|
|
1318
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1319
|
+
);
|
|
1320
|
+
throw error;
|
|
1864
1321
|
}
|
|
1865
1322
|
|
|
1866
1323
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1867
|
-
|
|
1324
|
+
|
|
1325
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1868
1326
|
target_id: item.targetId,
|
|
1869
1327
|
path: item.path,
|
|
1870
1328
|
}));
|
|
1871
|
-
|
|
1872
|
-
await this.
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
private async protocolV2SourceUpdateProcess({
|
|
1876
|
-
source,
|
|
1877
|
-
filePath,
|
|
1878
|
-
processedSize,
|
|
1879
|
-
totalSize,
|
|
1880
|
-
}: {
|
|
1881
|
-
source: FirmwareByteSource;
|
|
1882
|
-
filePath: string;
|
|
1883
|
-
processedSize: number;
|
|
1884
|
-
totalSize: number;
|
|
1885
|
-
}) {
|
|
1886
|
-
let lastError: unknown;
|
|
1887
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1888
|
-
try {
|
|
1889
|
-
const transferStartedAt = Date.now();
|
|
1890
|
-
await writeFirmwareByteSource({
|
|
1891
|
-
source,
|
|
1892
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
1893
|
-
write: async ({ data, sourceOffset, length, first }) => {
|
|
1894
|
-
const chunkEnd = sourceOffset + length;
|
|
1895
|
-
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
1896
|
-
processedSize + sourceOffset,
|
|
1897
|
-
processedSize + chunkEnd,
|
|
1898
|
-
totalSize
|
|
1899
|
-
);
|
|
1900
|
-
const response = await this.fileWriteChunk(
|
|
1901
|
-
filePath,
|
|
1902
|
-
source.size,
|
|
1903
|
-
sourceOffset,
|
|
1904
|
-
data,
|
|
1905
|
-
first,
|
|
1906
|
-
deviceProgress
|
|
1907
|
-
);
|
|
1908
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
1909
|
-
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1910
|
-
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1911
|
-
throw ERRORS.TypedError(
|
|
1912
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1913
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1916
|
-
const transferredBytes = processedSize + chunkEnd;
|
|
1917
|
-
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
1918
|
-
this.postProgressMessage(
|
|
1919
|
-
Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
|
|
1920
|
-
'transferData',
|
|
1921
|
-
{
|
|
1922
|
-
transferredBytes,
|
|
1923
|
-
totalBytes: totalSize,
|
|
1924
|
-
rateBytesPerSecond:
|
|
1925
|
-
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
1926
|
-
elapsedMs,
|
|
1927
|
-
}
|
|
1928
|
-
);
|
|
1929
|
-
return length;
|
|
1930
|
-
},
|
|
1931
|
-
});
|
|
1932
|
-
return processedSize + source.size;
|
|
1933
|
-
} catch (error) {
|
|
1934
|
-
lastError = error;
|
|
1935
|
-
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1936
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
throw ERRORS.TypedError(
|
|
1941
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1942
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1943
|
-
);
|
|
1329
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1330
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1331
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1944
1332
|
}
|
|
1945
1333
|
|
|
1946
|
-
private getProtocolV2InstallItemStagingPath(item:
|
|
1334
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1947
1335
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1948
1336
|
}
|
|
1949
1337
|
|
|
@@ -1962,8 +1350,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1962
1350
|
|
|
1963
1351
|
private assertProtocolV2TargetStatus(
|
|
1964
1352
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1965
|
-
expectedTargetIds: Set<number
|
|
1966
|
-
expectedPaths = new Map<number, string>()
|
|
1353
|
+
expectedTargetIds: Set<number>
|
|
1967
1354
|
) {
|
|
1968
1355
|
Log.log(
|
|
1969
1356
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1994,57 +1381,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1994
1381
|
const matchingTargets = statusTargets.filter(target =>
|
|
1995
1382
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1996
1383
|
);
|
|
1997
|
-
const seenTargetIds = new Set<number>();
|
|
1998
|
-
for (const target of matchingTargets) {
|
|
1999
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2000
|
-
if (
|
|
2001
|
-
targetId === undefined ||
|
|
2002
|
-
seenTargetIds.has(targetId) ||
|
|
2003
|
-
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
2004
|
-
) {
|
|
2005
|
-
throw ERRORS.TypedError(
|
|
2006
|
-
HardwareErrorCode.RuntimeError,
|
|
2007
|
-
`Protocol V2 install status conflicts with target ${target.target_id}`,
|
|
2008
|
-
{
|
|
2009
|
-
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2010
|
-
}
|
|
2011
|
-
);
|
|
2012
|
-
}
|
|
2013
|
-
seenTargetIds.add(targetId);
|
|
2014
|
-
}
|
|
2015
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2016
|
-
isProtocolV2TargetStatusFinished(target.status)
|
|
2017
|
-
);
|
|
2018
1384
|
const completedTargetIds = new Set<number>();
|
|
2019
|
-
|
|
1385
|
+
matchingTargets.forEach(target => {
|
|
2020
1386
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2021
|
-
if (targetId !== undefined) {
|
|
1387
|
+
if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
|
|
2022
1388
|
completedTargetIds.add(targetId);
|
|
2023
1389
|
}
|
|
2024
1390
|
});
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
if (targetId === undefined) {
|
|
2030
|
-
throw ERRORS.TypedError(
|
|
2031
|
-
HardwareErrorCode.RuntimeError,
|
|
2032
|
-
'Protocol V2 completed target identity is invalid'
|
|
2033
|
-
);
|
|
2034
|
-
}
|
|
2035
|
-
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2036
|
-
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
2037
|
-
if (payloadVersion !== expectedVersion) {
|
|
2038
|
-
throw ERRORS.TypedError(
|
|
2039
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
2040
|
-
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
2041
|
-
);
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
if (payloadVersion !== undefined) {
|
|
2045
|
-
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
1391
|
+
const allExpectedTargetsCompleted =
|
|
1392
|
+
expectedTargetIds.size > 0 &&
|
|
1393
|
+
Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
|
|
1394
|
+
if (allExpectedTargetsCompleted) {
|
|
2048
1395
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2049
1396
|
return true;
|
|
2050
1397
|
}
|
|
@@ -2082,9 +1429,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2082
1429
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2083
1430
|
targets: Array<{ target_id: number; path: string }>
|
|
2084
1431
|
) {
|
|
2085
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2086
1432
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2087
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2088
1433
|
const startTime = Date.now();
|
|
2089
1434
|
let lastError: unknown;
|
|
2090
1435
|
let shouldReconnect = true;
|
|
@@ -2119,8 +1464,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2119
1464
|
);
|
|
2120
1465
|
const statusTargets = (statusResponse.message.records ??
|
|
2121
1466
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2122
|
-
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds
|
|
2123
|
-
this.protocolV2FinalStatusVerified = true;
|
|
1467
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
2124
1468
|
return;
|
|
2125
1469
|
}
|
|
2126
1470
|
|
|
@@ -2168,7 +1512,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2168
1512
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2169
1513
|
}
|
|
2170
1514
|
} catch (error) {
|
|
2171
|
-
if (
|
|
1515
|
+
if (
|
|
1516
|
+
error instanceof HardwareError &&
|
|
1517
|
+
error.errorCode === HardwareErrorCode.FirmwareError
|
|
1518
|
+
) {
|
|
2172
1519
|
throw error;
|
|
2173
1520
|
}
|
|
2174
1521
|
// Only consecutive full status dumps can prove that a record is absent.
|
|
@@ -2208,13 +1555,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2208
1555
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2209
1556
|
throw error;
|
|
2210
1557
|
}
|
|
2211
|
-
if (
|
|
2212
|
-
throw error;
|
|
2213
|
-
}
|
|
2214
|
-
if (
|
|
2215
|
-
error instanceof HardwareError &&
|
|
2216
|
-
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
2217
|
-
) {
|
|
1558
|
+
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
2218
1559
|
throw error;
|
|
2219
1560
|
}
|
|
2220
1561
|
shouldReconnect = true;
|
|
@@ -2245,7 +1586,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2245
1586
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2246
1587
|
}
|
|
2247
1588
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
2248
|
-
this.protocolV2ExecutionInLoader = false;
|
|
2249
1589
|
}
|
|
2250
1590
|
|
|
2251
1591
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -2268,7 +1608,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2268
1608
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
2269
1609
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
2270
1610
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2271
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
2272
1611
|
if (firmwareVersion === '0.0.0') {
|
|
2273
1612
|
Log.warn(
|
|
2274
1613
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -2282,17 +1621,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2282
1621
|
};
|
|
2283
1622
|
}
|
|
2284
1623
|
|
|
2285
|
-
private async completeProtocolV2FinalVerification() {
|
|
2286
|
-
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
2287
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2288
|
-
}
|
|
2289
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
2290
|
-
this.assertExpectedProtocolV2Versions();
|
|
2291
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
2292
|
-
DevicePool.resetState();
|
|
2293
|
-
return versions;
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
1624
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
2297
1625
|
const startTime = Date.now();
|
|
2298
1626
|
let lastError: unknown;
|
|
@@ -2414,50 +1742,194 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2414
1742
|
}
|
|
2415
1743
|
|
|
2416
1744
|
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
2417
|
-
//
|
|
2418
|
-
//
|
|
1745
|
+
// 序列号在 Loader 刚启动时可能暂时不可用,BLE descriptor 也没有稳定的
|
|
1746
|
+
// path。此时应继续轮询,只有明确读到不同序列号时才立即终止更新。
|
|
2419
1747
|
return this.normalizeErrorMessage(error).includes(
|
|
2420
1748
|
'Protocol V2 reconnect physical identity mismatch'
|
|
2421
1749
|
);
|
|
2422
1750
|
}
|
|
2423
1751
|
|
|
2424
|
-
private async
|
|
1752
|
+
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1753
|
+
let lastError: unknown;
|
|
1754
|
+
let confirmedFileOffset = 0;
|
|
1755
|
+
const progressReportState: ProtocolV2ProgressReportState = {};
|
|
1756
|
+
const updateConfirmedFileOffset = (offset: number) => {
|
|
1757
|
+
confirmedFileOffset = offset;
|
|
1758
|
+
};
|
|
1759
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1760
|
+
try {
|
|
1761
|
+
return await this.protocolV2WriteWholeFile(
|
|
1762
|
+
params,
|
|
1763
|
+
confirmedFileOffset,
|
|
1764
|
+
updateConfirmedFileOffset,
|
|
1765
|
+
progressReportState
|
|
1766
|
+
);
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
lastError = error;
|
|
1769
|
+
Log.error(
|
|
1770
|
+
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT} confirmedOffset=${confirmedFileOffset}`,
|
|
1771
|
+
error
|
|
1772
|
+
);
|
|
1773
|
+
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1774
|
+
break;
|
|
1775
|
+
}
|
|
1776
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1777
|
+
confirmedFileOffset = await this.getProtocolV2ResumeOffset(
|
|
1778
|
+
params.filePath,
|
|
1779
|
+
confirmedFileOffset,
|
|
1780
|
+
params.payload.byteLength
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
throw ERRORS.TypedError(
|
|
1786
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1787
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
private async getProtocolV2ResumeOffset(
|
|
2425
1792
|
filePath: string,
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
1793
|
+
confirmedFileOffset: number,
|
|
1794
|
+
totalSize: number
|
|
1795
|
+
) {
|
|
1796
|
+
if (confirmedFileOffset <= 0) {
|
|
1797
|
+
return 0;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
try {
|
|
1801
|
+
const response = await this.device
|
|
1802
|
+
.getCommands()
|
|
1803
|
+
.typedCall(
|
|
1804
|
+
'FilesystemPathInfoQuery',
|
|
1805
|
+
'FilesystemPathInfo',
|
|
1806
|
+
{ path: filePath },
|
|
1807
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
1808
|
+
);
|
|
1809
|
+
const remoteSize = toProtocolV2FiniteNumber(response.message?.size);
|
|
1810
|
+
const canResume =
|
|
1811
|
+
response.message?.exist &&
|
|
1812
|
+
!response.message?.directory &&
|
|
1813
|
+
remoteSize !== undefined &&
|
|
1814
|
+
Number.isSafeInteger(remoteSize) &&
|
|
1815
|
+
remoteSize >= confirmedFileOffset &&
|
|
1816
|
+
remoteSize <= totalSize;
|
|
1817
|
+
if (canResume) {
|
|
1818
|
+
Log.log(
|
|
1819
|
+
`Protocol V2 file transfer resuming path=${filePath} confirmedOffset=${confirmedFileOffset} remoteSize=${remoteSize}`
|
|
1820
|
+
);
|
|
1821
|
+
return confirmedFileOffset;
|
|
1822
|
+
}
|
|
1823
|
+
Log.warn(
|
|
1824
|
+
`Protocol V2 file transfer cannot resume path=${filePath} confirmedOffset=${confirmedFileOffset} remoteSize=${
|
|
1825
|
+
remoteSize ?? 'unknown'
|
|
1826
|
+
} exist=${!!response.message?.exist} directory=${!!response.message
|
|
1827
|
+
?.directory}; restarting from offset 0`
|
|
1828
|
+
);
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
Log.warn(
|
|
1831
|
+
`Protocol V2 file transfer staging state query failed path=${filePath}; restarting from offset 0`,
|
|
1832
|
+
error
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
return 0;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
private async protocolV2WriteWholeFile(
|
|
1839
|
+
{
|
|
1840
|
+
payload,
|
|
1841
|
+
filePath,
|
|
1842
|
+
processedSize,
|
|
1843
|
+
totalSize,
|
|
1844
|
+
onTransferredBytes,
|
|
1845
|
+
}: ProtocolV2FileTransferParams,
|
|
1846
|
+
resumeOffset?: number,
|
|
1847
|
+
onConfirmedFileOffset?: (confirmedFileOffset: number) => void,
|
|
1848
|
+
progressReportState?: ProtocolV2ProgressReportState
|
|
1849
|
+
) {
|
|
1850
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize(filePath);
|
|
1851
|
+
const reportState = progressReportState ?? {};
|
|
1852
|
+
const normalizedResumeOffset =
|
|
1853
|
+
resumeOffset !== undefined && Number.isSafeInteger(resumeOffset) && resumeOffset > 0
|
|
1854
|
+
? Math.min(resumeOffset, payload.byteLength)
|
|
1855
|
+
: 0;
|
|
1856
|
+
const remainingPayload = payload.slice(normalizedResumeOffset);
|
|
1857
|
+
const getUploadProgress = (fileOffset: number) => {
|
|
1858
|
+
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1859
|
+
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1860
|
+
}
|
|
1861
|
+
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
try {
|
|
1865
|
+
await writeProtocolV2File({
|
|
1866
|
+
commands: this.device.getCommands(),
|
|
1867
|
+
path: filePath,
|
|
1868
|
+
data: remainingPayload,
|
|
1869
|
+
offset: normalizedResumeOffset,
|
|
1870
|
+
totalSize: payload.byteLength,
|
|
1871
|
+
chunkSize,
|
|
1872
|
+
chunkSizeLimit: chunkSize,
|
|
1873
|
+
overwrite: normalizedResumeOffset === 0,
|
|
2444
1874
|
append: false,
|
|
2445
|
-
ui_percentage: progress ?? undefined,
|
|
2446
|
-
},
|
|
2447
|
-
{
|
|
2448
1875
|
writeWithResponse: false,
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
1876
|
+
maxChunkRetries: 3,
|
|
1877
|
+
getUiPercentage: ({ offset, chunkLength }) =>
|
|
1878
|
+
getProtocolV2DeviceTransferProgress(
|
|
1879
|
+
(processedSize ?? 0) + offset,
|
|
1880
|
+
(processedSize ?? 0) + offset + chunkLength,
|
|
1881
|
+
totalSize ?? payload.byteLength
|
|
1882
|
+
),
|
|
1883
|
+
onProgress: progress => {
|
|
1884
|
+
const confirmedFileOffset = normalizedResumeOffset + progress.transferredBytes;
|
|
1885
|
+
const transferredBytes = (processedSize ?? 0) + confirmedFileOffset;
|
|
1886
|
+
onConfirmedFileOffset?.(confirmedFileOffset);
|
|
1887
|
+
onTransferredBytes?.(transferredBytes);
|
|
1888
|
+
const uploadProgress = getUploadProgress(confirmedFileOffset);
|
|
1889
|
+
const now = Date.now();
|
|
1890
|
+
const shouldReportProgress =
|
|
1891
|
+
uploadProgress !== reportState.lastProgress ||
|
|
1892
|
+
reportState.lastReportedAt === undefined ||
|
|
1893
|
+
now - reportState.lastReportedAt >= PROTOCOL_V2_PROGRESS_HEARTBEAT_INTERVAL ||
|
|
1894
|
+
confirmedFileOffset >= payload.byteLength;
|
|
1895
|
+
if (shouldReportProgress) {
|
|
1896
|
+
this.postProgressMessage(uploadProgress, 'transferData', {
|
|
1897
|
+
transferredBytes,
|
|
1898
|
+
totalBytes: totalSize ?? payload.byteLength,
|
|
1899
|
+
rateBytesPerSecond: progress.rateBytesPerSecond,
|
|
1900
|
+
elapsedMs: progress.elapsedMs,
|
|
1901
|
+
});
|
|
1902
|
+
reportState.lastProgress = uploadProgress;
|
|
1903
|
+
reportState.lastReportedAt = now;
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
});
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
if (
|
|
1909
|
+
error instanceof HardwareError &&
|
|
1910
|
+
error.errorCode === HardwareErrorCode.RuntimeError &&
|
|
1911
|
+
error.message.includes('FilesystemFileWrite')
|
|
1912
|
+
) {
|
|
1913
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, error.message);
|
|
2457
1914
|
}
|
|
2458
|
-
throw
|
|
1915
|
+
throw error;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1922
|
+
const env = DataManager.getSettings('env');
|
|
1923
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1924
|
+
return 'BLE';
|
|
1925
|
+
}
|
|
1926
|
+
if (
|
|
1927
|
+
env &&
|
|
1928
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1929
|
+
) {
|
|
1930
|
+
return 'WebUSB';
|
|
2459
1931
|
}
|
|
2460
|
-
return
|
|
1932
|
+
return env ?? 'unknown';
|
|
2461
1933
|
}
|
|
2462
1934
|
|
|
2463
1935
|
private async recoverProtocolV2FileTransfer() {
|