@onekeyfe/hd-core 1.2.0-alpha.74 → 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 +1078 -3083
- 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 +349 -881
- 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 -64
- 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
|
@@ -2,7 +2,6 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
|
-
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
6
5
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
7
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
8
7
|
} from '@onekeyfe/hd-transport';
|
|
@@ -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,14 +53,9 @@ 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
|
|
|
70
|
-
// Restored after a rebase dropped the declaration while keeping its use in
|
|
71
|
-
// fileWriteChunk; without it that error branch throws ReferenceError instead of
|
|
72
|
-
// the intended typed session error.
|
|
73
|
-
const SESSION_ERROR = 'session not found';
|
|
74
59
|
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
75
60
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
76
61
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
@@ -90,7 +75,7 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
90
75
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
91
76
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
92
77
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
93
|
-
const
|
|
78
|
+
const PROTOCOL_V2_PROGRESS_HEARTBEAT_INTERVAL = 1000;
|
|
94
79
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
95
80
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
96
81
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -135,6 +120,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
135
120
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
136
121
|
};
|
|
137
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
|
+
|
|
138
128
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
139
129
|
target_id: number | string;
|
|
140
130
|
status?: number | string;
|
|
@@ -148,6 +138,22 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
148
138
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
149
139
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
150
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
|
+
|
|
151
157
|
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
152
158
|
type ProtocolV2ResourceBundleBinary = {
|
|
153
159
|
name: string;
|
|
@@ -165,35 +171,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
165
171
|
kind: 'bootloader' | 'firmware';
|
|
166
172
|
};
|
|
167
173
|
|
|
168
|
-
type ProtocolV2InstallSource = {
|
|
169
|
-
fileName: string;
|
|
170
|
-
source: FirmwareByteSource;
|
|
171
|
-
targetId: number;
|
|
172
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
type ProtocolV2ResourceBundleSource = {
|
|
176
|
-
name: string;
|
|
177
|
-
source: FirmwareByteSource;
|
|
178
|
-
devicePath: string;
|
|
179
|
-
version?: IVersionArray;
|
|
180
|
-
payloadHash?: string;
|
|
181
|
-
headerHash?: string;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
type ProtocolV2ExecutionPhaseKind =
|
|
185
|
-
| 'resource-sync'
|
|
186
|
-
| 'bootloader-install'
|
|
187
|
-
| 'bootloader-verify'
|
|
188
|
-
| 'component-install'
|
|
189
|
-
| 'final-verify';
|
|
190
|
-
|
|
191
|
-
type ProtocolV2ExecutionPhase = {
|
|
192
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
193
|
-
installSources: ProtocolV2InstallSource[];
|
|
194
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
195
|
-
};
|
|
196
|
-
|
|
197
174
|
type ProtocolV2OkppHeader = {
|
|
198
175
|
type: string;
|
|
199
176
|
version: IVersionArray;
|
|
@@ -302,12 +279,6 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
302
279
|
);
|
|
303
280
|
};
|
|
304
281
|
|
|
305
|
-
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
306
|
-
error instanceof HardwareError &&
|
|
307
|
-
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
308
|
-
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
309
|
-
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
310
|
-
|
|
311
282
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
312
283
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
313
284
|
|
|
@@ -345,18 +316,6 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
345
316
|
|
|
346
317
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
347
318
|
|
|
348
|
-
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
349
|
-
if (!version) return undefined;
|
|
350
|
-
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
354
|
-
const currentNumber = versionArrayToNumber(current);
|
|
355
|
-
const targetNumber = versionArrayToNumber(target);
|
|
356
|
-
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
357
|
-
return currentNumber - targetNumber;
|
|
358
|
-
};
|
|
359
|
-
|
|
360
319
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
361
320
|
Array.from(bytes)
|
|
362
321
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -490,16 +449,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
490
449
|
return ['V2'] as const;
|
|
491
450
|
}
|
|
492
451
|
|
|
493
|
-
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
494
|
-
|
|
495
|
-
private protocolV2ExecutionInLoader = false;
|
|
496
|
-
|
|
497
|
-
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
498
|
-
|
|
499
|
-
private protocolV2LatestFinalFeatures?: Features;
|
|
500
|
-
|
|
501
|
-
private protocolV2FinalStatusVerified = false;
|
|
502
|
-
|
|
503
452
|
init() {
|
|
504
453
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
505
454
|
this.requireDeviceMode = [];
|
|
@@ -537,62 +486,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
537
486
|
{ name: 'firmwareType', type: 'string' },
|
|
538
487
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
539
488
|
{ name: 'platform', type: 'string' },
|
|
540
|
-
{ name: 'expectedDeviceId', type: 'string' },
|
|
541
489
|
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
542
490
|
]);
|
|
543
|
-
if (
|
|
544
|
-
payload.expectedDeviceId !== undefined &&
|
|
545
|
-
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
546
|
-
) {
|
|
547
|
-
throw ERRORS.TypedError(
|
|
548
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
549
|
-
'Protocol V2 expected device identity is invalid'
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
const hostBinding =
|
|
553
|
-
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
554
|
-
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
555
|
-
: undefined;
|
|
556
|
-
if (hostBinding) {
|
|
557
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
558
|
-
preparedPlan: payload.preparedPlan,
|
|
559
|
-
executor: 'v4',
|
|
560
|
-
platform: payload.platform,
|
|
561
|
-
scopeTargets: [
|
|
562
|
-
'boot',
|
|
563
|
-
'app_v1',
|
|
564
|
-
'app_v2',
|
|
565
|
-
'coprocessor',
|
|
566
|
-
'resource',
|
|
567
|
-
'se01',
|
|
568
|
-
'se02',
|
|
569
|
-
'se03',
|
|
570
|
-
'se04',
|
|
571
|
-
],
|
|
572
|
-
bindings: [
|
|
573
|
-
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
574
|
-
artifact
|
|
575
|
-
? [
|
|
576
|
-
{
|
|
577
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
578
|
-
artifact,
|
|
579
|
-
},
|
|
580
|
-
]
|
|
581
|
-
: []
|
|
582
|
-
),
|
|
583
|
-
...(payload.resourceBundleArtifacts ?? []).map(
|
|
584
|
-
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
585
|
-
target: 'resource' as const,
|
|
586
|
-
logicalName: entry.name,
|
|
587
|
-
artifact: entry.artifact,
|
|
588
|
-
})
|
|
589
|
-
),
|
|
590
|
-
],
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
491
|
|
|
594
492
|
this.params = {
|
|
595
|
-
preparedPlan: payload.preparedPlan,
|
|
596
493
|
chunkSize: payload.chunkSize,
|
|
597
494
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
598
495
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -607,29 +504,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
607
504
|
resourceFiles: payload.resourceFiles,
|
|
608
505
|
firmwareType: payload.firmwareType,
|
|
609
506
|
targetsToUpdate: payload.targetsToUpdate,
|
|
610
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
611
507
|
platform: payload.platform,
|
|
612
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
613
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
614
|
-
componentArtifacts: payload.componentArtifacts,
|
|
615
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
616
508
|
};
|
|
617
509
|
}
|
|
618
510
|
|
|
619
|
-
private getProtocolV2FirmwareChunkSize(
|
|
511
|
+
private getProtocolV2FirmwareChunkSize(filePath?: string) {
|
|
620
512
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
621
513
|
const env = DataManager.getSettings('env');
|
|
622
514
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
623
515
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
624
516
|
if (isBle) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
// Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
|
|
629
|
-
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
630
|
-
? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
|
|
631
|
-
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
632
|
-
}
|
|
517
|
+
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
518
|
+
? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
|
|
519
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
633
520
|
}
|
|
634
521
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
635
522
|
return maxChunkSize;
|
|
@@ -656,20 +543,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
656
543
|
assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
|
|
657
544
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
658
545
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (
|
|
662
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
663
|
-
this.params.resourceBundleArtifacts?.length
|
|
664
|
-
) {
|
|
665
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
666
|
-
}
|
|
667
|
-
const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
|
|
668
|
-
const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
669
|
-
const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
670
|
-
const needsRemoteResources = !hasExplicitResourceFiles && wantsStableResources;
|
|
671
|
-
const needsRemoteBootResources =
|
|
672
|
-
!hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
|
|
546
|
+
const needsRemoteResources =
|
|
547
|
+
!this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
|
|
673
548
|
|
|
674
549
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
675
550
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -681,19 +556,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
681
556
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
682
557
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
683
558
|
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
throw ERRORS.TypedError(
|
|
690
|
-
HardwareErrorCode.RuntimeError,
|
|
691
|
-
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
692
|
-
{
|
|
693
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
694
|
-
}
|
|
695
|
-
);
|
|
696
|
-
}
|
|
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);
|
|
697
564
|
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
698
565
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
699
566
|
await DataManager.forceReloadData({
|
|
@@ -720,7 +587,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
720
587
|
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
721
588
|
throw err;
|
|
722
589
|
}
|
|
723
|
-
throw
|
|
590
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
724
591
|
}
|
|
725
592
|
|
|
726
593
|
if (
|
|
@@ -736,8 +603,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
736
603
|
);
|
|
737
604
|
}
|
|
738
605
|
|
|
606
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
607
|
+
|
|
739
608
|
if (needsRemoteResources) {
|
|
740
|
-
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
741
609
|
try {
|
|
742
610
|
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
743
611
|
resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
|
|
@@ -753,293 +621,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
753
621
|
);
|
|
754
622
|
}
|
|
755
623
|
}
|
|
756
|
-
throw
|
|
624
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
757
625
|
}
|
|
758
626
|
}
|
|
759
627
|
|
|
760
|
-
|
|
628
|
+
await this.executeProtocolV2Update({
|
|
761
629
|
fwBinaryMap,
|
|
762
630
|
bootloaderBinary,
|
|
763
631
|
...(installItems ? { installItems } : undefined),
|
|
764
632
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
765
633
|
});
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
769
|
-
const source = await openFirmwareByteSource({
|
|
770
|
-
artifact,
|
|
771
|
-
reader: this.params.artifactReader,
|
|
772
|
-
});
|
|
773
|
-
if (!source) {
|
|
774
|
-
throw ERRORS.TypedError(
|
|
775
|
-
HardwareErrorCode.RuntimeError,
|
|
776
|
-
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
777
|
-
{
|
|
778
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
779
|
-
}
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
this.protocolV2PreparedSources.push(source);
|
|
783
|
-
return source;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
787
|
-
const source = await openFirmwareByteSource({ binary });
|
|
788
|
-
if (!source) {
|
|
789
|
-
throw ERRORS.TypedError(
|
|
790
|
-
HardwareErrorCode.RuntimeError,
|
|
791
|
-
'Protocol V2 firmware binary is empty'
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
this.protocolV2PreparedSources.push(source);
|
|
795
|
-
return source;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
private async closeProtocolV2PreparedSources() {
|
|
799
|
-
const sources = this.protocolV2PreparedSources.splice(0);
|
|
800
|
-
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
private async prepareProtocolV2InstallSources(
|
|
804
|
-
firmwareType: EFirmwareType,
|
|
805
|
-
features: Features
|
|
806
|
-
): Promise<ProtocolV2InstallSource[]> {
|
|
807
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
808
|
-
if (!release) {
|
|
809
|
-
throw ERRORS.TypedError(
|
|
810
|
-
HardwareErrorCode.RuntimeError,
|
|
811
|
-
'Protocol V2 firmware release is unavailable'
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
815
|
-
const requestedTargets = new Set(
|
|
816
|
-
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
817
|
-
);
|
|
818
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
819
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
820
|
-
|
|
821
|
-
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
822
|
-
const target = this.getRemoteComponentTarget(key, component);
|
|
823
|
-
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
824
|
-
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
825
|
-
const artifact = componentArtifacts[updateTarget];
|
|
826
|
-
if (!artifact) {
|
|
827
|
-
throw ERRORS.TypedError(
|
|
828
|
-
HardwareErrorCode.RuntimeError,
|
|
829
|
-
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
830
|
-
{
|
|
831
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
832
|
-
artifactName: updateTarget,
|
|
833
|
-
}
|
|
834
|
-
);
|
|
835
|
-
}
|
|
836
|
-
installSources.push({
|
|
837
|
-
...target,
|
|
838
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
839
|
-
});
|
|
840
|
-
preparedTargets.add(updateTarget);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
634
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
);
|
|
847
|
-
if (unknownTarget) {
|
|
848
|
-
throw ERRORS.TypedError(
|
|
849
|
-
HardwareErrorCode.RuntimeError,
|
|
850
|
-
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
|
-
return installSources;
|
|
854
|
-
}
|
|
635
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
636
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
855
637
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
860
|
-
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
861
|
-
const resourceRequested =
|
|
862
|
-
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
863
|
-
if (!resourceRequested) {
|
|
864
|
-
return [];
|
|
865
|
-
}
|
|
866
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
867
|
-
const descriptors = release?.resourceBundles ?? [];
|
|
868
|
-
const artifactByName = new Map(
|
|
869
|
-
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
870
|
-
);
|
|
871
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
872
|
-
for (const descriptor of descriptors) {
|
|
873
|
-
const artifact = artifactByName.get(descriptor.name);
|
|
874
|
-
if (!artifact) {
|
|
875
|
-
throw ERRORS.TypedError(
|
|
876
|
-
HardwareErrorCode.RuntimeError,
|
|
877
|
-
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
878
|
-
{
|
|
879
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
880
|
-
artifactName: descriptor.name,
|
|
881
|
-
}
|
|
882
|
-
);
|
|
883
|
-
}
|
|
884
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
885
|
-
descriptor.devicePath,
|
|
886
|
-
'resourceBundles[].devicePath'
|
|
887
|
-
);
|
|
888
|
-
sources.push({
|
|
889
|
-
name: descriptor.name,
|
|
890
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
891
|
-
devicePath,
|
|
892
|
-
version: descriptor.version,
|
|
893
|
-
payloadHash: descriptor.payloadHash,
|
|
894
|
-
headerHash: descriptor.headerHash,
|
|
895
|
-
});
|
|
896
|
-
artifactByName.delete(descriptor.name);
|
|
897
|
-
}
|
|
898
|
-
if (artifactByName.size > 0) {
|
|
899
|
-
throw ERRORS.TypedError(
|
|
900
|
-
HardwareErrorCode.RuntimeError,
|
|
901
|
-
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
return sources;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
908
|
-
try {
|
|
909
|
-
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
910
|
-
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
911
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
912
|
-
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
913
|
-
throw ERRORS.TypedError(
|
|
914
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
915
|
-
'No firmware to update'
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
919
|
-
|
|
920
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
921
|
-
installSources,
|
|
922
|
-
resourceSources,
|
|
923
|
-
});
|
|
924
|
-
} finally {
|
|
925
|
-
await this.closeProtocolV2PreparedSources();
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
private validateExpectedTargetVersions() {
|
|
930
|
-
const expected = this.params.expectedTargetVersions;
|
|
931
|
-
if (expected === undefined) return;
|
|
932
|
-
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
933
|
-
throw ERRORS.TypedError(
|
|
934
|
-
HardwareErrorCode.RuntimeError,
|
|
935
|
-
'Protocol V2 expected target versions are invalid'
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
|
-
for (const [target, version] of Object.entries(expected)) {
|
|
939
|
-
if (
|
|
940
|
-
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
941
|
-
target as FirmwareUpdateV4Target
|
|
942
|
-
) ||
|
|
943
|
-
typeof version !== 'string' ||
|
|
944
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
945
|
-
version.length > 64
|
|
946
|
-
) {
|
|
947
|
-
throw ERRORS.TypedError(
|
|
948
|
-
HardwareErrorCode.RuntimeError,
|
|
949
|
-
'Protocol V2 expected target version is invalid'
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
956
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
957
|
-
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
958
|
-
if (!version) return undefined;
|
|
959
|
-
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
960
|
-
if (
|
|
961
|
-
parts.length !== 3 ||
|
|
962
|
-
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
963
|
-
) {
|
|
964
|
-
return undefined;
|
|
965
|
-
}
|
|
966
|
-
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
970
|
-
const expected = this.params?.expectedTargetVersions;
|
|
971
|
-
if (!expected) return;
|
|
972
|
-
const features = this.protocolV2LatestFinalFeatures;
|
|
973
|
-
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
974
|
-
const applicationTargets = requestedTargets.filter(
|
|
975
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
976
|
-
);
|
|
977
|
-
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
978
|
-
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
979
|
-
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
980
|
-
se01: features?.se01Version ?? undefined,
|
|
981
|
-
se02: features?.se02Version ?? undefined,
|
|
982
|
-
se03: features?.se03Version ?? undefined,
|
|
983
|
-
se04: features?.se04Version ?? undefined,
|
|
984
|
-
};
|
|
985
|
-
if (features && applicationTargets.length === 1) {
|
|
986
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
987
|
-
}
|
|
988
|
-
const expectedEntries = (
|
|
989
|
-
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
990
|
-
).filter(([target]) => !targets || targets.includes(target));
|
|
991
|
-
for (const [target, expectedVersion] of expectedEntries) {
|
|
992
|
-
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
993
|
-
([, mappedTarget]) => mappedTarget === target
|
|
994
|
-
)?.[0];
|
|
995
|
-
const statusVersion =
|
|
996
|
-
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
997
|
-
const observedVersion =
|
|
998
|
-
statusVersion === undefined
|
|
999
|
-
? visibleVersions[target]
|
|
1000
|
-
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
1001
|
-
Math.floor(statusVersion / 0x100) % 0x100
|
|
1002
|
-
}.${statusVersion % 0x100}`;
|
|
1003
|
-
if (!observedVersion) {
|
|
1004
|
-
if (this.protocolV2FinalStatusVerified) {
|
|
1005
|
-
Log.warn(
|
|
1006
|
-
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
1007
|
-
);
|
|
1008
|
-
} else {
|
|
1009
|
-
throw ERRORS.TypedError(
|
|
1010
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1011
|
-
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
} else if (observedVersion !== expectedVersion) {
|
|
1015
|
-
throw ERRORS.TypedError(
|
|
1016
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1017
|
-
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
1018
|
-
);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
638
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
639
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
640
|
+
DevicePool.resetState();
|
|
1022
641
|
|
|
1023
|
-
|
|
1024
|
-
if (this.params.targetsToUpdate?.length) {
|
|
1025
|
-
return [...new Set(this.params.targetsToUpdate)];
|
|
1026
|
-
}
|
|
1027
|
-
const targets = new Set<FirmwareUpdateV4Target>();
|
|
1028
|
-
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
1029
|
-
targets.add(target as FirmwareUpdateV4Target)
|
|
1030
|
-
);
|
|
1031
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
|
|
1032
|
-
targets.add('resource');
|
|
1033
|
-
}
|
|
1034
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1035
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1036
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
1037
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
1038
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
1039
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
1040
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
1041
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
1042
|
-
return Array.from(targets);
|
|
642
|
+
return versions;
|
|
1043
643
|
}
|
|
1044
644
|
|
|
1045
645
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -1083,17 +683,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1083
683
|
private async captureProtocolV2PhysicalIdentity() {
|
|
1084
684
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
1085
685
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
1086
|
-
const path = this.device.originalDescriptor
|
|
1087
|
-
|
|
1088
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1089
|
-
preparedPlan: this.params.preparedPlan,
|
|
1090
|
-
deviceIdentity: serialNumber,
|
|
1091
|
-
});
|
|
1092
|
-
} else if (this.params?.expectedDeviceId) {
|
|
1093
|
-
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
1094
|
-
} else {
|
|
1095
|
-
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1096
|
-
}
|
|
686
|
+
const path = this.device.originalDescriptor.path?.trim() || undefined;
|
|
687
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1097
688
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
1098
689
|
this.protocolV2ExpectedPath = path;
|
|
1099
690
|
}
|
|
@@ -1329,7 +920,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1329
920
|
}
|
|
1330
921
|
|
|
1331
922
|
const digest = sha256.create();
|
|
1332
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize(
|
|
923
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1333
924
|
let offset = 0;
|
|
1334
925
|
while (offset < file.size) {
|
|
1335
926
|
const response = await commands.typedCall(
|
|
@@ -1424,7 +1015,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1424
1015
|
: await readProtocolV2ResourceInventory({
|
|
1425
1016
|
commands: this.device.getCommands(),
|
|
1426
1017
|
resources,
|
|
1427
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize(
|
|
1018
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1428
1019
|
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1429
1020
|
});
|
|
1430
1021
|
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
@@ -1459,88 +1050,38 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1459
1050
|
};
|
|
1460
1051
|
}
|
|
1461
1052
|
|
|
1462
|
-
private
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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();
|
|
1467
1059
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const
|
|
1471
|
-
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1472
|
-
path: filePath,
|
|
1473
|
-
});
|
|
1474
|
-
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1475
|
-
if (
|
|
1476
|
-
!pathInfoRes.message?.exist ||
|
|
1477
|
-
pathInfoRes.message?.directory ||
|
|
1478
|
-
fileSize === undefined ||
|
|
1479
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1480
|
-
) {
|
|
1481
|
-
return null;
|
|
1482
|
-
}
|
|
1060
|
+
// Write directly to the device through FileWrite.
|
|
1061
|
+
let totalSize = 0;
|
|
1062
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
1483
1063
|
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
chunk_len: readLen,
|
|
1496
|
-
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,
|
|
1497
1075
|
});
|
|
1498
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
1499
|
-
if (data.byteLength === 0) return null;
|
|
1500
|
-
chunks.push(data);
|
|
1501
|
-
offset += data.byteLength;
|
|
1502
1076
|
}
|
|
1503
1077
|
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
return
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
/** Compare a prepared okpkg header when its manifest supplies version or hash metadata. */
|
|
1514
|
-
private async isProtocolV2ResourceBundleUpToDate(
|
|
1515
|
-
bundle: Pick<
|
|
1516
|
-
ProtocolV2ResourceBundleSource,
|
|
1517
|
-
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1518
|
-
>
|
|
1519
|
-
): Promise<boolean> {
|
|
1520
|
-
if (this.params?.forcedUpdateRes) return false;
|
|
1521
|
-
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1522
|
-
|
|
1523
|
-
try {
|
|
1524
|
-
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1525
|
-
if (!header) return false;
|
|
1526
|
-
|
|
1527
|
-
if (bundle.version) {
|
|
1528
|
-
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
1529
|
-
if (cmp === undefined || cmp !== 0) return false;
|
|
1530
|
-
}
|
|
1531
|
-
if (bundle.payloadHash) {
|
|
1532
|
-
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1533
|
-
if (expected && header.payloadHash !== expected) return false;
|
|
1534
|
-
}
|
|
1535
|
-
if (bundle.headerHash) {
|
|
1536
|
-
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1537
|
-
if (expected && header.headerHash !== expected) return false;
|
|
1538
|
-
}
|
|
1539
|
-
return true;
|
|
1540
|
-
} catch (error) {
|
|
1541
|
-
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1542
|
-
return false;
|
|
1543
|
-
}
|
|
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 };
|
|
1544
1085
|
}
|
|
1545
1086
|
|
|
1546
1087
|
private isProtocolV2BootloaderMode() {
|
|
@@ -1572,12 +1113,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1572
1113
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1573
1114
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1574
1115
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1575
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1576
1116
|
return false;
|
|
1577
1117
|
}
|
|
1578
1118
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1579
1119
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1580
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1581
1120
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1582
1121
|
return false;
|
|
1583
1122
|
}
|
|
@@ -1587,7 +1126,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1587
1126
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1588
1127
|
await wait(1000);
|
|
1589
1128
|
await this.waitForProtocolV2BootloaderMode();
|
|
1590
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1591
1129
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1592
1130
|
return true;
|
|
1593
1131
|
} catch (error) {
|
|
@@ -1683,98 +1221,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1683
1221
|
return entries;
|
|
1684
1222
|
}
|
|
1685
1223
|
|
|
1686
|
-
private buildProtocolV2ExecutionPhases({
|
|
1687
|
-
installSources,
|
|
1688
|
-
resourceSources,
|
|
1689
|
-
}: {
|
|
1690
|
-
installSources: ProtocolV2InstallSource[];
|
|
1691
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1692
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
1693
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1694
|
-
if (resourceSources.length > 0) {
|
|
1695
|
-
phases.push({
|
|
1696
|
-
kind: 'resource-sync',
|
|
1697
|
-
installSources: [],
|
|
1698
|
-
resourceSources,
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1702
|
-
if (bootloaderSources.length > 0) {
|
|
1703
|
-
phases.push(
|
|
1704
|
-
{
|
|
1705
|
-
kind: 'bootloader-install',
|
|
1706
|
-
installSources: bootloaderSources,
|
|
1707
|
-
resourceSources: [],
|
|
1708
|
-
},
|
|
1709
|
-
{
|
|
1710
|
-
kind: 'bootloader-verify',
|
|
1711
|
-
installSources: [],
|
|
1712
|
-
resourceSources: [],
|
|
1713
|
-
}
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1717
|
-
if (componentSources.length > 0) {
|
|
1718
|
-
phases.push({
|
|
1719
|
-
kind: 'component-install',
|
|
1720
|
-
installSources: componentSources,
|
|
1721
|
-
resourceSources: [],
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
phases.push({
|
|
1725
|
-
kind: 'final-verify',
|
|
1726
|
-
installSources: [],
|
|
1727
|
-
resourceSources: [],
|
|
1728
|
-
});
|
|
1729
|
-
return phases;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
private async executeProtocolV2Phases({
|
|
1733
|
-
installSources,
|
|
1734
|
-
resourceSources,
|
|
1735
|
-
}: {
|
|
1736
|
-
installSources: ProtocolV2InstallSource[];
|
|
1737
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1738
|
-
}) {
|
|
1739
|
-
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1740
|
-
installSources,
|
|
1741
|
-
resourceSources,
|
|
1742
|
-
});
|
|
1743
|
-
for (const phase of phases) {
|
|
1744
|
-
if (phase.kind === 'final-verify') {
|
|
1745
|
-
return this.completeProtocolV2FinalVerification();
|
|
1746
|
-
}
|
|
1747
|
-
if (phase.kind === 'resource-sync') {
|
|
1748
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1749
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1750
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1751
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1752
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1753
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1754
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
1755
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
1756
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
throw ERRORS.TypedError(
|
|
1760
|
-
HardwareErrorCode.RuntimeError,
|
|
1761
|
-
'Protocol V2 execution has no final verification phase'
|
|
1762
|
-
);
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
private async executeProtocolV2SourceUpdate({
|
|
1766
|
-
installSources,
|
|
1767
|
-
resourceSources,
|
|
1768
|
-
}: {
|
|
1769
|
-
installSources: ProtocolV2InstallSource[];
|
|
1770
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1771
|
-
}) {
|
|
1772
|
-
return this.executeProtocolV2Phases({
|
|
1773
|
-
installSources,
|
|
1774
|
-
resourceSources,
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
1224
|
private async executeProtocolV2Update({
|
|
1779
1225
|
fwBinaryMap,
|
|
1780
1226
|
bootloaderBinary,
|
|
@@ -1786,168 +1232,106 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1786
1232
|
installItems?: ProtocolV2InstallItem[];
|
|
1787
1233
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1788
1234
|
}) {
|
|
1789
|
-
const
|
|
1235
|
+
const orderedInstallItems =
|
|
1790
1236
|
installItems ??
|
|
1791
1237
|
this.buildProtocolV2InstallItems({
|
|
1792
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1793
1238
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1239
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1794
1240
|
});
|
|
1795
|
-
try {
|
|
1796
|
-
const installSources = await Promise.all(
|
|
1797
|
-
memoryInstallItems.map(async item => ({
|
|
1798
|
-
fileName: item.fileName,
|
|
1799
|
-
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1800
|
-
targetId: item.targetId,
|
|
1801
|
-
kind: item.kind,
|
|
1802
|
-
}))
|
|
1803
|
-
);
|
|
1804
|
-
const resourceSources = await Promise.all(
|
|
1805
|
-
(resourceBundles ?? []).map(async bundle => ({
|
|
1806
|
-
name: bundle.name,
|
|
1807
|
-
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1808
|
-
devicePath: bundle.devicePath,
|
|
1809
|
-
}))
|
|
1810
|
-
);
|
|
1811
|
-
return await this.executeProtocolV2Phases({
|
|
1812
|
-
installSources,
|
|
1813
|
-
resourceSources,
|
|
1814
|
-
});
|
|
1815
|
-
} finally {
|
|
1816
|
-
await this.closeProtocolV2PreparedSources();
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
1241
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
resourceSources,
|
|
1823
|
-
}: ProtocolV2ExecutionPhase) {
|
|
1824
|
-
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1825
|
-
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1826
|
-
for (const resource of resourceSources) {
|
|
1827
|
-
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1828
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1829
|
-
} else {
|
|
1830
|
-
resourcesToSync.push(resource);
|
|
1831
|
-
totalSize += resource.source.size;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1242
|
+
let firmwareSize = 0;
|
|
1243
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1834
1244
|
|
|
1835
1245
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1246
|
+
|
|
1836
1247
|
let processedSize = 0;
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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;
|
|
1845
1256
|
}
|
|
1846
1257
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
totalSize,
|
|
1855
|
-
});
|
|
1856
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1857
|
-
stagedInstallTargets.push({
|
|
1858
|
-
targetId: item.targetId,
|
|
1859
|
-
path: filePath,
|
|
1860
|
-
});
|
|
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;
|
|
1861
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
|
+
);
|
|
1862
1278
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
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;
|
|
1868
1321
|
}
|
|
1869
1322
|
|
|
1870
1323
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1871
|
-
|
|
1324
|
+
|
|
1325
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1872
1326
|
target_id: item.targetId,
|
|
1873
1327
|
path: item.path,
|
|
1874
1328
|
}));
|
|
1875
|
-
|
|
1876
|
-
await this.
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
private async protocolV2SourceUpdateProcess({
|
|
1880
|
-
source,
|
|
1881
|
-
filePath,
|
|
1882
|
-
processedSize,
|
|
1883
|
-
totalSize,
|
|
1884
|
-
}: {
|
|
1885
|
-
source: FirmwareByteSource;
|
|
1886
|
-
filePath: string;
|
|
1887
|
-
processedSize: number;
|
|
1888
|
-
totalSize: number;
|
|
1889
|
-
}) {
|
|
1890
|
-
let lastError: unknown;
|
|
1891
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1892
|
-
try {
|
|
1893
|
-
const transferStartedAt = Date.now();
|
|
1894
|
-
await writeFirmwareByteSource({
|
|
1895
|
-
source,
|
|
1896
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
1897
|
-
write: async ({ data, sourceOffset, length, first }) => {
|
|
1898
|
-
const chunkEnd = sourceOffset + length;
|
|
1899
|
-
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
1900
|
-
processedSize + sourceOffset,
|
|
1901
|
-
processedSize + chunkEnd,
|
|
1902
|
-
totalSize
|
|
1903
|
-
);
|
|
1904
|
-
const response = await this.fileWriteChunk(
|
|
1905
|
-
filePath,
|
|
1906
|
-
source.size,
|
|
1907
|
-
sourceOffset,
|
|
1908
|
-
data,
|
|
1909
|
-
first,
|
|
1910
|
-
deviceProgress
|
|
1911
|
-
);
|
|
1912
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
1913
|
-
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1914
|
-
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1915
|
-
throw ERRORS.TypedError(
|
|
1916
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1917
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1918
|
-
);
|
|
1919
|
-
}
|
|
1920
|
-
const transferredBytes = processedSize + chunkEnd;
|
|
1921
|
-
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
1922
|
-
this.postProgressMessage(
|
|
1923
|
-
Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
|
|
1924
|
-
'transferData',
|
|
1925
|
-
{
|
|
1926
|
-
transferredBytes,
|
|
1927
|
-
totalBytes: totalSize,
|
|
1928
|
-
rateBytesPerSecond:
|
|
1929
|
-
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
1930
|
-
elapsedMs,
|
|
1931
|
-
}
|
|
1932
|
-
);
|
|
1933
|
-
return length;
|
|
1934
|
-
},
|
|
1935
|
-
});
|
|
1936
|
-
return processedSize + source.size;
|
|
1937
|
-
} catch (error) {
|
|
1938
|
-
lastError = error;
|
|
1939
|
-
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1940
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
throw ERRORS.TypedError(
|
|
1945
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1946
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1947
|
-
);
|
|
1329
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1330
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1331
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1948
1332
|
}
|
|
1949
1333
|
|
|
1950
|
-
private getProtocolV2InstallItemStagingPath(item:
|
|
1334
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1951
1335
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1952
1336
|
}
|
|
1953
1337
|
|
|
@@ -1966,8 +1350,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1966
1350
|
|
|
1967
1351
|
private assertProtocolV2TargetStatus(
|
|
1968
1352
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1969
|
-
expectedTargetIds: Set<number
|
|
1970
|
-
expectedPaths = new Map<number, string>()
|
|
1353
|
+
expectedTargetIds: Set<number>
|
|
1971
1354
|
) {
|
|
1972
1355
|
Log.log(
|
|
1973
1356
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1998,57 +1381,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1998
1381
|
const matchingTargets = statusTargets.filter(target =>
|
|
1999
1382
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
2000
1383
|
);
|
|
2001
|
-
const seenTargetIds = new Set<number>();
|
|
2002
|
-
for (const target of matchingTargets) {
|
|
2003
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2004
|
-
if (
|
|
2005
|
-
targetId === undefined ||
|
|
2006
|
-
seenTargetIds.has(targetId) ||
|
|
2007
|
-
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
2008
|
-
) {
|
|
2009
|
-
throw ERRORS.TypedError(
|
|
2010
|
-
HardwareErrorCode.RuntimeError,
|
|
2011
|
-
`Protocol V2 install status conflicts with target ${target.target_id}`,
|
|
2012
|
-
{
|
|
2013
|
-
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2014
|
-
}
|
|
2015
|
-
);
|
|
2016
|
-
}
|
|
2017
|
-
seenTargetIds.add(targetId);
|
|
2018
|
-
}
|
|
2019
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2020
|
-
isProtocolV2TargetStatusFinished(target.status)
|
|
2021
|
-
);
|
|
2022
1384
|
const completedTargetIds = new Set<number>();
|
|
2023
|
-
|
|
1385
|
+
matchingTargets.forEach(target => {
|
|
2024
1386
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2025
|
-
if (targetId !== undefined) {
|
|
1387
|
+
if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
|
|
2026
1388
|
completedTargetIds.add(targetId);
|
|
2027
1389
|
}
|
|
2028
1390
|
});
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
if (targetId === undefined) {
|
|
2034
|
-
throw ERRORS.TypedError(
|
|
2035
|
-
HardwareErrorCode.RuntimeError,
|
|
2036
|
-
'Protocol V2 completed target identity is invalid'
|
|
2037
|
-
);
|
|
2038
|
-
}
|
|
2039
|
-
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2040
|
-
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
2041
|
-
if (payloadVersion !== expectedVersion) {
|
|
2042
|
-
throw ERRORS.TypedError(
|
|
2043
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
2044
|
-
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
2045
|
-
);
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
if (payloadVersion !== undefined) {
|
|
2049
|
-
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
1391
|
+
const allExpectedTargetsCompleted =
|
|
1392
|
+
expectedTargetIds.size > 0 &&
|
|
1393
|
+
Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
|
|
1394
|
+
if (allExpectedTargetsCompleted) {
|
|
2052
1395
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2053
1396
|
return true;
|
|
2054
1397
|
}
|
|
@@ -2086,9 +1429,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2086
1429
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2087
1430
|
targets: Array<{ target_id: number; path: string }>
|
|
2088
1431
|
) {
|
|
2089
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2090
1432
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2091
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2092
1433
|
const startTime = Date.now();
|
|
2093
1434
|
let lastError: unknown;
|
|
2094
1435
|
let shouldReconnect = true;
|
|
@@ -2123,8 +1464,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2123
1464
|
);
|
|
2124
1465
|
const statusTargets = (statusResponse.message.records ??
|
|
2125
1466
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2126
|
-
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds
|
|
2127
|
-
this.protocolV2FinalStatusVerified = true;
|
|
1467
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
2128
1468
|
return;
|
|
2129
1469
|
}
|
|
2130
1470
|
|
|
@@ -2172,7 +1512,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2172
1512
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2173
1513
|
}
|
|
2174
1514
|
} catch (error) {
|
|
2175
|
-
if (
|
|
1515
|
+
if (
|
|
1516
|
+
error instanceof HardwareError &&
|
|
1517
|
+
error.errorCode === HardwareErrorCode.FirmwareError
|
|
1518
|
+
) {
|
|
2176
1519
|
throw error;
|
|
2177
1520
|
}
|
|
2178
1521
|
// Only consecutive full status dumps can prove that a record is absent.
|
|
@@ -2212,13 +1555,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2212
1555
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2213
1556
|
throw error;
|
|
2214
1557
|
}
|
|
2215
|
-
if (
|
|
2216
|
-
throw error;
|
|
2217
|
-
}
|
|
2218
|
-
if (
|
|
2219
|
-
error instanceof HardwareError &&
|
|
2220
|
-
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
2221
|
-
) {
|
|
1558
|
+
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
2222
1559
|
throw error;
|
|
2223
1560
|
}
|
|
2224
1561
|
shouldReconnect = true;
|
|
@@ -2249,7 +1586,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2249
1586
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2250
1587
|
}
|
|
2251
1588
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
2252
|
-
this.protocolV2ExecutionInLoader = false;
|
|
2253
1589
|
}
|
|
2254
1590
|
|
|
2255
1591
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -2272,7 +1608,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2272
1608
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
2273
1609
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
2274
1610
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2275
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
2276
1611
|
if (firmwareVersion === '0.0.0') {
|
|
2277
1612
|
Log.warn(
|
|
2278
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.'
|
|
@@ -2286,17 +1621,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2286
1621
|
};
|
|
2287
1622
|
}
|
|
2288
1623
|
|
|
2289
|
-
private async completeProtocolV2FinalVerification() {
|
|
2290
|
-
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
2291
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2292
|
-
}
|
|
2293
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
2294
|
-
this.assertExpectedProtocolV2Versions();
|
|
2295
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
2296
|
-
DevicePool.resetState();
|
|
2297
|
-
return versions;
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
1624
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
2301
1625
|
const startTime = Date.now();
|
|
2302
1626
|
let lastError: unknown;
|
|
@@ -2418,50 +1742,194 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2418
1742
|
}
|
|
2419
1743
|
|
|
2420
1744
|
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
2421
|
-
//
|
|
2422
|
-
//
|
|
1745
|
+
// 序列号在 Loader 刚启动时可能暂时不可用,BLE descriptor 也没有稳定的
|
|
1746
|
+
// path。此时应继续轮询,只有明确读到不同序列号时才立即终止更新。
|
|
2423
1747
|
return this.normalizeErrorMessage(error).includes(
|
|
2424
1748
|
'Protocol V2 reconnect physical identity mismatch'
|
|
2425
1749
|
);
|
|
2426
1750
|
}
|
|
2427
1751
|
|
|
2428
|
-
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(
|
|
2429
1792
|
filePath: string,
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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,
|
|
2448
1874
|
append: false,
|
|
2449
|
-
ui_percentage: progress ?? undefined,
|
|
2450
|
-
},
|
|
2451
|
-
{
|
|
2452
1875
|
writeWithResponse: false,
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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);
|
|
2461
1914
|
}
|
|
2462
|
-
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';
|
|
2463
1931
|
}
|
|
2464
|
-
return
|
|
1932
|
+
return env ?? 'unknown';
|
|
2465
1933
|
}
|
|
2466
1934
|
|
|
2467
1935
|
private async recoverProtocolV2FileTransfer() {
|