@onekeyfe/hd-core 1.2.0-alpha.75 → 1.2.0-alpha.77
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 +45 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +86 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +121 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/protocol-v2.test.ts +405 -100
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +25 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- 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 +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +179 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3088 -1063
- 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 +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +68 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +9 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +57 -6
- package/src/api/FirmwareUpdate.ts +13 -1
- package/src/api/FirmwareUpdateV2.ts +308 -121
- package/src/api/FirmwareUpdateV3.ts +265 -58
- package/src/api/FirmwareUpdateV4.ts +881 -349
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/data-manager/DataManager.ts +62 -54
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +6 -1
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -2
- package/src/lowLevelInject.ts +5 -1
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +75 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -0
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesCompat.ts +6 -0
|
@@ -2,6 +2,7 @@ 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,
|
|
5
6
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
6
7
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
8
|
} from '@onekeyfe/hd-transport';
|
|
@@ -9,7 +10,6 @@ import { sha256 } from '@noble/hashes/sha256';
|
|
|
9
10
|
|
|
10
11
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
11
12
|
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,6 +21,7 @@ import {
|
|
|
21
21
|
getLogger,
|
|
22
22
|
} from '../utils';
|
|
23
23
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
24
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
24
25
|
import { DataManager } from '../data-manager';
|
|
25
26
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
26
27
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -39,11 +40,20 @@ import {
|
|
|
39
40
|
getProtocolV2UnknownErrorText,
|
|
40
41
|
isProtocolV2DeviceDisconnectedError,
|
|
41
42
|
} 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';
|
|
42
49
|
|
|
43
|
-
import type {
|
|
50
|
+
import type {
|
|
51
|
+
FirmwareArtifactReference,
|
|
52
|
+
FirmwareUpdateV4Params,
|
|
53
|
+
FirmwareUpdateV4Target,
|
|
54
|
+
} from '../types/api/firmwareUpdate';
|
|
44
55
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
45
56
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
46
|
-
import type { PROTO } from '../constants';
|
|
47
57
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
48
58
|
import type {
|
|
49
59
|
Features,
|
|
@@ -53,9 +63,14 @@ import type {
|
|
|
53
63
|
IProtocolV2ResourceFile,
|
|
54
64
|
IVersionArray,
|
|
55
65
|
} from '../types';
|
|
66
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
56
67
|
|
|
57
68
|
const Log = getLogger(LoggerNames.Method);
|
|
58
69
|
|
|
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';
|
|
59
74
|
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
60
75
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
61
76
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
@@ -75,7 +90,7 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
75
90
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
76
91
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
77
92
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
78
|
-
const
|
|
93
|
+
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
79
94
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
80
95
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
81
96
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -120,11 +135,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
120
135
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
121
136
|
};
|
|
122
137
|
|
|
123
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
124
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
125
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
138
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
129
139
|
target_id: number | string;
|
|
130
140
|
status?: number | string;
|
|
@@ -138,22 +148,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
138
148
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
139
149
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
140
150
|
};
|
|
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
|
-
|
|
157
151
|
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
158
152
|
type ProtocolV2ResourceBundleBinary = {
|
|
159
153
|
name: string;
|
|
@@ -171,6 +165,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
171
165
|
kind: 'bootloader' | 'firmware';
|
|
172
166
|
};
|
|
173
167
|
|
|
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
|
+
|
|
174
197
|
type ProtocolV2OkppHeader = {
|
|
175
198
|
type: string;
|
|
176
199
|
version: IVersionArray;
|
|
@@ -279,6 +302,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
279
302
|
);
|
|
280
303
|
};
|
|
281
304
|
|
|
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
|
+
|
|
282
311
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
283
312
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
284
313
|
|
|
@@ -316,6 +345,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
316
345
|
|
|
317
346
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
318
347
|
|
|
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
|
+
|
|
319
360
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
320
361
|
Array.from(bytes)
|
|
321
362
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -449,6 +490,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
449
490
|
return ['V2'] as const;
|
|
450
491
|
}
|
|
451
492
|
|
|
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
|
+
|
|
452
503
|
init() {
|
|
453
504
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
454
505
|
this.requireDeviceMode = [];
|
|
@@ -486,10 +537,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
486
537
|
{ name: 'firmwareType', type: 'string' },
|
|
487
538
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
488
539
|
{ name: 'platform', type: 'string' },
|
|
540
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
489
541
|
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
490
542
|
]);
|
|
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
|
+
}
|
|
491
593
|
|
|
492
594
|
this.params = {
|
|
595
|
+
preparedPlan: payload.preparedPlan,
|
|
493
596
|
chunkSize: payload.chunkSize,
|
|
494
597
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
495
598
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -504,19 +607,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
504
607
|
resourceFiles: payload.resourceFiles,
|
|
505
608
|
firmwareType: payload.firmwareType,
|
|
506
609
|
targetsToUpdate: payload.targetsToUpdate,
|
|
610
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
507
611
|
platform: payload.platform,
|
|
612
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
613
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
614
|
+
componentArtifacts: payload.componentArtifacts,
|
|
615
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
508
616
|
};
|
|
509
617
|
}
|
|
510
618
|
|
|
511
|
-
private getProtocolV2FirmwareChunkSize(filePath?: string) {
|
|
619
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write', filePath?: string) {
|
|
512
620
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
513
621
|
const env = DataManager.getSettings('env');
|
|
514
622
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
515
623
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
516
624
|
if (isBle) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
625
|
+
if (direction === 'read') {
|
|
626
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
|
|
627
|
+
} else {
|
|
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
|
+
}
|
|
520
633
|
}
|
|
521
634
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
522
635
|
return maxChunkSize;
|
|
@@ -543,8 +656,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
543
656
|
assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
|
|
544
657
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
545
658
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
546
|
-
|
|
547
|
-
|
|
659
|
+
this.validateExpectedTargetVersions();
|
|
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);
|
|
548
673
|
|
|
549
674
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
550
675
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -556,11 +681,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
556
681
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
557
682
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
558
683
|
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
684
|
+
if (
|
|
685
|
+
(needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) &&
|
|
686
|
+
(this.params.artifactReader ||
|
|
687
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
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
|
+
}
|
|
564
697
|
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
565
698
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
566
699
|
await DataManager.forceReloadData({
|
|
@@ -587,7 +720,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
587
720
|
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
588
721
|
throw err;
|
|
589
722
|
}
|
|
590
|
-
throw
|
|
723
|
+
throw normalizeFirmwarePreparationError(err);
|
|
591
724
|
}
|
|
592
725
|
|
|
593
726
|
if (
|
|
@@ -603,9 +736,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
603
736
|
);
|
|
604
737
|
}
|
|
605
738
|
|
|
606
|
-
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
607
|
-
|
|
608
739
|
if (needsRemoteResources) {
|
|
740
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
609
741
|
try {
|
|
610
742
|
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
611
743
|
resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
|
|
@@ -621,25 +753,293 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
621
753
|
);
|
|
622
754
|
}
|
|
623
755
|
}
|
|
624
|
-
throw
|
|
756
|
+
throw normalizeFirmwarePreparationError(err);
|
|
625
757
|
}
|
|
626
758
|
}
|
|
627
759
|
|
|
628
|
-
|
|
760
|
+
return this.executeProtocolV2Update({
|
|
629
761
|
fwBinaryMap,
|
|
630
762
|
bootloaderBinary,
|
|
631
763
|
...(installItems ? { installItems } : undefined),
|
|
632
764
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
633
765
|
});
|
|
766
|
+
}
|
|
634
767
|
|
|
635
|
-
|
|
636
|
-
await
|
|
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
|
+
}
|
|
637
785
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
+
}
|
|
641
797
|
|
|
642
|
-
|
|
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
|
+
|
|
844
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
845
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
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
|
+
}
|
|
855
|
+
|
|
856
|
+
private async prepareProtocolV2ResourceSources(
|
|
857
|
+
firmwareType: EFirmwareType,
|
|
858
|
+
features: Features
|
|
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
|
+
}
|
|
1022
|
+
|
|
1023
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
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);
|
|
643
1043
|
}
|
|
644
1044
|
|
|
645
1045
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -683,8 +1083,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
683
1083
|
private async captureProtocolV2PhysicalIdentity() {
|
|
684
1084
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
685
1085
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
686
|
-
const path = this.device.originalDescriptor
|
|
687
|
-
|
|
1086
|
+
const path = this.device.originalDescriptor?.path?.trim() || undefined;
|
|
1087
|
+
if (this.params?.preparedPlan) {
|
|
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
|
+
}
|
|
688
1097
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
689
1098
|
this.protocolV2ExpectedPath = path;
|
|
690
1099
|
}
|
|
@@ -920,7 +1329,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
920
1329
|
}
|
|
921
1330
|
|
|
922
1331
|
const digest = sha256.create();
|
|
923
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1332
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('write');
|
|
924
1333
|
let offset = 0;
|
|
925
1334
|
while (offset < file.size) {
|
|
926
1335
|
const response = await commands.typedCall(
|
|
@@ -1015,7 +1424,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1015
1424
|
: await readProtocolV2ResourceInventory({
|
|
1016
1425
|
commands: this.device.getCommands(),
|
|
1017
1426
|
resources,
|
|
1018
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1427
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize('write'),
|
|
1019
1428
|
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1020
1429
|
});
|
|
1021
1430
|
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
@@ -1050,38 +1459,88 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1050
1459
|
};
|
|
1051
1460
|
}
|
|
1052
1461
|
|
|
1053
|
-
private
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1462
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
1463
|
+
if (path.startsWith('vol')) return path;
|
|
1464
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1465
|
+
return `vol0:/${path}`;
|
|
1466
|
+
}
|
|
1059
1467
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1468
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1469
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1470
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
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
|
+
}
|
|
1063
1483
|
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1484
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1485
|
+
const chunks: Uint8Array[] = [];
|
|
1486
|
+
let offset = 0;
|
|
1487
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1488
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1489
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1490
|
+
file: {
|
|
1491
|
+
path: filePath,
|
|
1492
|
+
offset,
|
|
1493
|
+
total_size: 0,
|
|
1494
|
+
},
|
|
1495
|
+
chunk_len: readLen,
|
|
1496
|
+
ui_percentage: undefined,
|
|
1075
1497
|
});
|
|
1498
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
1499
|
+
if (data.byteLength === 0) return null;
|
|
1500
|
+
chunks.push(data);
|
|
1501
|
+
offset += data.byteLength;
|
|
1076
1502
|
}
|
|
1077
1503
|
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
);
|
|
1084
|
-
return
|
|
1504
|
+
const headerBytes = new Uint8Array(offset);
|
|
1505
|
+
let cursor = 0;
|
|
1506
|
+
chunks.forEach(chunk => {
|
|
1507
|
+
headerBytes.set(chunk, cursor);
|
|
1508
|
+
cursor += chunk.byteLength;
|
|
1509
|
+
});
|
|
1510
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
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
|
+
}
|
|
1085
1544
|
}
|
|
1086
1545
|
|
|
1087
1546
|
private isProtocolV2BootloaderMode() {
|
|
@@ -1113,10 +1572,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1113
1572
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1114
1573
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1115
1574
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1575
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1116
1576
|
return false;
|
|
1117
1577
|
}
|
|
1118
1578
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1119
1579
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1580
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1120
1581
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1121
1582
|
return false;
|
|
1122
1583
|
}
|
|
@@ -1126,6 +1587,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1126
1587
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1127
1588
|
await wait(1000);
|
|
1128
1589
|
await this.waitForProtocolV2BootloaderMode();
|
|
1590
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1129
1591
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1130
1592
|
return true;
|
|
1131
1593
|
} catch (error) {
|
|
@@ -1221,6 +1683,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1221
1683
|
return entries;
|
|
1222
1684
|
}
|
|
1223
1685
|
|
|
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
|
+
|
|
1224
1778
|
private async executeProtocolV2Update({
|
|
1225
1779
|
fwBinaryMap,
|
|
1226
1780
|
bootloaderBinary,
|
|
@@ -1232,106 +1786,168 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1232
1786
|
installItems?: ProtocolV2InstallItem[];
|
|
1233
1787
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1234
1788
|
}) {
|
|
1235
|
-
const
|
|
1789
|
+
const memoryInstallItems =
|
|
1236
1790
|
installItems ??
|
|
1237
1791
|
this.buildProtocolV2InstallItems({
|
|
1238
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1239
1792
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1793
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1240
1794
|
});
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
if (resourceBundles?.length) {
|
|
1250
|
-
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1251
|
-
resourceBundles,
|
|
1252
|
-
firmwareSize
|
|
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
|
+
}))
|
|
1253
1803
|
);
|
|
1254
|
-
|
|
1255
|
-
|
|
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();
|
|
1256
1817
|
}
|
|
1818
|
+
}
|
|
1257
1819
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1820
|
+
private async executeProtocolV2TransferPhase({
|
|
1821
|
+
installSources,
|
|
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;
|
|
1263
1832
|
}
|
|
1264
|
-
return;
|
|
1265
1833
|
}
|
|
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
|
-
);
|
|
1278
1834
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
-
}
|
|
1835
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1836
|
+
let processedSize = 0;
|
|
1837
|
+
for (const resource of resourcesToSync) {
|
|
1838
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1839
|
+
source: resource.source,
|
|
1840
|
+
filePath: resource.devicePath,
|
|
1841
|
+
processedSize,
|
|
1842
|
+
totalSize,
|
|
1843
|
+
});
|
|
1844
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
1845
|
+
}
|
|
1302
1846
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1847
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
1848
|
+
for (const item of installSources) {
|
|
1849
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1850
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1851
|
+
source: item.source,
|
|
1852
|
+
filePath,
|
|
1853
|
+
processedSize,
|
|
1854
|
+
totalSize,
|
|
1855
|
+
});
|
|
1856
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1857
|
+
stagedInstallTargets.push({
|
|
1858
|
+
targetId: item.targetId,
|
|
1859
|
+
path: filePath,
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1306
1862
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
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;
|
|
1863
|
+
if (totalSize > 0) {
|
|
1864
|
+
this.postProgressMessage(100, 'transferData');
|
|
1865
|
+
}
|
|
1866
|
+
if (stagedInstallTargets.length === 0) {
|
|
1867
|
+
return;
|
|
1321
1868
|
}
|
|
1322
1869
|
|
|
1323
1870
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1324
|
-
|
|
1325
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1871
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1326
1872
|
target_id: item.targetId,
|
|
1327
1873
|
path: item.path,
|
|
1328
1874
|
}));
|
|
1329
|
-
|
|
1330
|
-
await this.
|
|
1331
|
-
|
|
1875
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1876
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
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
|
+
);
|
|
1332
1948
|
}
|
|
1333
1949
|
|
|
1334
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1950
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1335
1951
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1336
1952
|
}
|
|
1337
1953
|
|
|
@@ -1350,7 +1966,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1350
1966
|
|
|
1351
1967
|
private assertProtocolV2TargetStatus(
|
|
1352
1968
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1353
|
-
expectedTargetIds: Set<number
|
|
1969
|
+
expectedTargetIds: Set<number>,
|
|
1970
|
+
expectedPaths = new Map<number, string>()
|
|
1354
1971
|
) {
|
|
1355
1972
|
Log.log(
|
|
1356
1973
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1381,17 +1998,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1381
1998
|
const matchingTargets = statusTargets.filter(target =>
|
|
1382
1999
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1383
2000
|
);
|
|
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
|
+
);
|
|
1384
2022
|
const completedTargetIds = new Set<number>();
|
|
1385
|
-
|
|
2023
|
+
completedTargets.forEach(target => {
|
|
1386
2024
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1387
|
-
if (targetId !== undefined
|
|
2025
|
+
if (targetId !== undefined) {
|
|
1388
2026
|
completedTargetIds.add(targetId);
|
|
1389
2027
|
}
|
|
1390
2028
|
});
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
2029
|
+
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
2030
|
+
for (const target of completedTargets) {
|
|
2031
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2032
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
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
|
+
}
|
|
1395
2052
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1396
2053
|
return true;
|
|
1397
2054
|
}
|
|
@@ -1429,7 +2086,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1429
2086
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1430
2087
|
targets: Array<{ target_id: number; path: string }>
|
|
1431
2088
|
) {
|
|
2089
|
+
this.protocolV2FinalStatusVerified = false;
|
|
1432
2090
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2091
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1433
2092
|
const startTime = Date.now();
|
|
1434
2093
|
let lastError: unknown;
|
|
1435
2094
|
let shouldReconnect = true;
|
|
@@ -1464,7 +2123,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1464
2123
|
);
|
|
1465
2124
|
const statusTargets = (statusResponse.message.records ??
|
|
1466
2125
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1467
|
-
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
2126
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
2127
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1468
2128
|
return;
|
|
1469
2129
|
}
|
|
1470
2130
|
|
|
@@ -1512,10 +2172,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1512
2172
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1513
2173
|
}
|
|
1514
2174
|
} catch (error) {
|
|
1515
|
-
if (
|
|
1516
|
-
error instanceof HardwareError &&
|
|
1517
|
-
error.errorCode === HardwareErrorCode.FirmwareError
|
|
1518
|
-
) {
|
|
2175
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
1519
2176
|
throw error;
|
|
1520
2177
|
}
|
|
1521
2178
|
// Only consecutive full status dumps can prove that a record is absent.
|
|
@@ -1555,7 +2212,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1555
2212
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1556
2213
|
throw error;
|
|
1557
2214
|
}
|
|
1558
|
-
if (error
|
|
2215
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2216
|
+
throw error;
|
|
2217
|
+
}
|
|
2218
|
+
if (
|
|
2219
|
+
error instanceof HardwareError &&
|
|
2220
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
2221
|
+
) {
|
|
1559
2222
|
throw error;
|
|
1560
2223
|
}
|
|
1561
2224
|
shouldReconnect = true;
|
|
@@ -1586,6 +2249,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1586
2249
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1587
2250
|
}
|
|
1588
2251
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
2252
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1589
2253
|
}
|
|
1590
2254
|
|
|
1591
2255
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1608,6 +2272,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1608
2272
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1609
2273
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1610
2274
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2275
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1611
2276
|
if (firmwareVersion === '0.0.0') {
|
|
1612
2277
|
Log.warn(
|
|
1613
2278
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1621,6 +2286,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1621
2286
|
};
|
|
1622
2287
|
}
|
|
1623
2288
|
|
|
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
|
+
|
|
1624
2300
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1625
2301
|
const startTime = Date.now();
|
|
1626
2302
|
let lastError: unknown;
|
|
@@ -1742,194 +2418,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1742
2418
|
}
|
|
1743
2419
|
|
|
1744
2420
|
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
1745
|
-
//
|
|
1746
|
-
//
|
|
2421
|
+
// A serial can be temporarily unavailable while Loader starts, and BLE has no stable path.
|
|
2422
|
+
// Keep polling unless the device explicitly reports a different serial.
|
|
1747
2423
|
return this.normalizeErrorMessage(error).includes(
|
|
1748
2424
|
'Protocol V2 reconnect physical identity mismatch'
|
|
1749
2425
|
);
|
|
1750
2426
|
}
|
|
1751
2427
|
|
|
1752
|
-
private async
|
|
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(
|
|
2428
|
+
private async fileWriteChunk(
|
|
1792
2429
|
filePath: string,
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
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,
|
|
2430
|
+
totalFileSize: number,
|
|
2431
|
+
offset: number,
|
|
2432
|
+
chunk: ArrayBuffer | Buffer,
|
|
2433
|
+
overwrite: boolean,
|
|
2434
|
+
progress: number | null
|
|
2435
|
+
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
2436
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2437
|
+
const writeRes = await typedCall(
|
|
2438
|
+
'FilesystemFileWrite',
|
|
2439
|
+
'FilesystemFile',
|
|
2440
|
+
{
|
|
2441
|
+
file: {
|
|
2442
|
+
path: filePath,
|
|
2443
|
+
offset,
|
|
2444
|
+
total_size: totalFileSize,
|
|
2445
|
+
data: chunk,
|
|
2446
|
+
},
|
|
2447
|
+
overwrite,
|
|
1874
2448
|
append: false,
|
|
2449
|
+
ui_percentage: progress ?? undefined,
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
1875
2452
|
writeWithResponse: false,
|
|
1876
|
-
|
|
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);
|
|
2453
|
+
onWriteCompleted: () => undefined,
|
|
1914
2454
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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';
|
|
2455
|
+
);
|
|
2456
|
+
if (writeRes.type !== 'FilesystemFile') {
|
|
2457
|
+
if ((writeRes as any).type === 'CallMethodError') {
|
|
2458
|
+
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
2459
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
|
|
1931
2463
|
}
|
|
1932
|
-
return
|
|
2464
|
+
return writeRes;
|
|
1933
2465
|
}
|
|
1934
2466
|
|
|
1935
2467
|
private async recoverProtocolV2FileTransfer() {
|