@onekeyfe/hd-core 1.2.0-alpha.115 → 1.2.0-alpha.117
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__/DeviceCommands.test.ts +2 -2
- package/__tests__/device-lifecycle-events.test.ts +1 -1
- package/__tests__/device-state-mapper.test.ts +4 -4
- package/__tests__/device-utils.test.ts +1 -1
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +36 -89
- package/__tests__/get-device-state.test.ts +8 -8
- package/__tests__/protocol-v2-resources.test.ts +20 -0
- package/__tests__/protocol-v2.test.ts +411 -246
- package/dist/api/FirmwareUpdateV4.d.ts +0 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/index.d.ts +5 -20
- package/dist/index.js +191 -257
- package/dist/protocols/protocol-v2/features.d.ts +4 -4
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/types/settings.d.ts +4 -19
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +170 -243
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/data/messages/messages-protocol-v2.json +31 -14
- package/src/device/DeviceStateMapper.ts +15 -15
- package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
- package/src/protocols/protocol-v2/features.ts +6 -6
- package/src/protocols/protocol-v2/resources.ts +10 -49
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +1 -0
- package/src/types/settings.ts +4 -19
|
@@ -28,7 +28,6 @@ import { DevicePool } from '../device/DevicePool';
|
|
|
28
28
|
import {
|
|
29
29
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
30
30
|
ProtocolV2FirmwareTargetType,
|
|
31
|
-
isLegacyProtocolV2ProtocolInfo,
|
|
32
31
|
} from '../protocols/protocol-v2';
|
|
33
32
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
34
33
|
import {
|
|
@@ -70,6 +69,7 @@ import type {
|
|
|
70
69
|
Features,
|
|
71
70
|
IFirmwareReleaseInfo,
|
|
72
71
|
IProtocolV2FirmwareComponent,
|
|
72
|
+
IProtocolV2ResourceManifestFile,
|
|
73
73
|
IVersionArray,
|
|
74
74
|
} from '../types';
|
|
75
75
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
@@ -89,8 +89,8 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
|
89
89
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
90
90
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
91
91
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
92
|
-
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
93
92
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
93
|
+
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
94
94
|
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
95
95
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
96
96
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
@@ -113,6 +113,13 @@ const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
|
113
113
|
const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
|
|
114
114
|
const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
115
115
|
|
|
116
|
+
const getProtocolV2LocalResourceArchivePath = (entryName: string) => {
|
|
117
|
+
const match = entryName.match(
|
|
118
|
+
/(?:^|\/)((?:bundles\/|loaders\/(?:bootloader|rom)\/).+\.okpkg)$/iu
|
|
119
|
+
);
|
|
120
|
+
return match?.[1];
|
|
121
|
+
};
|
|
122
|
+
|
|
116
123
|
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
117
124
|
|
|
118
125
|
const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
|
|
@@ -181,14 +188,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
181
188
|
totalBytes: number
|
|
182
189
|
) => {
|
|
183
190
|
if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
|
|
184
|
-
return
|
|
191
|
+
return 0;
|
|
185
192
|
}
|
|
186
193
|
if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
|
|
187
194
|
return 0;
|
|
188
195
|
}
|
|
189
|
-
if (bytesAfterChunk >= totalBytes) {
|
|
190
|
-
return 100;
|
|
191
|
-
}
|
|
192
196
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
193
197
|
};
|
|
194
198
|
|
|
@@ -199,8 +203,6 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
|
199
203
|
path?: string;
|
|
200
204
|
};
|
|
201
205
|
|
|
202
|
-
type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
203
|
-
|
|
204
206
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
205
207
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
206
208
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
@@ -377,31 +379,6 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
|
|
|
377
379
|
);
|
|
378
380
|
};
|
|
379
381
|
|
|
380
|
-
const PROTOCOL_V2_BLE_INSTALL_INTERRUPTION_ERROR_CODES = new Set<number>([
|
|
381
|
-
HardwareErrorCode.BleConnectedError,
|
|
382
|
-
HardwareErrorCode.BleCharacteristicNotifyError,
|
|
383
|
-
HardwareErrorCode.BleForceCleanRunPromise,
|
|
384
|
-
HardwareErrorCode.BleDeviceDisconnected,
|
|
385
|
-
]);
|
|
386
|
-
|
|
387
|
-
const isProtocolV2BleInstallInterruptionError = (error: unknown) => {
|
|
388
|
-
if (
|
|
389
|
-
error instanceof HardwareError &&
|
|
390
|
-
PROTOCOL_V2_BLE_INSTALL_INTERRUPTION_ERROR_CODES.has(error.errorCode)
|
|
391
|
-
) {
|
|
392
|
-
return true;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
396
|
-
const compactMessage = message.replace(/\s+/gu, '');
|
|
397
|
-
return (
|
|
398
|
-
/react native ble transport (?:released|disconnected)/u.test(message) ||
|
|
399
|
-
(compactMessage.includes('rxerrorerror6') &&
|
|
400
|
-
(compactMessage.includes('multiplatformbleadapter') ||
|
|
401
|
-
compactMessage.includes('multipalformebleadapter')))
|
|
402
|
-
);
|
|
403
|
-
};
|
|
404
|
-
|
|
405
382
|
const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
406
383
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
407
384
|
return (
|
|
@@ -411,15 +388,6 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
411
388
|
);
|
|
412
389
|
};
|
|
413
390
|
|
|
414
|
-
const isProtocolV2FirmwareUpdateEndpointUnavailable = (error: unknown) => {
|
|
415
|
-
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
416
|
-
return (
|
|
417
|
-
message.includes('handler not registered') ||
|
|
418
|
-
message.includes('message handler not found') ||
|
|
419
|
-
message.includes('unsupported message')
|
|
420
|
-
);
|
|
421
|
-
};
|
|
422
|
-
|
|
423
391
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
424
392
|
error instanceof HardwareError &&
|
|
425
393
|
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
@@ -596,7 +564,7 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
596
564
|
*
|
|
597
565
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
598
566
|
* - upload uses FilesystemFileWrite
|
|
599
|
-
* - install uses DeviceFirmwareUpdateRequest
|
|
567
|
+
* - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
|
|
600
568
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
601
569
|
*/
|
|
602
570
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -614,8 +582,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
614
582
|
|
|
615
583
|
private protocolV2ExecutionInLoader = false;
|
|
616
584
|
|
|
617
|
-
private protocolV2LegacyDirectUpdate = false;
|
|
618
|
-
|
|
619
585
|
private protocolV2BootResourceStagingSafe = false;
|
|
620
586
|
|
|
621
587
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
@@ -624,8 +590,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
624
590
|
|
|
625
591
|
private protocolV2FinalStatusVerified = false;
|
|
626
592
|
|
|
627
|
-
private protocolV2InstallAckReceived = false;
|
|
628
|
-
|
|
629
593
|
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
630
594
|
|
|
631
595
|
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
@@ -1293,92 +1257,86 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1293
1257
|
);
|
|
1294
1258
|
}
|
|
1295
1259
|
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1296
|
-
if (entries.length === 0
|
|
1260
|
+
if (entries.length === 0) {
|
|
1297
1261
|
throw ERRORS.TypedError(
|
|
1298
1262
|
HardwareErrorCode.RuntimeError,
|
|
1299
1263
|
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1300
1264
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1301
1265
|
);
|
|
1302
1266
|
}
|
|
1303
|
-
|
|
1304
|
-
let
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
declaredUncompressedSize += sizes.uncompressedSize;
|
|
1309
|
-
const entryLimit =
|
|
1310
|
-
entry.name === 'manifest.json'
|
|
1311
|
-
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1312
|
-
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1267
|
+
const manifestEntry = entries.find(entry => entry.name.split('/').pop() === 'manifest.json');
|
|
1268
|
+
let manifestBinary: ArrayBuffer | undefined;
|
|
1269
|
+
let manifestDirectory = '';
|
|
1270
|
+
let selectedFiles: IProtocolV2ResourceManifestFile[];
|
|
1271
|
+
if (manifestEntry) {
|
|
1313
1272
|
if (
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
declaredUncompressedSize >
|
|
1317
|
-
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1273
|
+
getProtocolV2ZipEntrySizes(manifestEntry).uncompressedSize >
|
|
1274
|
+
PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1318
1275
|
) {
|
|
1319
1276
|
throw ERRORS.TypedError(
|
|
1320
1277
|
HardwareErrorCode.RuntimeError,
|
|
1321
|
-
'Protocol V2 local resource
|
|
1278
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1322
1279
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1323
1280
|
);
|
|
1324
1281
|
}
|
|
1282
|
+
manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1283
|
+
if (
|
|
1284
|
+
manifestBinary.byteLength <= 0 ||
|
|
1285
|
+
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1286
|
+
) {
|
|
1287
|
+
throw ERRORS.TypedError(
|
|
1288
|
+
HardwareErrorCode.RuntimeError,
|
|
1289
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1290
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
let manifestValue: unknown;
|
|
1294
|
+
try {
|
|
1295
|
+
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
throw ERRORS.TypedError(
|
|
1298
|
+
HardwareErrorCode.RuntimeError,
|
|
1299
|
+
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1300
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1304
|
+
manifest: parseProtocolV2ResourceManifest(manifestValue),
|
|
1305
|
+
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1306
|
+
});
|
|
1307
|
+
manifestDirectory = manifestEntry.name.slice(0, -'manifest.json'.length);
|
|
1308
|
+
} else {
|
|
1309
|
+
selectedFiles = entries.flatMap(entry => {
|
|
1310
|
+
const archivePath = getProtocolV2LocalResourceArchivePath(entry.name);
|
|
1311
|
+
if (!archivePath) return [];
|
|
1312
|
+
return [
|
|
1313
|
+
{
|
|
1314
|
+
archive_path: archivePath,
|
|
1315
|
+
original_name: archivePath.split('/').pop() ?? archivePath,
|
|
1316
|
+
device_path: `vol0:/${archivePath}`,
|
|
1317
|
+
size: getProtocolV2ZipEntrySizes(entry).uncompressedSize,
|
|
1318
|
+
sha256: '',
|
|
1319
|
+
},
|
|
1320
|
+
];
|
|
1321
|
+
});
|
|
1325
1322
|
}
|
|
1326
|
-
|
|
1327
|
-
if (!manifestEntry) {
|
|
1328
|
-
throw ERRORS.TypedError(
|
|
1329
|
-
HardwareErrorCode.RuntimeError,
|
|
1330
|
-
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1331
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
|
-
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1335
|
-
if (
|
|
1336
|
-
manifestBinary.byteLength <= 0 ||
|
|
1337
|
-
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1338
|
-
) {
|
|
1339
|
-
throw ERRORS.TypedError(
|
|
1340
|
-
HardwareErrorCode.RuntimeError,
|
|
1341
|
-
'Protocol V2 local resource manifest size is invalid',
|
|
1342
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1343
|
-
);
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
let manifestValue: unknown;
|
|
1347
|
-
try {
|
|
1348
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1349
|
-
} catch (error) {
|
|
1350
|
-
throw ERRORS.TypedError(
|
|
1351
|
-
HardwareErrorCode.RuntimeError,
|
|
1352
|
-
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1353
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1354
|
-
);
|
|
1355
|
-
}
|
|
1356
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1357
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1358
|
-
manifest,
|
|
1359
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1360
|
-
});
|
|
1361
|
-
const expectedEntryNames = new Set([
|
|
1362
|
-
'manifest.json',
|
|
1363
|
-
...selectedFiles.map(file => file.archive_path),
|
|
1364
|
-
]);
|
|
1365
|
-
if (
|
|
1366
|
-
entries.length !== expectedEntryNames.size ||
|
|
1367
|
-
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1368
|
-
) {
|
|
1323
|
+
if (selectedFiles.length === 0 || selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1369
1324
|
throw ERRORS.TypedError(
|
|
1370
1325
|
HardwareErrorCode.RuntimeError,
|
|
1371
|
-
'Protocol V2 local resource ZIP
|
|
1326
|
+
'Protocol V2 local resource ZIP has no resource packages',
|
|
1372
1327
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1373
1328
|
);
|
|
1374
1329
|
}
|
|
1375
1330
|
|
|
1376
1331
|
let totalSize = 0;
|
|
1377
|
-
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1378
|
-
|
|
1379
|
-
];
|
|
1332
|
+
const materializedEntries: FirmwareMemoryArtifactEntry[] = [];
|
|
1333
|
+
const normalizedFiles: IProtocolV2ResourceManifestFile[] = [];
|
|
1380
1334
|
for (const file of selectedFiles) {
|
|
1381
|
-
const entry =
|
|
1335
|
+
const entry = manifestEntry
|
|
1336
|
+
? zip.file(`${manifestDirectory}${file.archive_path}`)
|
|
1337
|
+
: entries.find(
|
|
1338
|
+
candidate => getProtocolV2LocalResourceArchivePath(candidate.name) === file.archive_path
|
|
1339
|
+
);
|
|
1382
1340
|
if (!entry) {
|
|
1383
1341
|
throw ERRORS.TypedError(
|
|
1384
1342
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1397,15 +1355,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1397
1355
|
}
|
|
1398
1356
|
const fileBinary = await entry.async('arraybuffer');
|
|
1399
1357
|
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1400
|
-
if (
|
|
1358
|
+
if (
|
|
1359
|
+
fileBinary.byteLength !== file.size ||
|
|
1360
|
+
(file.sha256 && digest !== file.sha256.toLowerCase())
|
|
1361
|
+
) {
|
|
1401
1362
|
throw ERRORS.TypedError(
|
|
1402
1363
|
HardwareErrorCode.RuntimeError,
|
|
1403
1364
|
`Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
|
|
1404
1365
|
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1405
1366
|
);
|
|
1406
1367
|
}
|
|
1368
|
+
normalizedFiles.push({ ...file, sha256: digest });
|
|
1407
1369
|
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1408
1370
|
}
|
|
1371
|
+
manifestBinary ??= new TextEncoder().encode(JSON.stringify({ files: normalizedFiles })).buffer;
|
|
1372
|
+
materializedEntries.unshift({ entryName: 'manifest.json', binary: manifestBinary });
|
|
1409
1373
|
return { binary, materializedEntries };
|
|
1410
1374
|
}
|
|
1411
1375
|
|
|
@@ -1471,29 +1435,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1471
1435
|
);
|
|
1472
1436
|
}
|
|
1473
1437
|
|
|
1474
|
-
//
|
|
1438
|
+
// The verified archive bytes are authoritative. Host materialization is an
|
|
1439
|
+
// implementation detail and may preserve wrapper paths or extra metadata files.
|
|
1475
1440
|
const verifiedArchive = await this.prepareProtocolV2LocalResourceArchive(archiveBinary);
|
|
1476
|
-
const entries = archiveArtifact.materializedEntries ?? [];
|
|
1477
|
-
const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
|
|
1478
|
-
if (
|
|
1479
|
-
entries.length !== verifiedArchive.materializedEntries.length ||
|
|
1480
|
-
verifiedArchive.materializedEntries.some(entry => {
|
|
1481
|
-
const preparedEntry = entriesByName.get(entry.entryName);
|
|
1482
|
-
const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
|
|
1483
|
-
return (
|
|
1484
|
-
!preparedEntry ||
|
|
1485
|
-
preparedEntry.artifact.size !== entry.binary.byteLength ||
|
|
1486
|
-
preparedEntry.artifact.sha256.toLowerCase() !== digest
|
|
1487
|
-
);
|
|
1488
|
-
})
|
|
1489
|
-
) {
|
|
1490
|
-
throw ERRORS.TypedError(
|
|
1491
|
-
HardwareErrorCode.RuntimeError,
|
|
1492
|
-
'Protocol V2 prepared resource entries do not match the approved archive',
|
|
1493
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1494
|
-
);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
1441
|
const verifiedEntriesByName = new Map(
|
|
1498
1442
|
verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
|
|
1499
1443
|
);
|
|
@@ -1929,16 +1873,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1929
1873
|
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
1930
1874
|
);
|
|
1931
1875
|
}
|
|
1932
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
|
|
1933
|
-
if (expectedPayloadHash) {
|
|
1934
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1935
|
-
if (!header || header.payloadHash !== expectedPayloadHash) {
|
|
1936
|
-
throw ERRORS.TypedError(
|
|
1937
|
-
HardwareErrorCode.RuntimeError,
|
|
1938
|
-
`Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
|
|
1939
|
-
);
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
1876
|
return {
|
|
1943
1877
|
...target,
|
|
1944
1878
|
binary,
|
|
@@ -2168,11 +2102,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2168
2102
|
return this.device.features?.mode === 'romloader';
|
|
2169
2103
|
}
|
|
2170
2104
|
|
|
2171
|
-
private isLegacyProtocolV2Runtime() {
|
|
2172
|
-
const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
|
|
2173
|
-
return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
2105
|
private async rebootProtocolV2ToBootloader() {
|
|
2177
2106
|
try {
|
|
2178
2107
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
@@ -2192,18 +2121,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2192
2121
|
}
|
|
2193
2122
|
|
|
2194
2123
|
async enterProtocolV2BootloaderMode() {
|
|
2195
|
-
this.protocolV2LegacyDirectUpdate = false;
|
|
2196
2124
|
// romloader is the first update environment and forwards targets to bootloader.
|
|
2197
2125
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2198
2126
|
if (this.isProtocolV2RomloaderMode()) {
|
|
2199
2127
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2200
|
-
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2201
2128
|
this.protocolV2ExecutionInLoader = true;
|
|
2202
2129
|
return false;
|
|
2203
2130
|
}
|
|
2204
2131
|
if (this.isProtocolV2BootloaderMode()) {
|
|
2205
2132
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2206
|
-
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2207
2133
|
this.protocolV2ExecutionInLoader = true;
|
|
2208
2134
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2209
2135
|
return false;
|
|
@@ -2450,7 +2376,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2450
2376
|
path: item.path,
|
|
2451
2377
|
}));
|
|
2452
2378
|
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2453
|
-
await
|
|
2379
|
+
await wait(PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY);
|
|
2380
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
2454
2381
|
}
|
|
2455
2382
|
|
|
2456
2383
|
private async protocolV2SourceUpdateProcess({
|
|
@@ -2702,7 +2629,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2702
2629
|
}
|
|
2703
2630
|
|
|
2704
2631
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2705
|
-
targets: Array<{ target_id: number; path: string }
|
|
2632
|
+
targets: Array<{ target_id: number; path: string }>,
|
|
2633
|
+
requireCurrentInstallStatus = false
|
|
2706
2634
|
) {
|
|
2707
2635
|
this.protocolV2FinalStatusVerified = false;
|
|
2708
2636
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
@@ -2714,7 +2642,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2714
2642
|
let missingTargetStatusSince: number | undefined;
|
|
2715
2643
|
let missingTargetStatusKey: string | undefined;
|
|
2716
2644
|
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2717
|
-
let installEvidenceObserved =
|
|
2645
|
+
let installEvidenceObserved = false;
|
|
2646
|
+
let currentInstallStatusObserved = false;
|
|
2718
2647
|
const resetMissingTargetStatusGrace = () => {
|
|
2719
2648
|
missingTargetStatusSince = undefined;
|
|
2720
2649
|
missingTargetStatusKey = undefined;
|
|
@@ -2734,7 +2663,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2734
2663
|
try {
|
|
2735
2664
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2736
2665
|
'DeviceFirmwareUpdateStatusGet',
|
|
2737
|
-
'DeviceFirmwareUpdateStatus',
|
|
2666
|
+
['DeviceFirmwareUpdateStatus', 'Success'],
|
|
2738
2667
|
{
|
|
2739
2668
|
fields: {
|
|
2740
2669
|
status: true,
|
|
@@ -2744,22 +2673,64 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2744
2673
|
},
|
|
2745
2674
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2746
2675
|
);
|
|
2747
|
-
|
|
2748
|
-
|
|
2676
|
+
if (statusResponse.type === 'Success') {
|
|
2677
|
+
installEvidenceObserved = true;
|
|
2678
|
+
resetMissingTargetStatusGrace();
|
|
2679
|
+
lastError = new Error(
|
|
2680
|
+
'Protocol V2 firmware install acknowledged; waiting for target status'
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
const statusTargets =
|
|
2684
|
+
statusResponse.type === 'DeviceFirmwareUpdateStatus'
|
|
2685
|
+
? ((statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[])
|
|
2686
|
+
: [];
|
|
2749
2687
|
if (
|
|
2750
2688
|
statusTargets.some(target => {
|
|
2751
2689
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2752
|
-
return
|
|
2690
|
+
return (
|
|
2691
|
+
targetId !== undefined &&
|
|
2692
|
+
expectedTargetIds.has(targetId) &&
|
|
2693
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
2694
|
+
);
|
|
2753
2695
|
})
|
|
2696
|
+
) {
|
|
2697
|
+
currentInstallStatusObserved = true;
|
|
2698
|
+
}
|
|
2699
|
+
const hasMatchingTargetStatus = statusTargets.some(target => {
|
|
2700
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2701
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2702
|
+
});
|
|
2703
|
+
if (
|
|
2704
|
+
hasMatchingTargetStatus &&
|
|
2705
|
+
(!requireCurrentInstallStatus || currentInstallStatusObserved)
|
|
2754
2706
|
) {
|
|
2755
2707
|
installEvidenceObserved = true;
|
|
2756
2708
|
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2757
2709
|
}
|
|
2758
|
-
|
|
2710
|
+
const matchingStatusTargets = statusTargets.filter(target => {
|
|
2711
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2712
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2713
|
+
});
|
|
2714
|
+
const shouldVerifyTargetCompletion =
|
|
2715
|
+
!requireCurrentInstallStatus || currentInstallStatusObserved;
|
|
2716
|
+
if (
|
|
2717
|
+
shouldVerifyTargetCompletion &&
|
|
2718
|
+
this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)
|
|
2719
|
+
) {
|
|
2759
2720
|
this.protocolV2FinalStatusVerified = true;
|
|
2760
2721
|
return;
|
|
2761
2722
|
}
|
|
2762
2723
|
|
|
2724
|
+
if (
|
|
2725
|
+
requireCurrentInstallStatus &&
|
|
2726
|
+
!currentInstallStatusObserved &&
|
|
2727
|
+
matchingStatusTargets.length > 0
|
|
2728
|
+
) {
|
|
2729
|
+
lastError = new Error(
|
|
2730
|
+
'Protocol V2 firmware status is stale; waiting for the current install to start'
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2763
2734
|
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2764
2735
|
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2765
2736
|
if (
|
|
@@ -2775,36 +2746,45 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2775
2746
|
}
|
|
2776
2747
|
}
|
|
2777
2748
|
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
const now = Date.now();
|
|
2784
|
-
const missingKey = missingTargetIds.join(',');
|
|
2785
|
-
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2786
|
-
missingTargetStatusSince = now;
|
|
2787
|
-
missingTargetStatusKey = missingKey;
|
|
2788
|
-
} else if (
|
|
2789
|
-
now - missingTargetStatusSince >=
|
|
2790
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2791
|
-
) {
|
|
2792
|
-
const reportedTargetIds = statusTargets
|
|
2793
|
-
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2794
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2795
|
-
throw ERRORS.TypedError(
|
|
2796
|
-
HardwareErrorCode.FirmwareError,
|
|
2797
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2798
|
-
','
|
|
2799
|
-
)}`
|
|
2749
|
+
if (statusTargets.length === 0) {
|
|
2750
|
+
resetMissingTargetStatusGrace();
|
|
2751
|
+
if (statusResponse.type !== 'Success') {
|
|
2752
|
+
lastError = new Error(
|
|
2753
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2800
2754
|
);
|
|
2801
2755
|
}
|
|
2802
|
-
lastError = new Error(
|
|
2803
|
-
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2804
|
-
);
|
|
2805
2756
|
} else {
|
|
2806
|
-
|
|
2807
|
-
|
|
2757
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2758
|
+
statusTargets,
|
|
2759
|
+
expectedTargetIds
|
|
2760
|
+
);
|
|
2761
|
+
if (missingTargetIds.length > 0) {
|
|
2762
|
+
const now = Date.now();
|
|
2763
|
+
const missingKey = missingTargetIds.join(',');
|
|
2764
|
+
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2765
|
+
missingTargetStatusSince = now;
|
|
2766
|
+
missingTargetStatusKey = missingKey;
|
|
2767
|
+
} else if (
|
|
2768
|
+
now - missingTargetStatusSince >=
|
|
2769
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2770
|
+
) {
|
|
2771
|
+
const reportedTargetIds = statusTargets
|
|
2772
|
+
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2773
|
+
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2774
|
+
throw ERRORS.TypedError(
|
|
2775
|
+
HardwareErrorCode.FirmwareError,
|
|
2776
|
+
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2777
|
+
','
|
|
2778
|
+
)}`
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
lastError = new Error(
|
|
2782
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2783
|
+
);
|
|
2784
|
+
} else {
|
|
2785
|
+
resetMissingTargetStatusGrace();
|
|
2786
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2787
|
+
}
|
|
2808
2788
|
}
|
|
2809
2789
|
} catch (error) {
|
|
2810
2790
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
@@ -3148,65 +3128,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3148
3128
|
}: {
|
|
3149
3129
|
targets: Array<{ target_id: number; path: string }>;
|
|
3150
3130
|
}) {
|
|
3151
|
-
this.protocolV2InstallAckReceived = false;
|
|
3152
3131
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3153
|
-
const
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
.typedCall(
|
|
3157
|
-
'DeviceFirmwareUpdateRequest',
|
|
3158
|
-
'Success',
|
|
3159
|
-
{ targets },
|
|
3160
|
-
{ timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
|
|
3161
|
-
);
|
|
3162
|
-
let response: ProtocolV2FirmwareUpdateStartResponse | undefined;
|
|
3163
|
-
try {
|
|
3164
|
-
response = await startUpdate();
|
|
3165
|
-
this.protocolV2InstallAckReceived = true;
|
|
3166
|
-
} catch (error) {
|
|
3167
|
-
if (this.isBleReconnect() && isProtocolV2BleInstallInterruptionError(error)) {
|
|
3168
|
-
this.throwIfAborted();
|
|
3169
|
-
// Installation can reboot the device before the Success response reaches
|
|
3170
|
-
// the host. The request has side effects, so do not replay it; reconnect
|
|
3171
|
-
// and let status polling determine whether installation was accepted.
|
|
3172
|
-
Log.log(
|
|
3173
|
-
'[FirmwareUpdateV4] install request interrupted by device reboot; continue status polling: ',
|
|
3174
|
-
error
|
|
3175
|
-
);
|
|
3176
|
-
} else if (
|
|
3177
|
-
this.protocolV2LegacyDirectUpdate &&
|
|
3178
|
-
isProtocolV2FirmwareUpdateEndpointUnavailable(error)
|
|
3179
|
-
) {
|
|
3180
|
-
// Both legacy loaders register this request. A missing handler therefore identifies
|
|
3181
|
-
// a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
|
|
3182
|
-
this.protocolV2LegacyDirectUpdate = false;
|
|
3183
|
-
Log.debug(
|
|
3184
|
-
'[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
|
|
3185
|
-
);
|
|
3186
|
-
await this.rebootProtocolV2ToBootloader();
|
|
3187
|
-
try {
|
|
3188
|
-
response = await startUpdate();
|
|
3189
|
-
this.protocolV2InstallAckReceived = true;
|
|
3190
|
-
} catch (retryError) {
|
|
3191
|
-
if (!(this.isBleReconnect() && isProtocolV2BleInstallInterruptionError(retryError))) {
|
|
3192
|
-
throw retryError;
|
|
3193
|
-
}
|
|
3194
|
-
this.throwIfAborted();
|
|
3195
|
-
Log.log(
|
|
3196
|
-
'[FirmwareUpdateV4] install request interrupted after legacy reboot; continue status polling: ',
|
|
3197
|
-
retryError
|
|
3198
|
-
);
|
|
3199
|
-
}
|
|
3200
|
-
} else {
|
|
3201
|
-
throw error;
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
this.protocolV2LegacyDirectUpdate = false;
|
|
3205
|
-
// A Success ACK or an install-time disconnect both end confirmation. In the
|
|
3206
|
-
// latter case only status polling may establish the final outcome.
|
|
3132
|
+
const commands = this.device.getCommands();
|
|
3133
|
+
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
3134
|
+
await commands.call('DeviceFirmwareUpdateRequest', {}, { returnAfterWrite: true });
|
|
3207
3135
|
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
|
|
3208
3136
|
this.postProgressMessage(0, 'installingFirmware');
|
|
3209
|
-
return response;
|
|
3210
3137
|
}
|
|
3211
3138
|
|
|
3212
3139
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
@@ -25,7 +25,7 @@ export default class DeviceFactoryInfoSet extends BaseMethod<DeviceFactoryInfoSe
|
|
|
25
25
|
info: {
|
|
26
26
|
version: this.params.version,
|
|
27
27
|
serial_number: this.params.serial_number,
|
|
28
|
-
|
|
28
|
+
factory_burn_in_completed: this.params.burn_in_completed,
|
|
29
29
|
factory_test_completed: this.params.factory_test_completed,
|
|
30
30
|
manufacture_time: this.params.manufacture_time,
|
|
31
31
|
},
|