@onekeyfe/hd-core 1.2.0-alpha.109 → 1.2.0-alpha.110

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 (34) 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 +54 -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 -8
  9. package/__tests__/protocol-v2.test.ts +236 -192
  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 +218 -134
  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/types/settings.d.ts +19 -4
  20. package/dist/types/settings.d.ts.map +1 -1
  21. package/dist/utils/patch.d.ts +1 -1
  22. package/dist/utils/patch.d.ts.map +1 -1
  23. package/package.json +4 -4
  24. package/src/api/FirmwareUpdateV3.ts +23 -16
  25. package/src/api/FirmwareUpdateV4.ts +174 -84
  26. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  27. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  28. package/src/api/ton/TonSignMessage.ts +1 -1
  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 +46 -9
  34. 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) => {
@@ -205,6 +199,8 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
205
199
  path?: string;
206
200
  };
207
201
 
202
+ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
203
+
208
204
  type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
209
205
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
210
206
  kind: ProtocolV2RemoteComponentTarget['kind'];
@@ -381,6 +377,31 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
381
377
  );
382
378
  };
383
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
+
384
405
  const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
385
406
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
386
407
  return (
@@ -390,6 +411,15 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
390
411
  );
391
412
  };
392
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
+
393
423
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
394
424
  error instanceof HardwareError &&
395
425
  (error.errorCode === HardwareErrorCode.FirmwareError ||
@@ -566,7 +596,7 @@ export const assertProtocolV2ReconnectIdentity = (
566
596
  *
567
597
  * It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
568
598
  * - upload uses FilesystemFileWrite
569
- * - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
599
+ * - install uses DeviceFirmwareUpdateRequest
570
600
  * - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
571
601
  */
572
602
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
@@ -584,6 +614,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
584
614
 
585
615
  private protocolV2ExecutionInLoader = false;
586
616
 
617
+ private protocolV2LegacyDirectUpdate = false;
618
+
587
619
  private protocolV2BootResourceStagingSafe = false;
588
620
 
589
621
  private protocolV2CompletedTargetVersions = new Map<number, number>();
@@ -592,6 +624,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
592
624
 
593
625
  private protocolV2FinalStatusVerified = false;
594
626
 
627
+ private protocolV2InstallAckReceived = false;
628
+
595
629
  private protocolV2InstallBaselineVersions = new Map<number, string>();
596
630
 
597
631
  private protocolV2LastRuntimeProbeFeatures?: Features;
@@ -1259,86 +1293,92 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1259
1293
  );
1260
1294
  }
1261
1295
  const entries = zipEntries.filter(entry => !entry.dir);
1262
- if (entries.length === 0) {
1296
+ if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
1263
1297
  throw ERRORS.TypedError(
1264
1298
  HardwareErrorCode.RuntimeError,
1265
1299
  'Protocol V2 local resource ZIP entry set is invalid',
1266
1300
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1267
1301
  );
1268
1302
  }
1269
- const manifestEntry = entries.find(entry => entry.name.split('/').pop() === 'manifest.json');
1270
- let manifestBinary: ArrayBuffer | undefined;
1271
- let manifestDirectory = '';
1272
- let selectedFiles: IProtocolV2ResourceManifestFile[];
1273
- 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;
1274
1313
  if (
1275
- getProtocolV2ZipEntrySizes(manifestEntry).uncompressedSize >
1276
- 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
1277
1318
  ) {
1278
1319
  throw ERRORS.TypedError(
1279
1320
  HardwareErrorCode.RuntimeError,
1280
- 'Protocol V2 local resource manifest size is invalid',
1321
+ 'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
1281
1322
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1282
1323
  );
1283
1324
  }
1284
- manifestBinary = await manifestEntry.async('arraybuffer');
1285
- if (
1286
- manifestBinary.byteLength <= 0 ||
1287
- manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1288
- ) {
1289
- throw ERRORS.TypedError(
1290
- HardwareErrorCode.RuntimeError,
1291
- 'Protocol V2 local resource manifest size is invalid',
1292
- { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1293
- );
1294
- }
1295
- let manifestValue: unknown;
1296
- try {
1297
- manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
1298
- } catch (error) {
1299
- throw ERRORS.TypedError(
1300
- HardwareErrorCode.RuntimeError,
1301
- `Protocol V2 local resource manifest is invalid: ${String(error)}`,
1302
- { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1303
- );
1304
- }
1305
- selectedFiles = selectProtocolV2ResourceManifestFiles({
1306
- manifest: parseProtocolV2ResourceManifest(manifestValue),
1307
- targetsToUpdate: this.params.targetsToUpdate ?? [],
1308
- });
1309
- manifestDirectory = manifestEntry.name.slice(0, -'manifest.json'.length);
1310
- } else {
1311
- selectedFiles = entries.flatMap(entry => {
1312
- const archivePath = getProtocolV2LocalResourceArchivePath(entry.name);
1313
- if (!archivePath) return [];
1314
- return [
1315
- {
1316
- archive_path: archivePath,
1317
- original_name: archivePath.split('/').pop() ?? archivePath,
1318
- device_path: `vol0:/${archivePath}`,
1319
- size: getProtocolV2ZipEntrySizes(entry).uncompressedSize,
1320
- sha256: '',
1321
- },
1322
- ];
1323
- });
1324
1325
  }
1325
- 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
+ ) {
1326
1369
  throw ERRORS.TypedError(
1327
1370
  HardwareErrorCode.RuntimeError,
1328
- 'Protocol V2 local resource ZIP has no resource packages',
1371
+ 'Protocol V2 local resource ZIP contains an unexpected entry',
1329
1372
  { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1330
1373
  );
1331
1374
  }
1332
1375
 
1333
1376
  let totalSize = 0;
1334
- const materializedEntries: FirmwareMemoryArtifactEntry[] = [];
1335
- const normalizedFiles: IProtocolV2ResourceManifestFile[] = [];
1377
+ const materializedEntries: FirmwareMemoryArtifactEntry[] = [
1378
+ { entryName: 'manifest.json', binary: manifestBinary },
1379
+ ];
1336
1380
  for (const file of selectedFiles) {
1337
- const entry = manifestEntry
1338
- ? zip.file(`${manifestDirectory}${file.archive_path}`)
1339
- : entries.find(
1340
- candidate => getProtocolV2LocalResourceArchivePath(candidate.name) === file.archive_path
1341
- );
1381
+ const entry = zip.file(file.archive_path);
1342
1382
  if (!entry) {
1343
1383
  throw ERRORS.TypedError(
1344
1384
  HardwareErrorCode.RuntimeError,
@@ -1357,21 +1397,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1357
1397
  }
1358
1398
  const fileBinary = await entry.async('arraybuffer');
1359
1399
  const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
1360
- if (
1361
- fileBinary.byteLength !== file.size ||
1362
- (file.sha256 && digest !== file.sha256.toLowerCase())
1363
- ) {
1400
+ if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
1364
1401
  throw ERRORS.TypedError(
1365
1402
  HardwareErrorCode.RuntimeError,
1366
1403
  `Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
1367
1404
  { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1368
1405
  );
1369
1406
  }
1370
- normalizedFiles.push({ ...file, sha256: digest });
1371
1407
  materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
1372
1408
  }
1373
- manifestBinary ??= new TextEncoder().encode(JSON.stringify({ files: normalizedFiles })).buffer;
1374
- materializedEntries.unshift({ entryName: 'manifest.json', binary: manifestBinary });
1375
1409
  return { binary, materializedEntries };
1376
1410
  }
1377
1411
 
@@ -2134,6 +2168,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2134
2168
  return this.device.features?.mode === 'romloader';
2135
2169
  }
2136
2170
 
2171
+ private isLegacyProtocolV2Runtime() {
2172
+ const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
2173
+ return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
2174
+ }
2175
+
2137
2176
  private async rebootProtocolV2ToBootloader() {
2138
2177
  try {
2139
2178
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
@@ -2153,15 +2192,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2153
2192
  }
2154
2193
 
2155
2194
  async enterProtocolV2BootloaderMode() {
2195
+ this.protocolV2LegacyDirectUpdate = false;
2156
2196
  // romloader is the first update environment and forwards targets to bootloader.
2157
2197
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
2158
2198
  if (this.isProtocolV2RomloaderMode()) {
2159
2199
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
2200
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2160
2201
  this.protocolV2ExecutionInLoader = true;
2161
2202
  return false;
2162
2203
  }
2163
2204
  if (this.isProtocolV2BootloaderMode()) {
2164
2205
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
2206
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2165
2207
  this.protocolV2ExecutionInLoader = true;
2166
2208
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
2167
2209
  return false;
@@ -2672,7 +2714,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2672
2714
  let missingTargetStatusSince: number | undefined;
2673
2715
  let missingTargetStatusKey: string | undefined;
2674
2716
  let normalModeWithoutInstallEvidenceSince: number | undefined;
2675
- let installEvidenceObserved = false;
2717
+ let installEvidenceObserved = this.protocolV2InstallAckReceived;
2676
2718
  const resetMissingTargetStatusGrace = () => {
2677
2719
  missingTargetStatusSince = undefined;
2678
2720
  missingTargetStatusKey = undefined;
@@ -2692,7 +2734,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2692
2734
  try {
2693
2735
  const statusResponse = await this.device.getCommands().typedCall(
2694
2736
  'DeviceFirmwareUpdateStatusGet',
2695
- ['DeviceFirmwareUpdateStatus', 'Success'],
2737
+ 'DeviceFirmwareUpdateStatus',
2696
2738
  {
2697
2739
  fields: {
2698
2740
  status: true,
@@ -2702,11 +2744,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2702
2744
  },
2703
2745
  { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
2704
2746
  );
2705
- if (statusResponse.type === 'Success') {
2706
- this.protocolV2FinalStatusVerified = true;
2707
- this.postProgressMessage(100, 'installingFirmware');
2708
- return;
2709
- }
2710
2747
  const statusTargets = (statusResponse.message.records ??
2711
2748
  []) as ProtocolV2FirmwareUpdateStatusTarget[];
2712
2749
  if (
@@ -3111,12 +3148,65 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3111
3148
  }: {
3112
3149
  targets: Array<{ target_id: number; path: string }>;
3113
3150
  }) {
3151
+ this.protocolV2InstallAckReceived = false;
3114
3152
  this.protocolV2LastRuntimeProbeFeatures = undefined;
3115
- const commands = this.device.getCommands();
3116
- await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
3117
- 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.
3118
3207
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
3119
3208
  this.postProgressMessage(0, 'installingFirmware');
3209
+ return response;
3120
3210
  }
3121
3211
 
3122
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
 
@@ -174,7 +174,7 @@ export default class TonSignMessage extends BaseMethod<HardwareTonSignMessage> {
174
174
  if (!request.init_data_length) {
175
175
  const deviceType = this.device.getCurrentDeviceType();
176
176
  const hasClassic = DeviceModelToTypes.model_classic1s.includes(deviceType);
177
- const signingMessage = request.signning_message;
177
+ const signingMessage = request.signing_message ?? request.signning_message;
178
178
  // Blind signing omits the signing message in both protocol schemas.
179
179
  const shouldSkipValidation = signingMessage == null;
180
180
 
@@ -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;