@onekeyfe/hd-core 1.2.0-alpha.112 → 1.2.0-alpha.113

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.
Files changed (35) hide show
  1. package/__tests__/DeviceCommands.test.ts +2 -2
  2. package/__tests__/device-lifecycle-events.test.ts +1 -1
  3. package/__tests__/device-state-mapper.test.ts +4 -4
  4. package/__tests__/device-utils.test.ts +1 -1
  5. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  6. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +89 -36
  7. package/__tests__/get-device-state.test.ts +8 -8
  8. package/__tests__/protocol-v2-resources.test.ts +0 -20
  9. package/__tests__/protocol-v2.test.ts +240 -219
  10. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts +3 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  14. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  15. package/dist/index.d.ts +20 -5
  16. package/dist/index.js +230 -140
  17. package/dist/protocols/protocol-v2/features.d.ts +4 -4
  18. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  19. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  20. package/dist/types/settings.d.ts +19 -4
  21. package/dist/types/settings.d.ts.map +1 -1
  22. package/dist/utils/patch.d.ts +1 -1
  23. package/dist/utils/patch.d.ts.map +1 -1
  24. package/package.json +4 -4
  25. package/src/api/FirmwareUpdateV3.ts +29 -19
  26. package/src/api/FirmwareUpdateV4.ts +178 -85
  27. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  28. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  29. package/src/data/messages/messages-protocol-v2.json +14 -31
  30. package/src/device/DeviceStateMapper.ts +15 -15
  31. package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
  32. package/src/protocols/protocol-v2/features.ts +6 -6
  33. package/src/protocols/protocol-v2/resources.ts +49 -10
  34. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -1
  35. package/src/types/settings.ts +19 -4
@@ -28,6 +28,7 @@ import { DevicePool } from '../device/DevicePool';
28
28
  import {
29
29
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
30
30
  ProtocolV2FirmwareTargetType,
31
+ isLegacyProtocolV2ProtocolInfo,
31
32
  } from '../protocols/protocol-v2';
32
33
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
33
34
  import {
@@ -69,7 +70,6 @@ import type {
69
70
  Features,
70
71
  IFirmwareReleaseInfo,
71
72
  IProtocolV2FirmwareComponent,
72
- IProtocolV2ResourceManifestFile,
73
73
  IVersionArray,
74
74
  } from '../types';
75
75
  import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
@@ -89,6 +89,7 @@ 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;
92
93
  const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
93
94
  const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
94
95
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
@@ -112,13 +113,6 @@ const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
112
113
  const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
113
114
  const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
114
115
 
115
- const getProtocolV2LocalResourceArchivePath = (entryName: string) => {
116
- const match = entryName.match(
117
- /(?:^|\/)((?:bundles\/|loaders\/(?:bootloader|rom)\/).+\.okpkg)$/iu
118
- );
119
- return match?.[1];
120
- };
121
-
122
116
  const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
123
117
 
124
118
  const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
@@ -187,11 +181,14 @@ const getProtocolV2DeviceTransferProgress = (
187
181
  totalBytes: number
188
182
  ) => {
189
183
  if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
190
- return 0;
184
+ return 100;
191
185
  }
192
186
  if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
193
187
  return 0;
194
188
  }
189
+ if (bytesAfterChunk >= totalBytes) {
190
+ return 100;
191
+ }
195
192
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
196
193
  };
197
194
 
@@ -202,6 +199,8 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
202
199
  path?: string;
203
200
  };
204
201
 
202
+ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
203
+
205
204
  type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
206
205
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
207
206
  kind: ProtocolV2RemoteComponentTarget['kind'];
@@ -378,6 +377,31 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
378
377
  );
379
378
  };
380
379
 
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
+
381
405
  const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
382
406
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
383
407
  return (
@@ -387,6 +411,15 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
387
411
  );
388
412
  };
389
413
 
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
+
390
423
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
391
424
  error instanceof HardwareError &&
392
425
  (error.errorCode === HardwareErrorCode.FirmwareError ||
@@ -563,7 +596,7 @@ export const assertProtocolV2ReconnectIdentity = (
563
596
  *
564
597
  * It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
565
598
  * - upload uses FilesystemFileWrite
566
- * - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
599
+ * - install uses DeviceFirmwareUpdateRequest
567
600
  * - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
568
601
  */
569
602
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
@@ -581,6 +614,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
581
614
 
582
615
  private protocolV2ExecutionInLoader = false;
583
616
 
617
+ private protocolV2LegacyDirectUpdate = false;
618
+
584
619
  private protocolV2BootResourceStagingSafe = false;
585
620
 
586
621
  private protocolV2CompletedTargetVersions = new Map<number, number>();
@@ -589,6 +624,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
589
624
 
590
625
  private protocolV2FinalStatusVerified = false;
591
626
 
627
+ private protocolV2InstallAckReceived = false;
628
+
592
629
  private protocolV2InstallBaselineVersions = new Map<number, string>();
593
630
 
594
631
  private protocolV2LastRuntimeProbeFeatures?: Features;
@@ -1256,86 +1293,92 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1256
1293
  );
1257
1294
  }
1258
1295
  const entries = zipEntries.filter(entry => !entry.dir);
1259
- if (entries.length === 0) {
1296
+ if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
1260
1297
  throw ERRORS.TypedError(
1261
1298
  HardwareErrorCode.RuntimeError,
1262
1299
  'Protocol V2 local resource ZIP entry set is invalid',
1263
1300
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1264
1301
  );
1265
1302
  }
1266
- const manifestEntry = entries.find(entry => entry.name.split('/').pop() === 'manifest.json');
1267
- let manifestBinary: ArrayBuffer | undefined;
1268
- let manifestDirectory = '';
1269
- let selectedFiles: IProtocolV2ResourceManifestFile[];
1270
- if (manifestEntry) {
1303
+ let declaredUncompressedSize = 0;
1304
+ let declaredCompressedSize = 0;
1305
+ for (const entry of entries) {
1306
+ const sizes = getProtocolV2ZipEntrySizes(entry);
1307
+ declaredCompressedSize += sizes.compressedSize;
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;
1271
1313
  if (
1272
- getProtocolV2ZipEntrySizes(manifestEntry).uncompressedSize >
1273
- PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1314
+ sizes.uncompressedSize > entryLimit ||
1315
+ declaredCompressedSize > binary.byteLength ||
1316
+ declaredUncompressedSize >
1317
+ PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1274
1318
  ) {
1275
1319
  throw ERRORS.TypedError(
1276
1320
  HardwareErrorCode.RuntimeError,
1277
- 'Protocol V2 local resource manifest size is invalid',
1321
+ 'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
1278
1322
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1279
1323
  );
1280
1324
  }
1281
- manifestBinary = await manifestEntry.async('arraybuffer');
1282
- if (
1283
- manifestBinary.byteLength <= 0 ||
1284
- manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1285
- ) {
1286
- throw ERRORS.TypedError(
1287
- HardwareErrorCode.RuntimeError,
1288
- 'Protocol V2 local resource manifest size is invalid',
1289
- { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1290
- );
1291
- }
1292
- let manifestValue: unknown;
1293
- try {
1294
- manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
1295
- } catch (error) {
1296
- throw ERRORS.TypedError(
1297
- HardwareErrorCode.RuntimeError,
1298
- `Protocol V2 local resource manifest is invalid: ${String(error)}`,
1299
- { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1300
- );
1301
- }
1302
- selectedFiles = selectProtocolV2ResourceManifestFiles({
1303
- manifest: parseProtocolV2ResourceManifest(manifestValue),
1304
- targetsToUpdate: this.params.targetsToUpdate ?? [],
1305
- });
1306
- manifestDirectory = manifestEntry.name.slice(0, -'manifest.json'.length);
1307
- } else {
1308
- selectedFiles = entries.flatMap(entry => {
1309
- const archivePath = getProtocolV2LocalResourceArchivePath(entry.name);
1310
- if (!archivePath) return [];
1311
- return [
1312
- {
1313
- archive_path: archivePath,
1314
- original_name: archivePath.split('/').pop() ?? archivePath,
1315
- device_path: `vol0:/${archivePath}`,
1316
- size: getProtocolV2ZipEntrySizes(entry).uncompressedSize,
1317
- sha256: '',
1318
- },
1319
- ];
1320
- });
1321
1325
  }
1322
- if (selectedFiles.length === 0 || selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
1326
+ const manifestEntry = zip.file('manifest.json');
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
1369
  throw ERRORS.TypedError(
1324
1370
  HardwareErrorCode.RuntimeError,
1325
- 'Protocol V2 local resource ZIP has no resource packages',
1371
+ 'Protocol V2 local resource ZIP contains an unexpected entry',
1326
1372
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1327
1373
  );
1328
1374
  }
1329
1375
 
1330
1376
  let totalSize = 0;
1331
- const materializedEntries: FirmwareMemoryArtifactEntry[] = [];
1332
- const normalizedFiles: IProtocolV2ResourceManifestFile[] = [];
1377
+ const materializedEntries: FirmwareMemoryArtifactEntry[] = [
1378
+ { entryName: 'manifest.json', binary: manifestBinary },
1379
+ ];
1333
1380
  for (const file of selectedFiles) {
1334
- const entry = manifestEntry
1335
- ? zip.file(`${manifestDirectory}${file.archive_path}`)
1336
- : entries.find(
1337
- candidate => getProtocolV2LocalResourceArchivePath(candidate.name) === file.archive_path
1338
- );
1381
+ const entry = zip.file(file.archive_path);
1339
1382
  if (!entry) {
1340
1383
  throw ERRORS.TypedError(
1341
1384
  HardwareErrorCode.RuntimeError,
@@ -1354,21 +1397,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1354
1397
  }
1355
1398
  const fileBinary = await entry.async('arraybuffer');
1356
1399
  const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
1357
- if (
1358
- fileBinary.byteLength !== file.size ||
1359
- (file.sha256 && digest !== file.sha256.toLowerCase())
1360
- ) {
1400
+ if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
1361
1401
  throw ERRORS.TypedError(
1362
1402
  HardwareErrorCode.RuntimeError,
1363
1403
  `Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
1364
1404
  { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1365
1405
  );
1366
1406
  }
1367
- normalizedFiles.push({ ...file, sha256: digest });
1368
1407
  materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
1369
1408
  }
1370
- manifestBinary ??= new TextEncoder().encode(JSON.stringify({ files: normalizedFiles })).buffer;
1371
- materializedEntries.unshift({ entryName: 'manifest.json', binary: manifestBinary });
1372
1409
  return { binary, materializedEntries };
1373
1410
  }
1374
1411
 
@@ -2131,6 +2168,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2131
2168
  return this.device.features?.mode === 'romloader';
2132
2169
  }
2133
2170
 
2171
+ private isLegacyProtocolV2Runtime() {
2172
+ const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
2173
+ return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
2174
+ }
2175
+
2134
2176
  private async rebootProtocolV2ToBootloader() {
2135
2177
  try {
2136
2178
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
@@ -2150,15 +2192,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2150
2192
  }
2151
2193
 
2152
2194
  async enterProtocolV2BootloaderMode() {
2195
+ this.protocolV2LegacyDirectUpdate = false;
2153
2196
  // romloader is the first update environment and forwards targets to bootloader.
2154
2197
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
2155
2198
  if (this.isProtocolV2RomloaderMode()) {
2156
2199
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
2200
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2157
2201
  this.protocolV2ExecutionInLoader = true;
2158
2202
  return false;
2159
2203
  }
2160
2204
  if (this.isProtocolV2BootloaderMode()) {
2161
2205
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
2206
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2162
2207
  this.protocolV2ExecutionInLoader = true;
2163
2208
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
2164
2209
  return false;
@@ -2669,7 +2714,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2669
2714
  let missingTargetStatusSince: number | undefined;
2670
2715
  let missingTargetStatusKey: string | undefined;
2671
2716
  let normalModeWithoutInstallEvidenceSince: number | undefined;
2672
- let installEvidenceObserved = false;
2717
+ let installEvidenceObserved = this.protocolV2InstallAckReceived;
2673
2718
  const resetMissingTargetStatusGrace = () => {
2674
2719
  missingTargetStatusSince = undefined;
2675
2720
  missingTargetStatusKey = undefined;
@@ -2689,7 +2734,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2689
2734
  try {
2690
2735
  const statusResponse = await this.device.getCommands().typedCall(
2691
2736
  'DeviceFirmwareUpdateStatusGet',
2692
- ['DeviceFirmwareUpdateStatus', 'Success'],
2737
+ 'DeviceFirmwareUpdateStatus',
2693
2738
  {
2694
2739
  fields: {
2695
2740
  status: true,
@@ -2699,11 +2744,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2699
2744
  },
2700
2745
  { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
2701
2746
  );
2702
- if (statusResponse.type === 'Success') {
2703
- this.protocolV2FinalStatusVerified = true;
2704
- this.postProgressMessage(100, 'installingFirmware');
2705
- return;
2706
- }
2707
2747
  const statusTargets = (statusResponse.message.records ??
2708
2748
  []) as ProtocolV2FirmwareUpdateStatusTarget[];
2709
2749
  if (
@@ -3108,12 +3148,65 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3108
3148
  }: {
3109
3149
  targets: Array<{ target_id: number; path: string }>;
3110
3150
  }) {
3151
+ this.protocolV2InstallAckReceived = false;
3111
3152
  this.protocolV2LastRuntimeProbeFeatures = undefined;
3112
- const commands = this.device.getCommands();
3113
- await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
3114
- await commands.call('DeviceFirmwareUpdateRequest', {}, { returnAfterWrite: true });
3153
+ const startUpdate = () =>
3154
+ this.device
3155
+ .getCommands()
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.
3115
3207
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
3116
3208
  this.postProgressMessage(0, 'installingFirmware');
3209
+ return response;
3117
3210
  }
3118
3211
 
3119
3212
  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
- factory_burn_in_completed: this.params.burn_in_completed,
28
+ 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
  },
@@ -5,7 +5,7 @@ import { invalidParameter } from '../helpers/filesystemValidation';
5
5
 
6
6
  export type DeviceInfoGetTargets = {
7
7
  hw?: boolean;
8
- main_mcu?: boolean;
8
+ fw?: boolean;
9
9
  coprocessor?: boolean;
10
10
  se1?: boolean;
11
11
  se2?: boolean;
@@ -27,7 +27,7 @@ export type DeviceInfoGetParams = {
27
27
 
28
28
  const TARGET_KEYS: (keyof DeviceInfoGetTargets)[] = [
29
29
  'hw',
30
- 'main_mcu',
30
+ 'fw',
31
31
  'coprocessor',
32
32
  'se1',
33
33
  'se2',
@@ -39,7 +39,7 @@ const TYPE_KEYS: (keyof DeviceInfoGetTypes)[] = ['version', 'build_id', 'hash',
39
39
 
40
40
  const DEFAULT_TARGETS: DeviceInfoGetTargets = {
41
41
  hw: true,
42
- main_mcu: true,
42
+ fw: true,
43
43
  coprocessor: true,
44
44
  };
45
45
 
@@ -462,10 +462,9 @@
462
462
  "MessageType_FilesystemDirMake": 60809,
463
463
  "MessageType_FilesystemDirRemove": 60810,
464
464
  "MessageType_FilesystemFormat": 60811,
465
- "MessageType_DeviceFirmwareUpdateStage": 61000,
466
- "MessageType_DeviceFirmwareUpdateRequest": 61001,
467
- "MessageType_DeviceFirmwareUpdateStatusGet": 61002,
468
- "MessageType_DeviceFirmwareUpdateStatus": 61003,
465
+ "MessageType_DeviceFirmwareUpdateRequest": 61000,
466
+ "MessageType_DeviceFirmwareUpdateStatusGet": 61001,
467
+ "MessageType_DeviceFirmwareUpdateStatus": 61002,
469
468
  "MessageType_DeviceSessionGet": 61200,
470
469
  "MessageType_DeviceSession": 61201,
471
470
  "MessageType_DeviceSessionAskPin": 61202,
@@ -10925,7 +10924,7 @@
10925
10924
  "type": "bytes",
10926
10925
  "id": 1
10927
10926
  },
10928
- "signning_message": {
10927
+ "signing_message": {
10929
10928
  "type": "bytes",
10930
10929
  "id": 2
10931
10930
  },
@@ -11814,13 +11813,13 @@
11814
11813
  "type": "string",
11815
11814
  "id": 2
11816
11815
  },
11817
- "factory_test_completed": {
11816
+ "burn_in_completed": {
11818
11817
  "type": "bool",
11819
- "id": 4
11818
+ "id": 3
11820
11819
  },
11821
- "factory_burn_in_completed": {
11820
+ "factory_test_completed": {
11822
11821
  "type": "bool",
11823
- "id": 3
11822
+ "id": 4
11824
11823
  },
11825
11824
  "manufacture_time": {
11826
11825
  "type": "DeviceFactoryInfoManufactureTime",
@@ -11907,7 +11906,7 @@
11907
11906
  }
11908
11907
  }
11909
11908
  },
11910
- "DeviceFirmwareUpdateStage": {
11909
+ "DeviceFirmwareUpdateRequest": {
11911
11910
  "fields": {
11912
11911
  "targets": {
11913
11912
  "rule": "repeated",
@@ -11916,9 +11915,6 @@
11916
11915
  }
11917
11916
  }
11918
11917
  },
11919
- "DeviceFirmwareUpdateRequest": {
11920
- "fields": {}
11921
- },
11922
11918
  "DeviceFirmwareUpdateRecord": {
11923
11919
  "fields": {
11924
11920
  "target_id": {
@@ -12100,7 +12096,7 @@
12100
12096
  "type": "bool",
12101
12097
  "id": 100
12102
12098
  },
12103
- "main_mcu": {
12099
+ "fw": {
12104
12100
  "type": "bool",
12105
12101
  "id": 200
12106
12102
  },
@@ -12169,7 +12165,7 @@
12169
12165
  "type": "DeviceHardwareInfo",
12170
12166
  "id": 100
12171
12167
  },
12172
- "main_mcu": {
12168
+ "fw": {
12173
12169
  "type": "DeviceMainMcuInfo",
12174
12170
  "id": 200
12175
12171
  },
@@ -12707,12 +12703,9 @@
12707
12703
  "id": 1
12708
12704
  },
12709
12705
  "text": {
12706
+ "rule": "required",
12710
12707
  "type": "string",
12711
12708
  "id": 2
12712
- },
12713
- "text_id": {
12714
- "type": "uint32",
12715
- "id": 3
12716
12709
  }
12717
12710
  }
12718
12711
  },
@@ -12743,6 +12736,7 @@
12743
12736
  "ViewSignPage": {
12744
12737
  "fields": {
12745
12738
  "title": {
12739
+ "rule": "required",
12746
12740
  "type": "string",
12747
12741
  "id": 1
12748
12742
  },
@@ -12776,16 +12770,13 @@
12776
12770
  "options": {
12777
12771
  "default": "LayoutDefault"
12778
12772
  }
12779
- },
12780
- "title_id": {
12781
- "type": "uint32",
12782
- "id": 8
12783
12773
  }
12784
12774
  }
12785
12775
  },
12786
12776
  "ViewVerifyPage": {
12787
12777
  "fields": {
12788
12778
  "title": {
12779
+ "rule": "required",
12789
12780
  "type": "string",
12790
12781
  "id": 1
12791
12782
  },
@@ -12810,14 +12801,6 @@
12810
12801
  "value_key": {
12811
12802
  "type": "uint32",
12812
12803
  "id": 6
12813
- },
12814
- "title_id": {
12815
- "type": "uint32",
12816
- "id": 7
12817
- },
12818
- "chain_id": {
12819
- "type": "uint32",
12820
- "id": 8
12821
12804
  }
12822
12805
  }
12823
12806
  },
@@ -267,11 +267,11 @@ export const mapProtocolV2DeviceInfoToState = (
267
267
  }
268
268
  : { mode },
269
269
  versions: definedEntries({
270
- firmware: imageVersion(info.main_mcu?.application),
271
- applicationP1: imageVersion(info.main_mcu?.application),
272
- applicationP2: imageVersion(info.main_mcu?.application_data),
273
- bootloader: imageVersion(info.main_mcu?.bootloader),
274
- board: imageVersion(info.main_mcu?.romloader),
270
+ firmware: imageVersion(info.fw?.application),
271
+ applicationP1: imageVersion(info.fw?.application),
272
+ applicationP2: imageVersion(info.fw?.application_data),
273
+ bootloader: imageVersion(info.fw?.bootloader),
274
+ board: imageVersion(info.fw?.romloader),
275
275
  ble: imageVersion(info.coprocessor?.application),
276
276
  se01: imageVersion(info.se1?.application),
277
277
  se02: imageVersion(info.se2?.application),
@@ -283,16 +283,16 @@ export const mapProtocolV2DeviceInfoToState = (
283
283
  se04Boot: imageVersion(info.se4?.bootloader),
284
284
  }),
285
285
  verification: definedEntries({
286
- firmwareBuildId: imageBuildId(info.main_mcu?.application),
287
- firmwareHash: imageHash(info.main_mcu?.application),
288
- applicationP1BuildId: imageBuildId(info.main_mcu?.application),
289
- applicationP1Hash: imageHash(info.main_mcu?.application),
290
- applicationP2BuildId: imageBuildId(info.main_mcu?.application_data),
291
- applicationP2Hash: imageHash(info.main_mcu?.application_data),
292
- bootloaderBuildId: imageBuildId(info.main_mcu?.bootloader),
293
- bootloaderHash: imageHash(info.main_mcu?.bootloader),
294
- boardBuildId: imageBuildId(info.main_mcu?.romloader),
295
- boardHash: imageHash(info.main_mcu?.romloader),
286
+ firmwareBuildId: imageBuildId(info.fw?.application),
287
+ firmwareHash: imageHash(info.fw?.application),
288
+ applicationP1BuildId: imageBuildId(info.fw?.application),
289
+ applicationP1Hash: imageHash(info.fw?.application),
290
+ applicationP2BuildId: imageBuildId(info.fw?.application_data),
291
+ applicationP2Hash: imageHash(info.fw?.application_data),
292
+ bootloaderBuildId: imageBuildId(info.fw?.bootloader),
293
+ bootloaderHash: imageHash(info.fw?.bootloader),
294
+ boardBuildId: imageBuildId(info.fw?.romloader),
295
+ boardHash: imageHash(info.fw?.romloader),
296
296
  bleBuildId: imageBuildId(info.coprocessor?.application),
297
297
  bleHash: imageHash(info.coprocessor?.application),
298
298
  se01BuildId: imageBuildId(info.se1?.application),
@@ -216,9 +216,9 @@ export const buildProtocolV2FeaturesPayload = ({
216
216
  runtimeMode?: ProtocolV2RuntimeMode;
217
217
  }): Features => {
218
218
  const info = deviceInfo;
219
- const fwApplication = info?.main_mcu?.application;
220
- const fwBootloader = info?.main_mcu?.bootloader;
221
- const fwBoard = info?.main_mcu?.romloader;
219
+ const fwApplication = info?.fw?.application;
220
+ const fwBootloader = info?.fw?.bootloader;
221
+ const fwBoard = info?.fw?.romloader;
222
222
  const bleApplication = info?.coprocessor?.application;
223
223
  const status = deviceStatus;
224
224
  const incomingSerialNo = info?.hw?.serial_no;