@onekeyfe/hd-core 1.2.0-alpha.69 → 1.2.0-alpha.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -28
- 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 +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +63 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +1 -33
- package/__tests__/protocol-v2-resources.test.ts +6 -9
- package/__tests__/protocol-v2.test.ts +383 -174
- package/__tests__/protocolV2FileWrite.test.ts +7 -48
- package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
- 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 -6
- 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/helpers/protocolV2FileWrite.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 +180 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3038 -1041
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -1
- package/dist/protocols/protocol-v2/resources.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 +14 -1
- 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 +61 -25
- package/src/api/FirmwareUpdate.ts +13 -1
- package/src/api/FirmwareUpdateV2.ts +296 -121
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +865 -287
- 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/api/helpers/protocolV2FileWrite.ts +4 -54
- 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/protocols/protocol-v2/resources.ts +1 -5
- 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 +14 -2
- package/src/utils/deviceFeaturesCompat.ts +6 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
|
+
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
4
5
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
6
7
|
} from '@onekeyfe/hd-transport';
|
|
@@ -8,18 +9,17 @@ import { sha256 } from '@noble/hashes/sha256';
|
|
|
8
9
|
|
|
9
10
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
10
11
|
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
11
|
-
import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
|
|
12
12
|
import { validateParams } from './helpers/paramsValidator';
|
|
13
13
|
import {
|
|
14
14
|
LoggerNames,
|
|
15
15
|
getDeviceBLEFirmwareVersion,
|
|
16
16
|
getDeviceBootloaderVersion,
|
|
17
17
|
getDeviceFirmwareVersion,
|
|
18
|
-
getDeviceType,
|
|
19
18
|
getFirmwareType,
|
|
20
19
|
getLogger,
|
|
21
20
|
} from '../utils';
|
|
22
21
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
22
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
23
23
|
import { DataManager } from '../data-manager';
|
|
24
24
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
25
25
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -38,11 +38,20 @@ import {
|
|
|
38
38
|
getProtocolV2UnknownErrorText,
|
|
39
39
|
isProtocolV2DeviceDisconnectedError,
|
|
40
40
|
} from './protocol-v2/helpers';
|
|
41
|
+
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
42
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
43
|
+
import {
|
|
44
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
45
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
46
|
+
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
41
47
|
|
|
42
|
-
import type {
|
|
48
|
+
import type {
|
|
49
|
+
FirmwareArtifactReference,
|
|
50
|
+
FirmwareUpdateV4Params,
|
|
51
|
+
FirmwareUpdateV4Target,
|
|
52
|
+
} from '../types/api/firmwareUpdate';
|
|
43
53
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
44
54
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
45
|
-
import type { PROTO } from '../constants';
|
|
46
55
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
47
56
|
import type {
|
|
48
57
|
Features,
|
|
@@ -52,6 +61,7 @@ import type {
|
|
|
52
61
|
IProtocolV2ResourceFile,
|
|
53
62
|
IVersionArray,
|
|
54
63
|
} from '../types';
|
|
64
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
55
65
|
|
|
56
66
|
const Log = getLogger(LoggerNames.Method);
|
|
57
67
|
|
|
@@ -74,35 +84,12 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
74
84
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
75
85
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
76
86
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
87
|
+
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
77
88
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
78
89
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
79
90
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
80
91
|
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
81
92
|
|
|
82
|
-
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
83
|
-
|
|
84
|
-
export function assertProtocolV2FirmwareTargetsSupported(
|
|
85
|
-
deviceType: EDeviceType | string | undefined,
|
|
86
|
-
params: FirmwareUpdateV4Params
|
|
87
|
-
) {
|
|
88
|
-
if (deviceType !== EDeviceType.Neo) return;
|
|
89
|
-
|
|
90
|
-
const unsupportedTargets = new Set(
|
|
91
|
-
(params.targetsToUpdate ?? []).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
|
|
92
|
-
);
|
|
93
|
-
if (params.se03Binary) unsupportedTargets.add('se03');
|
|
94
|
-
if (params.se04Binary) unsupportedTargets.add('se04');
|
|
95
|
-
|
|
96
|
-
if (unsupportedTargets.size) {
|
|
97
|
-
throw ERRORS.TypedError(
|
|
98
|
-
HardwareErrorCode.RuntimeError,
|
|
99
|
-
`Neo only supports SE01 and SE02; unsupported firmware targets: ${Array.from(
|
|
100
|
-
unsupportedTargets
|
|
101
|
-
).join(', ')}`
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
93
|
const getProtocolV2DeviceTransferProgress = (
|
|
107
94
|
bytesBeforeChunk: number,
|
|
108
95
|
bytesAfterChunk: number,
|
|
@@ -120,11 +107,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
120
107
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
121
108
|
};
|
|
122
109
|
|
|
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
110
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
129
111
|
target_id: number | string;
|
|
130
112
|
status?: number | string;
|
|
@@ -138,17 +120,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
138
120
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
139
121
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
140
122
|
};
|
|
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
123
|
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
153
124
|
type ProtocolV2ResourceBundleBinary = {
|
|
154
125
|
name: string;
|
|
@@ -166,6 +137,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
166
137
|
kind: 'bootloader' | 'firmware';
|
|
167
138
|
};
|
|
168
139
|
|
|
140
|
+
type ProtocolV2InstallSource = {
|
|
141
|
+
fileName: string;
|
|
142
|
+
source: FirmwareByteSource;
|
|
143
|
+
targetId: number;
|
|
144
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type ProtocolV2ResourceBundleSource = {
|
|
148
|
+
name: string;
|
|
149
|
+
source: FirmwareByteSource;
|
|
150
|
+
devicePath: string;
|
|
151
|
+
version?: IVersionArray;
|
|
152
|
+
payloadHash?: string;
|
|
153
|
+
headerHash?: string;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
type ProtocolV2ExecutionPhaseKind =
|
|
157
|
+
| 'resource-sync'
|
|
158
|
+
| 'bootloader-install'
|
|
159
|
+
| 'bootloader-verify'
|
|
160
|
+
| 'component-install'
|
|
161
|
+
| 'final-verify';
|
|
162
|
+
|
|
163
|
+
type ProtocolV2ExecutionPhase = {
|
|
164
|
+
kind: ProtocolV2ExecutionPhaseKind;
|
|
165
|
+
installSources: ProtocolV2InstallSource[];
|
|
166
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
169
|
type ProtocolV2OkppHeader = {
|
|
170
170
|
type: string;
|
|
171
171
|
version: IVersionArray;
|
|
@@ -268,6 +268,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
268
268
|
);
|
|
269
269
|
};
|
|
270
270
|
|
|
271
|
+
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
272
|
+
error instanceof HardwareError &&
|
|
273
|
+
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
274
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
275
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
276
|
+
|
|
271
277
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
272
278
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
273
279
|
|
|
@@ -305,6 +311,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
305
311
|
|
|
306
312
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
307
313
|
|
|
314
|
+
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
315
|
+
if (!version) return undefined;
|
|
316
|
+
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
320
|
+
const currentNumber = versionArrayToNumber(current);
|
|
321
|
+
const targetNumber = versionArrayToNumber(target);
|
|
322
|
+
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
323
|
+
return currentNumber - targetNumber;
|
|
324
|
+
};
|
|
325
|
+
|
|
308
326
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
309
327
|
Array.from(bytes)
|
|
310
328
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -438,6 +456,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
438
456
|
return ['V2'] as const;
|
|
439
457
|
}
|
|
440
458
|
|
|
459
|
+
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
460
|
+
|
|
461
|
+
private protocolV2ExecutionInLoader = false;
|
|
462
|
+
|
|
463
|
+
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
464
|
+
|
|
465
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
466
|
+
|
|
467
|
+
private protocolV2FinalStatusVerified = false;
|
|
468
|
+
|
|
441
469
|
init() {
|
|
442
470
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
443
471
|
this.requireDeviceMode = [];
|
|
@@ -475,10 +503,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
475
503
|
{ name: 'firmwareType', type: 'string' },
|
|
476
504
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
477
505
|
{ name: 'platform', type: 'string' },
|
|
506
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
478
507
|
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
479
508
|
]);
|
|
509
|
+
if (
|
|
510
|
+
payload.expectedDeviceId !== undefined &&
|
|
511
|
+
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
512
|
+
) {
|
|
513
|
+
throw ERRORS.TypedError(
|
|
514
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
515
|
+
'Protocol V2 expected device identity is invalid'
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const hostBinding =
|
|
519
|
+
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
520
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
521
|
+
: undefined;
|
|
522
|
+
if (hostBinding) {
|
|
523
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
524
|
+
preparedPlan: payload.preparedPlan,
|
|
525
|
+
executor: 'v4',
|
|
526
|
+
platform: payload.platform,
|
|
527
|
+
scopeTargets: [
|
|
528
|
+
'boot',
|
|
529
|
+
'app_v1',
|
|
530
|
+
'app_v2',
|
|
531
|
+
'coprocessor',
|
|
532
|
+
'resource',
|
|
533
|
+
'se01',
|
|
534
|
+
'se02',
|
|
535
|
+
'se03',
|
|
536
|
+
'se04',
|
|
537
|
+
],
|
|
538
|
+
bindings: [
|
|
539
|
+
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
540
|
+
artifact
|
|
541
|
+
? [
|
|
542
|
+
{
|
|
543
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
544
|
+
artifact,
|
|
545
|
+
},
|
|
546
|
+
]
|
|
547
|
+
: []
|
|
548
|
+
),
|
|
549
|
+
...(payload.resourceBundleArtifacts ?? []).map(
|
|
550
|
+
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
551
|
+
target: 'resource' as const,
|
|
552
|
+
logicalName: entry.name,
|
|
553
|
+
artifact: entry.artifact,
|
|
554
|
+
})
|
|
555
|
+
),
|
|
556
|
+
],
|
|
557
|
+
});
|
|
558
|
+
}
|
|
480
559
|
|
|
481
560
|
this.params = {
|
|
561
|
+
preparedPlan: payload.preparedPlan,
|
|
482
562
|
chunkSize: payload.chunkSize,
|
|
483
563
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
484
564
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -493,17 +573,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
493
573
|
resourceFiles: payload.resourceFiles,
|
|
494
574
|
firmwareType: payload.firmwareType,
|
|
495
575
|
targetsToUpdate: payload.targetsToUpdate,
|
|
576
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
496
577
|
platform: payload.platform,
|
|
578
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
579
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
580
|
+
componentArtifacts: payload.componentArtifacts,
|
|
581
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
497
582
|
};
|
|
498
583
|
}
|
|
499
584
|
|
|
500
|
-
private getProtocolV2FirmwareChunkSize() {
|
|
585
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
|
|
501
586
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
502
587
|
const env = DataManager.getSettings('env');
|
|
503
588
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
504
589
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
505
590
|
if (isBle) {
|
|
506
|
-
maxChunkSize =
|
|
591
|
+
maxChunkSize =
|
|
592
|
+
direction === 'read'
|
|
593
|
+
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
594
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
507
595
|
}
|
|
508
596
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
509
597
|
return maxChunkSize;
|
|
@@ -522,16 +610,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
522
610
|
private async runProtocolV2() {
|
|
523
611
|
await this.captureProtocolV2PhysicalIdentity();
|
|
524
612
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
525
|
-
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
526
|
-
const capabilityDeviceType =
|
|
527
|
-
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
528
|
-
? currentDeviceType
|
|
529
|
-
: getDeviceType(deviceFeatures);
|
|
530
|
-
assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
|
|
531
613
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
532
614
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
615
|
+
this.validateExpectedTargetVersions();
|
|
616
|
+
|
|
617
|
+
if (
|
|
618
|
+
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
619
|
+
this.params.resourceBundleArtifacts?.length
|
|
620
|
+
) {
|
|
621
|
+
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
622
|
+
}
|
|
533
623
|
const needsRemoteResources =
|
|
534
624
|
!this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
|
|
625
|
+
const needsRemoteBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
535
626
|
|
|
536
627
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
537
628
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -543,9 +634,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
543
634
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
544
635
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
545
636
|
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
637
|
+
if (
|
|
638
|
+
(needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) &&
|
|
639
|
+
(this.params.artifactReader ||
|
|
640
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
641
|
+
) {
|
|
642
|
+
throw ERRORS.TypedError(
|
|
643
|
+
HardwareErrorCode.RuntimeError,
|
|
644
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
645
|
+
{
|
|
646
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
}
|
|
549
650
|
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
550
651
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
551
652
|
await DataManager.forceReloadData({
|
|
@@ -572,7 +673,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
572
673
|
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
573
674
|
throw err;
|
|
574
675
|
}
|
|
575
|
-
throw
|
|
676
|
+
throw normalizeFirmwarePreparationError(err);
|
|
576
677
|
}
|
|
577
678
|
|
|
578
679
|
if (
|
|
@@ -588,9 +689,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
588
689
|
);
|
|
589
690
|
}
|
|
590
691
|
|
|
591
|
-
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
592
|
-
|
|
593
692
|
if (needsRemoteResources) {
|
|
693
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
594
694
|
try {
|
|
595
695
|
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
596
696
|
resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
|
|
@@ -606,25 +706,293 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
606
706
|
);
|
|
607
707
|
}
|
|
608
708
|
}
|
|
609
|
-
throw
|
|
709
|
+
throw normalizeFirmwarePreparationError(err);
|
|
610
710
|
}
|
|
611
711
|
}
|
|
612
712
|
|
|
613
|
-
|
|
713
|
+
return this.executeProtocolV2Update({
|
|
614
714
|
fwBinaryMap,
|
|
615
715
|
bootloaderBinary,
|
|
616
716
|
...(installItems ? { installItems } : undefined),
|
|
617
717
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
618
718
|
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
722
|
+
const source = await openFirmwareByteSource({
|
|
723
|
+
artifact,
|
|
724
|
+
reader: this.params.artifactReader,
|
|
725
|
+
});
|
|
726
|
+
if (!source) {
|
|
727
|
+
throw ERRORS.TypedError(
|
|
728
|
+
HardwareErrorCode.RuntimeError,
|
|
729
|
+
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
730
|
+
{
|
|
731
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
this.protocolV2PreparedSources.push(source);
|
|
736
|
+
return source;
|
|
737
|
+
}
|
|
619
738
|
|
|
620
|
-
|
|
621
|
-
await
|
|
739
|
+
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
740
|
+
const source = await openFirmwareByteSource({ binary });
|
|
741
|
+
if (!source) {
|
|
742
|
+
throw ERRORS.TypedError(
|
|
743
|
+
HardwareErrorCode.RuntimeError,
|
|
744
|
+
'Protocol V2 firmware binary is empty'
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
this.protocolV2PreparedSources.push(source);
|
|
748
|
+
return source;
|
|
749
|
+
}
|
|
622
750
|
|
|
623
|
-
|
|
624
|
-
this.
|
|
625
|
-
|
|
751
|
+
private async closeProtocolV2PreparedSources() {
|
|
752
|
+
const sources = this.protocolV2PreparedSources.splice(0);
|
|
753
|
+
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
754
|
+
}
|
|
626
755
|
|
|
627
|
-
|
|
756
|
+
private async prepareProtocolV2InstallSources(
|
|
757
|
+
firmwareType: EFirmwareType,
|
|
758
|
+
features: Features
|
|
759
|
+
): Promise<ProtocolV2InstallSource[]> {
|
|
760
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
761
|
+
if (!release) {
|
|
762
|
+
throw ERRORS.TypedError(
|
|
763
|
+
HardwareErrorCode.RuntimeError,
|
|
764
|
+
'Protocol V2 firmware release is unavailable'
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
768
|
+
const requestedTargets = new Set(
|
|
769
|
+
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
770
|
+
);
|
|
771
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
772
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
773
|
+
|
|
774
|
+
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
775
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
776
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
777
|
+
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
778
|
+
const artifact = componentArtifacts[updateTarget];
|
|
779
|
+
if (!artifact) {
|
|
780
|
+
throw ERRORS.TypedError(
|
|
781
|
+
HardwareErrorCode.RuntimeError,
|
|
782
|
+
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
783
|
+
{
|
|
784
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
785
|
+
artifactName: updateTarget,
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
installSources.push({
|
|
790
|
+
...target,
|
|
791
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
792
|
+
});
|
|
793
|
+
preparedTargets.add(updateTarget);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
798
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
799
|
+
);
|
|
800
|
+
if (unknownTarget) {
|
|
801
|
+
throw ERRORS.TypedError(
|
|
802
|
+
HardwareErrorCode.RuntimeError,
|
|
803
|
+
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return installSources;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private async prepareProtocolV2ResourceSources(
|
|
810
|
+
firmwareType: EFirmwareType,
|
|
811
|
+
features: Features
|
|
812
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
813
|
+
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
814
|
+
const resourceRequested =
|
|
815
|
+
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
816
|
+
if (!resourceRequested) {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
820
|
+
const descriptors = release?.resourceBundles ?? [];
|
|
821
|
+
const artifactByName = new Map(
|
|
822
|
+
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
823
|
+
);
|
|
824
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
825
|
+
for (const descriptor of descriptors) {
|
|
826
|
+
const artifact = artifactByName.get(descriptor.name);
|
|
827
|
+
if (!artifact) {
|
|
828
|
+
throw ERRORS.TypedError(
|
|
829
|
+
HardwareErrorCode.RuntimeError,
|
|
830
|
+
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
831
|
+
{
|
|
832
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
833
|
+
artifactName: descriptor.name,
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
838
|
+
descriptor.devicePath,
|
|
839
|
+
'resourceBundles[].devicePath'
|
|
840
|
+
);
|
|
841
|
+
sources.push({
|
|
842
|
+
name: descriptor.name,
|
|
843
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
844
|
+
devicePath,
|
|
845
|
+
version: descriptor.version,
|
|
846
|
+
payloadHash: descriptor.payloadHash,
|
|
847
|
+
headerHash: descriptor.headerHash,
|
|
848
|
+
});
|
|
849
|
+
artifactByName.delete(descriptor.name);
|
|
850
|
+
}
|
|
851
|
+
if (artifactByName.size > 0) {
|
|
852
|
+
throw ERRORS.TypedError(
|
|
853
|
+
HardwareErrorCode.RuntimeError,
|
|
854
|
+
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
return sources;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
861
|
+
try {
|
|
862
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
863
|
+
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
864
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
865
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
866
|
+
throw ERRORS.TypedError(
|
|
867
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
868
|
+
'No firmware to update'
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
872
|
+
|
|
873
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
874
|
+
installSources,
|
|
875
|
+
resourceSources,
|
|
876
|
+
});
|
|
877
|
+
} finally {
|
|
878
|
+
await this.closeProtocolV2PreparedSources();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
private validateExpectedTargetVersions() {
|
|
883
|
+
const expected = this.params.expectedTargetVersions;
|
|
884
|
+
if (expected === undefined) return;
|
|
885
|
+
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
886
|
+
throw ERRORS.TypedError(
|
|
887
|
+
HardwareErrorCode.RuntimeError,
|
|
888
|
+
'Protocol V2 expected target versions are invalid'
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
for (const [target, version] of Object.entries(expected)) {
|
|
892
|
+
if (
|
|
893
|
+
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
894
|
+
target as FirmwareUpdateV4Target
|
|
895
|
+
) ||
|
|
896
|
+
typeof version !== 'string' ||
|
|
897
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
898
|
+
version.length > 64
|
|
899
|
+
) {
|
|
900
|
+
throw ERRORS.TypedError(
|
|
901
|
+
HardwareErrorCode.RuntimeError,
|
|
902
|
+
'Protocol V2 expected target version is invalid'
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
909
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
910
|
+
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
911
|
+
if (!version) return undefined;
|
|
912
|
+
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
913
|
+
if (
|
|
914
|
+
parts.length !== 3 ||
|
|
915
|
+
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
916
|
+
) {
|
|
917
|
+
return undefined;
|
|
918
|
+
}
|
|
919
|
+
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
923
|
+
const expected = this.params?.expectedTargetVersions;
|
|
924
|
+
if (!expected) return;
|
|
925
|
+
const features = this.protocolV2LatestFinalFeatures;
|
|
926
|
+
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
927
|
+
const applicationTargets = requestedTargets.filter(
|
|
928
|
+
target => target === 'app_v1' || target === 'app_v2'
|
|
929
|
+
);
|
|
930
|
+
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
931
|
+
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
932
|
+
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
933
|
+
se01: features?.se01Version ?? undefined,
|
|
934
|
+
se02: features?.se02Version ?? undefined,
|
|
935
|
+
se03: features?.se03Version ?? undefined,
|
|
936
|
+
se04: features?.se04Version ?? undefined,
|
|
937
|
+
};
|
|
938
|
+
if (features && applicationTargets.length === 1) {
|
|
939
|
+
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
940
|
+
}
|
|
941
|
+
const expectedEntries = (
|
|
942
|
+
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
943
|
+
).filter(([target]) => !targets || targets.includes(target));
|
|
944
|
+
for (const [target, expectedVersion] of expectedEntries) {
|
|
945
|
+
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
946
|
+
([, mappedTarget]) => mappedTarget === target
|
|
947
|
+
)?.[0];
|
|
948
|
+
const statusVersion =
|
|
949
|
+
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
950
|
+
const observedVersion =
|
|
951
|
+
statusVersion === undefined
|
|
952
|
+
? visibleVersions[target]
|
|
953
|
+
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
954
|
+
Math.floor(statusVersion / 0x100) % 0x100
|
|
955
|
+
}.${statusVersion % 0x100}`;
|
|
956
|
+
if (!observedVersion) {
|
|
957
|
+
if (this.protocolV2FinalStatusVerified) {
|
|
958
|
+
Log.warn(
|
|
959
|
+
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
960
|
+
);
|
|
961
|
+
} else {
|
|
962
|
+
throw ERRORS.TypedError(
|
|
963
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
964
|
+
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
} else if (observedVersion !== expectedVersion) {
|
|
968
|
+
throw ERRORS.TypedError(
|
|
969
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
970
|
+
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
977
|
+
if (this.params.targetsToUpdate?.length) {
|
|
978
|
+
return [...new Set(this.params.targetsToUpdate)];
|
|
979
|
+
}
|
|
980
|
+
const targets = new Set<FirmwareUpdateV4Target>();
|
|
981
|
+
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
982
|
+
targets.add(target as FirmwareUpdateV4Target)
|
|
983
|
+
);
|
|
984
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
|
|
985
|
+
targets.add('resource');
|
|
986
|
+
}
|
|
987
|
+
if (this.params.bootloaderBinary) targets.add('boot');
|
|
988
|
+
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
989
|
+
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
990
|
+
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
991
|
+
if (this.params.se01Binary) targets.add('se01');
|
|
992
|
+
if (this.params.se02Binary) targets.add('se02');
|
|
993
|
+
if (this.params.se03Binary) targets.add('se03');
|
|
994
|
+
if (this.params.se04Binary) targets.add('se04');
|
|
995
|
+
return Array.from(targets);
|
|
628
996
|
}
|
|
629
997
|
|
|
630
998
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -668,8 +1036,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
668
1036
|
private async captureProtocolV2PhysicalIdentity() {
|
|
669
1037
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
670
1038
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
671
|
-
const path = this.device.originalDescriptor
|
|
672
|
-
|
|
1039
|
+
const path = this.device.originalDescriptor?.path?.trim() || undefined;
|
|
1040
|
+
if (this.params?.preparedPlan) {
|
|
1041
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1042
|
+
preparedPlan: this.params.preparedPlan,
|
|
1043
|
+
deviceIdentity: serialNumber,
|
|
1044
|
+
});
|
|
1045
|
+
} else if (this.params?.expectedDeviceId) {
|
|
1046
|
+
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
1047
|
+
} else {
|
|
1048
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1049
|
+
}
|
|
673
1050
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
674
1051
|
this.protocolV2ExpectedPath = path;
|
|
675
1052
|
}
|
|
@@ -846,12 +1223,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
846
1223
|
private async prepareProtocolV2BootResources(): Promise<
|
|
847
1224
|
ProtocolV2ResourceBundleBinary[] | undefined
|
|
848
1225
|
> {
|
|
849
|
-
if (
|
|
850
|
-
!this.params.targetsToUpdate?.includes('resource') &&
|
|
851
|
-
!this.params.targetsToUpdate?.includes('boot_resources')
|
|
852
|
-
) {
|
|
853
|
-
return undefined;
|
|
854
|
-
}
|
|
1226
|
+
if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
|
|
855
1227
|
const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
|
|
856
1228
|
if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
|
|
857
1229
|
|
|
@@ -958,38 +1330,88 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
958
1330
|
};
|
|
959
1331
|
}
|
|
960
1332
|
|
|
961
|
-
private
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1333
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
1334
|
+
if (path.startsWith('vol')) return path;
|
|
1335
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1336
|
+
return `vol0:/${path}`;
|
|
1337
|
+
}
|
|
967
1338
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1339
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1340
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1341
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1342
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1343
|
+
path: filePath,
|
|
1344
|
+
});
|
|
1345
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1346
|
+
if (
|
|
1347
|
+
!pathInfoRes.message?.exist ||
|
|
1348
|
+
pathInfoRes.message?.directory ||
|
|
1349
|
+
fileSize === undefined ||
|
|
1350
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1351
|
+
) {
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
971
1354
|
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1355
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1356
|
+
const chunks: Uint8Array[] = [];
|
|
1357
|
+
let offset = 0;
|
|
1358
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1359
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1360
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1361
|
+
file: {
|
|
1362
|
+
path: filePath,
|
|
1363
|
+
offset,
|
|
1364
|
+
total_size: 0,
|
|
1365
|
+
},
|
|
1366
|
+
chunk_len: readLen,
|
|
1367
|
+
ui_percentage: undefined,
|
|
983
1368
|
});
|
|
1369
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
1370
|
+
if (data.byteLength === 0) return null;
|
|
1371
|
+
chunks.push(data);
|
|
1372
|
+
offset += data.byteLength;
|
|
984
1373
|
}
|
|
985
1374
|
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
);
|
|
992
|
-
return
|
|
1375
|
+
const headerBytes = new Uint8Array(offset);
|
|
1376
|
+
let cursor = 0;
|
|
1377
|
+
chunks.forEach(chunk => {
|
|
1378
|
+
headerBytes.set(chunk, cursor);
|
|
1379
|
+
cursor += chunk.byteLength;
|
|
1380
|
+
});
|
|
1381
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/** Compare a prepared okpkg header when its manifest supplies version or hash metadata. */
|
|
1385
|
+
private async isProtocolV2ResourceBundleUpToDate(
|
|
1386
|
+
bundle: Pick<
|
|
1387
|
+
ProtocolV2ResourceBundleSource,
|
|
1388
|
+
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1389
|
+
>
|
|
1390
|
+
): Promise<boolean> {
|
|
1391
|
+
if (this.params?.forcedUpdateRes) return false;
|
|
1392
|
+
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1393
|
+
|
|
1394
|
+
try {
|
|
1395
|
+
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1396
|
+
if (!header) return false;
|
|
1397
|
+
|
|
1398
|
+
if (bundle.version) {
|
|
1399
|
+
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
1400
|
+
if (cmp === undefined || cmp !== 0) return false;
|
|
1401
|
+
}
|
|
1402
|
+
if (bundle.payloadHash) {
|
|
1403
|
+
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1404
|
+
if (expected && header.payloadHash !== expected) return false;
|
|
1405
|
+
}
|
|
1406
|
+
if (bundle.headerHash) {
|
|
1407
|
+
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1408
|
+
if (expected && header.headerHash !== expected) return false;
|
|
1409
|
+
}
|
|
1410
|
+
return true;
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
993
1415
|
}
|
|
994
1416
|
|
|
995
1417
|
private isProtocolV2BootloaderMode() {
|
|
@@ -1021,10 +1443,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1021
1443
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1022
1444
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1023
1445
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1446
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1024
1447
|
return false;
|
|
1025
1448
|
}
|
|
1026
1449
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1027
1450
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1451
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1028
1452
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1029
1453
|
return false;
|
|
1030
1454
|
}
|
|
@@ -1034,6 +1458,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1034
1458
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1035
1459
|
await wait(1000);
|
|
1036
1460
|
await this.waitForProtocolV2BootloaderMode();
|
|
1461
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1037
1462
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1038
1463
|
return true;
|
|
1039
1464
|
} catch (error) {
|
|
@@ -1129,6 +1554,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1129
1554
|
return entries;
|
|
1130
1555
|
}
|
|
1131
1556
|
|
|
1557
|
+
private buildProtocolV2ExecutionPhases({
|
|
1558
|
+
installSources,
|
|
1559
|
+
resourceSources,
|
|
1560
|
+
}: {
|
|
1561
|
+
installSources: ProtocolV2InstallSource[];
|
|
1562
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1563
|
+
}): ProtocolV2ExecutionPhase[] {
|
|
1564
|
+
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1565
|
+
if (resourceSources.length > 0) {
|
|
1566
|
+
phases.push({
|
|
1567
|
+
kind: 'resource-sync',
|
|
1568
|
+
installSources: [],
|
|
1569
|
+
resourceSources,
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1573
|
+
if (bootloaderSources.length > 0) {
|
|
1574
|
+
phases.push(
|
|
1575
|
+
{
|
|
1576
|
+
kind: 'bootloader-install',
|
|
1577
|
+
installSources: bootloaderSources,
|
|
1578
|
+
resourceSources: [],
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
kind: 'bootloader-verify',
|
|
1582
|
+
installSources: [],
|
|
1583
|
+
resourceSources: [],
|
|
1584
|
+
}
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1588
|
+
if (componentSources.length > 0) {
|
|
1589
|
+
phases.push({
|
|
1590
|
+
kind: 'component-install',
|
|
1591
|
+
installSources: componentSources,
|
|
1592
|
+
resourceSources: [],
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
phases.push({
|
|
1596
|
+
kind: 'final-verify',
|
|
1597
|
+
installSources: [],
|
|
1598
|
+
resourceSources: [],
|
|
1599
|
+
});
|
|
1600
|
+
return phases;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
private async executeProtocolV2Phases({
|
|
1604
|
+
installSources,
|
|
1605
|
+
resourceSources,
|
|
1606
|
+
}: {
|
|
1607
|
+
installSources: ProtocolV2InstallSource[];
|
|
1608
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1609
|
+
}) {
|
|
1610
|
+
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1611
|
+
installSources,
|
|
1612
|
+
resourceSources,
|
|
1613
|
+
});
|
|
1614
|
+
for (const phase of phases) {
|
|
1615
|
+
if (phase.kind === 'final-verify') {
|
|
1616
|
+
return this.completeProtocolV2FinalVerification();
|
|
1617
|
+
}
|
|
1618
|
+
if (phase.kind === 'resource-sync') {
|
|
1619
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1620
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1621
|
+
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1622
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1623
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1624
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1625
|
+
} else if (phase.kind === 'bootloader-verify') {
|
|
1626
|
+
await this.waitForProtocolV2FinalFeatures();
|
|
1627
|
+
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
throw ERRORS.TypedError(
|
|
1631
|
+
HardwareErrorCode.RuntimeError,
|
|
1632
|
+
'Protocol V2 execution has no final verification phase'
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
private async executeProtocolV2SourceUpdate({
|
|
1637
|
+
installSources,
|
|
1638
|
+
resourceSources,
|
|
1639
|
+
}: {
|
|
1640
|
+
installSources: ProtocolV2InstallSource[];
|
|
1641
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1642
|
+
}) {
|
|
1643
|
+
return this.executeProtocolV2Phases({
|
|
1644
|
+
installSources,
|
|
1645
|
+
resourceSources,
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1132
1649
|
private async executeProtocolV2Update({
|
|
1133
1650
|
fwBinaryMap,
|
|
1134
1651
|
bootloaderBinary,
|
|
@@ -1140,104 +1657,168 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1140
1657
|
installItems?: ProtocolV2InstallItem[];
|
|
1141
1658
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1142
1659
|
}) {
|
|
1143
|
-
const
|
|
1660
|
+
const memoryInstallItems =
|
|
1144
1661
|
installItems ??
|
|
1145
1662
|
this.buildProtocolV2InstallItems({
|
|
1146
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1147
1663
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1664
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1148
1665
|
});
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
resourceBundles
|
|
1160
|
-
|
|
1666
|
+
try {
|
|
1667
|
+
const installSources = await Promise.all(
|
|
1668
|
+
memoryInstallItems.map(async item => ({
|
|
1669
|
+
fileName: item.fileName,
|
|
1670
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1671
|
+
targetId: item.targetId,
|
|
1672
|
+
kind: item.kind,
|
|
1673
|
+
}))
|
|
1674
|
+
);
|
|
1675
|
+
const resourceSources = await Promise.all(
|
|
1676
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1677
|
+
name: bundle.name,
|
|
1678
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1679
|
+
devicePath: bundle.devicePath,
|
|
1680
|
+
}))
|
|
1161
1681
|
);
|
|
1162
|
-
|
|
1163
|
-
|
|
1682
|
+
return await this.executeProtocolV2Phases({
|
|
1683
|
+
installSources,
|
|
1684
|
+
resourceSources,
|
|
1685
|
+
});
|
|
1686
|
+
} finally {
|
|
1687
|
+
await this.closeProtocolV2PreparedSources();
|
|
1164
1688
|
}
|
|
1689
|
+
}
|
|
1165
1690
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1691
|
+
private async executeProtocolV2TransferPhase({
|
|
1692
|
+
installSources,
|
|
1693
|
+
resourceSources,
|
|
1694
|
+
}: ProtocolV2ExecutionPhase) {
|
|
1695
|
+
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1696
|
+
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1697
|
+
for (const resource of resourceSources) {
|
|
1698
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1699
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1700
|
+
} else {
|
|
1701
|
+
resourcesToSync.push(resource);
|
|
1702
|
+
totalSize += resource.source.size;
|
|
1171
1703
|
}
|
|
1172
|
-
return;
|
|
1173
1704
|
}
|
|
1174
|
-
let transferredSize = processedSize;
|
|
1175
|
-
const transferStartTime = Date.now();
|
|
1176
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1177
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1178
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1179
|
-
transferredSize = bytes;
|
|
1180
|
-
};
|
|
1181
|
-
Log.log(
|
|
1182
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1183
|
-
);
|
|
1184
1705
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
processedSize,
|
|
1197
|
-
totalSize,
|
|
1198
|
-
onTransferredBytes,
|
|
1199
|
-
});
|
|
1200
|
-
transferredSize = processedSize;
|
|
1201
|
-
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1202
|
-
|
|
1203
|
-
stagedInstallTargets.push({
|
|
1204
|
-
...item,
|
|
1205
|
-
path: filePath,
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1706
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1707
|
+
let processedSize = 0;
|
|
1708
|
+
for (const resource of resourcesToSync) {
|
|
1709
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1710
|
+
source: resource.source,
|
|
1711
|
+
filePath: resource.devicePath,
|
|
1712
|
+
processedSize,
|
|
1713
|
+
totalSize,
|
|
1714
|
+
});
|
|
1715
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
1716
|
+
}
|
|
1208
1717
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1718
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
1719
|
+
for (const item of installSources) {
|
|
1720
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1721
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1722
|
+
source: item.source,
|
|
1723
|
+
filePath,
|
|
1724
|
+
processedSize,
|
|
1725
|
+
totalSize,
|
|
1726
|
+
});
|
|
1727
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1728
|
+
stagedInstallTargets.push({
|
|
1729
|
+
targetId: item.targetId,
|
|
1730
|
+
path: filePath,
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1212
1733
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
);
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1221
|
-
Log.warn(
|
|
1222
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1223
|
-
elapsedMs / 1000
|
|
1224
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1225
|
-
);
|
|
1226
|
-
throw error;
|
|
1734
|
+
if (totalSize > 0) {
|
|
1735
|
+
this.postProgressMessage(100, 'transferData');
|
|
1736
|
+
}
|
|
1737
|
+
if (stagedInstallTargets.length === 0) {
|
|
1738
|
+
return;
|
|
1227
1739
|
}
|
|
1228
1740
|
|
|
1229
1741
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1230
|
-
|
|
1231
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1742
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1232
1743
|
target_id: item.targetId,
|
|
1233
1744
|
path: item.path,
|
|
1234
1745
|
}));
|
|
1235
|
-
|
|
1236
|
-
await this.
|
|
1237
|
-
|
|
1746
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1747
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
private async protocolV2SourceUpdateProcess({
|
|
1751
|
+
source,
|
|
1752
|
+
filePath,
|
|
1753
|
+
processedSize,
|
|
1754
|
+
totalSize,
|
|
1755
|
+
}: {
|
|
1756
|
+
source: FirmwareByteSource;
|
|
1757
|
+
filePath: string;
|
|
1758
|
+
processedSize: number;
|
|
1759
|
+
totalSize: number;
|
|
1760
|
+
}) {
|
|
1761
|
+
let lastError: unknown;
|
|
1762
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1763
|
+
try {
|
|
1764
|
+
const transferStartedAt = Date.now();
|
|
1765
|
+
await writeFirmwareByteSource({
|
|
1766
|
+
source,
|
|
1767
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1768
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
1769
|
+
const chunkEnd = sourceOffset + length;
|
|
1770
|
+
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
1771
|
+
processedSize + sourceOffset,
|
|
1772
|
+
processedSize + chunkEnd,
|
|
1773
|
+
totalSize
|
|
1774
|
+
);
|
|
1775
|
+
const response = await this.fileWriteChunk(
|
|
1776
|
+
filePath,
|
|
1777
|
+
source.size,
|
|
1778
|
+
sourceOffset,
|
|
1779
|
+
data,
|
|
1780
|
+
first,
|
|
1781
|
+
deviceProgress
|
|
1782
|
+
);
|
|
1783
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
1784
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1785
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1786
|
+
throw ERRORS.TypedError(
|
|
1787
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1788
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const transferredBytes = processedSize + chunkEnd;
|
|
1792
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
1793
|
+
this.postProgressMessage(
|
|
1794
|
+
Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
|
|
1795
|
+
'transferData',
|
|
1796
|
+
{
|
|
1797
|
+
transferredBytes,
|
|
1798
|
+
totalBytes: totalSize,
|
|
1799
|
+
rateBytesPerSecond:
|
|
1800
|
+
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
1801
|
+
elapsedMs,
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
return length;
|
|
1805
|
+
},
|
|
1806
|
+
});
|
|
1807
|
+
return processedSize + source.size;
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
lastError = error;
|
|
1810
|
+
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1811
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
throw ERRORS.TypedError(
|
|
1816
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1817
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1818
|
+
);
|
|
1238
1819
|
}
|
|
1239
1820
|
|
|
1240
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1821
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1241
1822
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1242
1823
|
}
|
|
1243
1824
|
|
|
@@ -1256,7 +1837,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1256
1837
|
|
|
1257
1838
|
private assertProtocolV2TargetStatus(
|
|
1258
1839
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1259
|
-
expectedTargetIds: Set<number
|
|
1840
|
+
expectedTargetIds: Set<number>,
|
|
1841
|
+
expectedPaths = new Map<number, string>()
|
|
1260
1842
|
) {
|
|
1261
1843
|
Log.log(
|
|
1262
1844
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1287,17 +1869,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1287
1869
|
const matchingTargets = statusTargets.filter(target =>
|
|
1288
1870
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1289
1871
|
);
|
|
1872
|
+
const seenTargetIds = new Set<number>();
|
|
1873
|
+
for (const target of matchingTargets) {
|
|
1874
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1875
|
+
if (
|
|
1876
|
+
targetId === undefined ||
|
|
1877
|
+
seenTargetIds.has(targetId) ||
|
|
1878
|
+
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1879
|
+
) {
|
|
1880
|
+
throw ERRORS.TypedError(
|
|
1881
|
+
HardwareErrorCode.RuntimeError,
|
|
1882
|
+
`Protocol V2 install status conflicts with target ${target.target_id}`,
|
|
1883
|
+
{
|
|
1884
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
1885
|
+
}
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
seenTargetIds.add(targetId);
|
|
1889
|
+
}
|
|
1890
|
+
const completedTargets = matchingTargets.filter(target =>
|
|
1891
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
1892
|
+
);
|
|
1290
1893
|
const completedTargetIds = new Set<number>();
|
|
1291
|
-
|
|
1894
|
+
completedTargets.forEach(target => {
|
|
1292
1895
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1293
|
-
if (targetId !== undefined
|
|
1896
|
+
if (targetId !== undefined) {
|
|
1294
1897
|
completedTargetIds.add(targetId);
|
|
1295
1898
|
}
|
|
1296
1899
|
});
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1900
|
+
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1901
|
+
for (const target of completedTargets) {
|
|
1902
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1903
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1904
|
+
if (targetId === undefined) {
|
|
1905
|
+
throw ERRORS.TypedError(
|
|
1906
|
+
HardwareErrorCode.RuntimeError,
|
|
1907
|
+
'Protocol V2 completed target identity is invalid'
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1911
|
+
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1912
|
+
if (payloadVersion !== expectedVersion) {
|
|
1913
|
+
throw ERRORS.TypedError(
|
|
1914
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1915
|
+
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (payloadVersion !== undefined) {
|
|
1920
|
+
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1301
1923
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1302
1924
|
return true;
|
|
1303
1925
|
}
|
|
@@ -1335,7 +1957,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1335
1957
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1336
1958
|
targets: Array<{ target_id: number; path: string }>
|
|
1337
1959
|
) {
|
|
1960
|
+
this.protocolV2FinalStatusVerified = false;
|
|
1338
1961
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1962
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1339
1963
|
const startTime = Date.now();
|
|
1340
1964
|
let lastError: unknown;
|
|
1341
1965
|
let shouldReconnect = true;
|
|
@@ -1370,7 +1994,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1370
1994
|
);
|
|
1371
1995
|
const statusTargets = (statusResponse.message.records ??
|
|
1372
1996
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1373
|
-
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
1997
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
1998
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1374
1999
|
return;
|
|
1375
2000
|
}
|
|
1376
2001
|
|
|
@@ -1418,10 +2043,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1418
2043
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1419
2044
|
}
|
|
1420
2045
|
} catch (error) {
|
|
1421
|
-
if (
|
|
1422
|
-
error instanceof HardwareError &&
|
|
1423
|
-
error.errorCode === HardwareErrorCode.FirmwareError
|
|
1424
|
-
) {
|
|
2046
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
1425
2047
|
throw error;
|
|
1426
2048
|
}
|
|
1427
2049
|
// Only consecutive full status dumps can prove that a record is absent.
|
|
@@ -1461,7 +2083,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1461
2083
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1462
2084
|
throw error;
|
|
1463
2085
|
}
|
|
1464
|
-
if (error
|
|
2086
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2087
|
+
throw error;
|
|
2088
|
+
}
|
|
2089
|
+
if (
|
|
2090
|
+
error instanceof HardwareError &&
|
|
2091
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
2092
|
+
) {
|
|
1465
2093
|
throw error;
|
|
1466
2094
|
}
|
|
1467
2095
|
shouldReconnect = true;
|
|
@@ -1492,6 +2120,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1492
2120
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1493
2121
|
}
|
|
1494
2122
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
2123
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1495
2124
|
}
|
|
1496
2125
|
|
|
1497
2126
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1514,6 +2143,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1514
2143
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1515
2144
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1516
2145
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2146
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1517
2147
|
if (firmwareVersion === '0.0.0') {
|
|
1518
2148
|
Log.warn(
|
|
1519
2149
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1527,6 +2157,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1527
2157
|
};
|
|
1528
2158
|
}
|
|
1529
2159
|
|
|
2160
|
+
private async completeProtocolV2FinalVerification() {
|
|
2161
|
+
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
2162
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
2163
|
+
}
|
|
2164
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
2165
|
+
this.assertExpectedProtocolV2Versions();
|
|
2166
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
2167
|
+
DevicePool.resetState();
|
|
2168
|
+
return versions;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
1530
2171
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1531
2172
|
const startTime = Date.now();
|
|
1532
2173
|
let lastError: unknown;
|
|
@@ -1648,106 +2289,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1648
2289
|
}
|
|
1649
2290
|
|
|
1650
2291
|
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
1651
|
-
|
|
1652
|
-
// path。此时应继续轮询,只有明确读到不同序列号时才立即终止更新。
|
|
1653
|
-
return this.normalizeErrorMessage(error).includes(
|
|
1654
|
-
'Protocol V2 reconnect physical identity mismatch'
|
|
1655
|
-
);
|
|
2292
|
+
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1656
2293
|
}
|
|
1657
2294
|
|
|
1658
|
-
private async
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
throw ERRORS.TypedError(
|
|
1677
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1678
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
private async protocolV2WriteWholeFile({
|
|
1683
|
-
payload,
|
|
1684
|
-
filePath,
|
|
1685
|
-
processedSize,
|
|
1686
|
-
totalSize,
|
|
1687
|
-
onTransferredBytes,
|
|
1688
|
-
}: ProtocolV2FileTransferParams) {
|
|
1689
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1690
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1691
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1692
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1693
|
-
}
|
|
1694
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1695
|
-
};
|
|
1696
|
-
|
|
1697
|
-
try {
|
|
1698
|
-
await writeProtocolV2File({
|
|
1699
|
-
commands: this.device.getCommands(),
|
|
1700
|
-
path: filePath,
|
|
1701
|
-
data: payload,
|
|
1702
|
-
totalSize: payload.byteLength,
|
|
1703
|
-
chunkSize,
|
|
1704
|
-
overwrite: true,
|
|
1705
|
-
append: false,
|
|
1706
|
-
writeWithResponse: false,
|
|
1707
|
-
maxChunkRetries: 0,
|
|
1708
|
-
getUiPercentage: ({ offset, chunkLength }) =>
|
|
1709
|
-
getProtocolV2DeviceTransferProgress(
|
|
1710
|
-
(processedSize ?? 0) + offset,
|
|
1711
|
-
(processedSize ?? 0) + offset + chunkLength,
|
|
1712
|
-
totalSize ?? payload.byteLength
|
|
1713
|
-
),
|
|
1714
|
-
onProgress: progress => {
|
|
1715
|
-
const transferredBytes = (processedSize ?? 0) + progress.transferredBytes;
|
|
1716
|
-
onTransferredBytes?.(transferredBytes);
|
|
1717
|
-
this.postProgressMessage(getUploadProgress(progress.transferredBytes), 'transferData', {
|
|
1718
|
-
transferredBytes,
|
|
1719
|
-
totalBytes: totalSize ?? payload.byteLength,
|
|
1720
|
-
rateBytesPerSecond: progress.rateBytesPerSecond,
|
|
1721
|
-
elapsedMs: progress.elapsedMs,
|
|
1722
|
-
});
|
|
2295
|
+
private async fileWriteChunk(
|
|
2296
|
+
filePath: string,
|
|
2297
|
+
totalFileSize: number,
|
|
2298
|
+
offset: number,
|
|
2299
|
+
chunk: ArrayBuffer | Buffer,
|
|
2300
|
+
overwrite: boolean,
|
|
2301
|
+
progress: number | null
|
|
2302
|
+
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
2303
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2304
|
+
const writeRes = await typedCall(
|
|
2305
|
+
'FilesystemFileWrite',
|
|
2306
|
+
'FilesystemFile',
|
|
2307
|
+
{
|
|
2308
|
+
file: {
|
|
2309
|
+
path: filePath,
|
|
2310
|
+
offset,
|
|
2311
|
+
total_size: totalFileSize,
|
|
2312
|
+
data: chunk,
|
|
1723
2313
|
},
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2314
|
+
overwrite,
|
|
2315
|
+
append: false,
|
|
2316
|
+
ui_percentage: progress ?? undefined,
|
|
2317
|
+
},
|
|
2318
|
+
{ writeWithResponse: true }
|
|
2319
|
+
);
|
|
2320
|
+
if (writeRes.type !== 'FilesystemFile') {
|
|
2321
|
+
if ((writeRes as any).type === 'CallMethodError') {
|
|
2322
|
+
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
2323
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
2324
|
+
}
|
|
1732
2325
|
}
|
|
1733
|
-
throw error;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
private getProtocolV2FirmwareTransferTransport() {
|
|
1740
|
-
const env = DataManager.getSettings('env');
|
|
1741
|
-
if (env && DataManager.isBleConnect(env)) {
|
|
1742
|
-
return 'BLE';
|
|
1743
|
-
}
|
|
1744
|
-
if (
|
|
1745
|
-
env &&
|
|
1746
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1747
|
-
) {
|
|
1748
|
-
return 'WebUSB';
|
|
2326
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
|
|
1749
2327
|
}
|
|
1750
|
-
return
|
|
2328
|
+
return writeRes;
|
|
1751
2329
|
}
|
|
1752
2330
|
|
|
1753
2331
|
private async recoverProtocolV2FileTransfer() {
|