@onekeyfe/hd-core 1.2.0-alpha.30 → 1.2.0-alpha.32

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 (123) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +251 -0
  2. package/__tests__/connectSettings.test.ts +5 -47
  3. package/__tests__/device-state-mapper.test.ts +10 -1
  4. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  5. package/__tests__/networkUtils.test.ts +20 -0
  6. package/__tests__/open-wallet-session.test.ts +19 -5
  7. package/__tests__/protocol-v2-firmware-targets.test.ts +18 -0
  8. package/__tests__/protocol-v2.test.ts +58 -240
  9. package/__tests__/public-device-state-api.test.ts +10 -1
  10. package/dist/api/CheckAllFirmwareRelease.d.ts +24 -1
  11. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  12. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  13. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  14. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  15. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  16. package/dist/api/FirmwareUpdateV4.d.ts +6 -22
  17. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  18. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  19. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  20. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  21. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  22. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -7
  23. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  24. package/dist/api/firmware/getBinary.d.ts +0 -7
  25. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  26. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  27. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  28. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  29. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  30. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  31. package/dist/data-manager/DataManager.d.ts +0 -1
  32. package/dist/data-manager/DataManager.d.ts.map +1 -1
  33. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  34. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  35. package/dist/index.d.ts +43 -179
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1048 -2606
  38. package/dist/inject.d.ts.map +1 -1
  39. package/dist/lowLevelInject.d.ts.map +1 -1
  40. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  41. package/dist/topLevelInject.d.ts.map +1 -1
  42. package/dist/types/api/checkAllFirmwareRelease.d.ts +25 -7
  43. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  44. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  45. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/types/api/export.d.ts +2 -4
  47. package/dist/types/api/export.d.ts.map +1 -1
  48. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  49. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  50. package/dist/types/api/index.d.ts +3 -10
  51. package/dist/types/api/index.d.ts.map +1 -1
  52. package/dist/types/api/openWalletSession.d.ts +9 -3
  53. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  54. package/dist/types/device.d.ts +6 -0
  55. package/dist/types/device.d.ts.map +1 -1
  56. package/dist/types/settings.d.ts +1 -13
  57. package/dist/types/settings.d.ts.map +1 -1
  58. package/dist/utils/networkUtils.d.ts +1 -0
  59. package/dist/utils/networkUtils.d.ts.map +1 -1
  60. package/package.json +4 -4
  61. package/src/api/CheckAllFirmwareRelease.ts +368 -50
  62. package/src/api/FirmwareUpdateV2.ts +120 -298
  63. package/src/api/FirmwareUpdateV3.ts +56 -258
  64. package/src/api/FirmwareUpdateV4.ts +328 -765
  65. package/src/api/OpenWalletSession.ts +12 -8
  66. package/src/api/PromptWebDeviceAccess.ts +2 -6
  67. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  68. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -50
  69. package/src/api/firmware/updateBootloader.ts +4 -19
  70. package/src/api/firmware/uploadFirmware.ts +22 -140
  71. package/src/data/messages/messages-protocol-v2.json +4 -0
  72. package/src/data-manager/DataManager.ts +33 -33
  73. package/src/data-manager/connectSettings.ts +0 -11
  74. package/src/device/DeviceStateMapper.ts +6 -0
  75. package/src/index.ts +0 -5
  76. package/src/inject.ts +2 -22
  77. package/src/lowLevelInject.ts +1 -5
  78. package/src/protocols/protocol-v2/walletSession.ts +2 -1
  79. package/src/topLevelInject.ts +1 -5
  80. package/src/types/api/checkAllFirmwareRelease.ts +46 -7
  81. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  82. package/src/types/api/export.ts +5 -18
  83. package/src/types/api/firmwareUpdate.ts +0 -76
  84. package/src/types/api/index.ts +6 -15
  85. package/src/types/api/openWalletSession.ts +12 -3
  86. package/src/types/device.ts +6 -0
  87. package/src/types/settings.ts +3 -13
  88. package/src/utils/networkUtils.ts +5 -3
  89. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -174
  90. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  91. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  92. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  93. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  94. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  95. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  96. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  97. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  98. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  99. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  100. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  101. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  102. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  103. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  104. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  105. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  106. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  107. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  108. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  109. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  110. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  111. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  112. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  113. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  114. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  115. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  116. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  117. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  118. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  119. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  120. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  121. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  122. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  123. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -5,6 +5,7 @@ import {
5
5
  PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
6
6
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
7
7
  } from '@onekeyfe/hd-transport';
8
+ import { sha256 } from '@noble/hashes/sha256';
8
9
 
9
10
  import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
10
11
  import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
@@ -18,7 +19,6 @@ import {
18
19
  getLogger,
19
20
  } from '../utils';
20
21
  import { getSysResourceBinary } from './firmware/getBinary';
21
- import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
22
22
  import { DataManager } from '../data-manager';
23
23
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
24
24
  import { DevicePool } from '../device/DevicePool';
@@ -31,20 +31,11 @@ import {
31
31
  getProtocolV2UnknownErrorText,
32
32
  isProtocolV2DeviceDisconnectedError,
33
33
  } from './protocol-v2/helpers';
34
- import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
35
- import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
36
- import {
37
- assertFirmwareUpdatePreparedPlanBinding,
38
- assertFirmwareUpdatePreparedPlanDeviceIdentity,
39
- } from './firmware/FirmwareUpdatePreparedPlan';
40
34
 
41
- import type {
42
- FirmwareArtifactReference,
43
- FirmwareUpdateV4Params,
44
- FirmwareUpdateV4Target,
45
- } from '../types/api/firmwareUpdate';
35
+ import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
46
36
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
47
37
  import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
38
+ import type { PROTO } from '../constants';
48
39
  import type { TypedResponseMessage } from '../device/DeviceCommands';
49
40
  import type {
50
41
  Features,
@@ -52,7 +43,6 @@ import type {
52
43
  IProtocolV2FirmwareComponent,
53
44
  IVersionArray,
54
45
  } from '../types';
55
- import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
56
46
 
57
47
  const Log = getLogger(LoggerNames.Method);
58
48
 
@@ -96,6 +86,11 @@ const getProtocolV2DeviceTransferProgress = (
96
86
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
97
87
  };
98
88
 
89
+ const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
90
+ const safeElapsedMs = Math.max(elapsedMs, 1);
91
+ return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
92
+ };
93
+
99
94
  type ProtocolV2FirmwareUpdateStatusTarget = {
100
95
  target_id: number | string;
101
96
  status?: number | string;
@@ -109,6 +104,17 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
109
104
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
110
105
  kind: ProtocolV2RemoteComponentTarget['kind'];
111
106
  };
107
+ type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
108
+ path: string;
109
+ };
110
+
111
+ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
112
+ filePath: string;
113
+ processedSize?: number;
114
+ totalSize?: number;
115
+ onTransferredBytes?: (transferredBytes: number) => void;
116
+ };
117
+
112
118
  /** RESC bundle okpkg written independently to devicePath through FileWrite. */
113
119
  type ProtocolV2ResourceBundleBinary = {
114
120
  name: string;
@@ -131,35 +137,6 @@ type ProtocolV2RemoteComponentTarget = {
131
137
  kind: 'bootloader' | 'firmware';
132
138
  };
133
139
 
134
- type ProtocolV2InstallSource = {
135
- fileName: string;
136
- source: FirmwareByteSource;
137
- targetId: number;
138
- kind: ProtocolV2RemoteComponentTarget['kind'];
139
- };
140
-
141
- type ProtocolV2ResourceBundleSource = {
142
- name: string;
143
- source: FirmwareByteSource;
144
- devicePath: string;
145
- version?: IVersionArray;
146
- payloadHash?: string;
147
- headerHash?: string;
148
- };
149
-
150
- type ProtocolV2ExecutionPhaseKind =
151
- | 'resource-sync'
152
- | 'bootloader-install'
153
- | 'bootloader-verify'
154
- | 'component-install'
155
- | 'final-verify';
156
-
157
- type ProtocolV2ExecutionPhase = {
158
- kind: ProtocolV2ExecutionPhaseKind;
159
- installSources: ProtocolV2InstallSource[];
160
- resourceSources: ProtocolV2ResourceBundleSource[];
161
- };
162
-
163
140
  type ProtocolV2OkppHeader = {
164
141
  type: string;
165
142
  version: IVersionArray;
@@ -390,6 +367,15 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
390
367
  };
391
368
  };
392
369
 
370
+ export const isProtocolV2FirmwareFingerprintValid = (
371
+ binary: ArrayBuffer | Uint8Array,
372
+ fingerprint: string | undefined
373
+ ) => {
374
+ const expectedFingerprint = normalizeProtocolV2Hex(fingerprint);
375
+ if (!expectedFingerprint) return true;
376
+ return bytesToHex(sha256(toProtocolV2Bytes(binary))) === expectedFingerprint;
377
+ };
378
+
393
379
  export const assertProtocolV2ReconnectIdentity = (
394
380
  expectedSerialNumber?: string,
395
381
  actualSerialNumber?: string
@@ -423,16 +409,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
423
409
  return ['V2'] as const;
424
410
  }
425
411
 
426
- private protocolV2PreparedSources: FirmwareByteSource[] = [];
427
-
428
- private protocolV2ExecutionInLoader = false;
429
-
430
- private protocolV2CompletedTargetVersions = new Map<number, number>();
431
-
432
- private protocolV2LatestFinalFeatures?: Features;
433
-
434
- private protocolV2FinalStatusVerified = false;
435
-
436
412
  init() {
437
413
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
438
414
  this.requireDeviceMode = [];
@@ -470,62 +446,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
470
446
  { name: 'firmwareType', type: 'string' },
471
447
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
472
448
  { name: 'platform', type: 'string' },
473
- { name: 'expectedDeviceId', type: 'string' },
474
449
  { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
475
450
  ]);
476
- if (
477
- payload.expectedDeviceId !== undefined &&
478
- (payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
479
- ) {
480
- throw ERRORS.TypedError(
481
- HardwareErrorCode.CallMethodInvalidParameter,
482
- 'Protocol V2 expected device identity is invalid'
483
- );
484
- }
485
- const hostBinding =
486
- payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
487
- ? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
488
- : undefined;
489
- if (hostBinding) {
490
- assertFirmwareUpdatePreparedPlanBinding({
491
- preparedPlan: payload.preparedPlan,
492
- executor: 'v4',
493
- platform: payload.platform,
494
- scopeTargets: [
495
- 'boot',
496
- 'app_v1',
497
- 'app_v2',
498
- 'coprocessor',
499
- 'resource',
500
- 'se01',
501
- 'se02',
502
- 'se03',
503
- 'se04',
504
- ],
505
- bindings: [
506
- ...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
507
- artifact
508
- ? [
509
- {
510
- target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
511
- artifact,
512
- },
513
- ]
514
- : []
515
- ),
516
- ...(payload.resourceBundleArtifacts ?? []).map(
517
- (entry: { name: string; artifact: FirmwareArtifactReference }) => ({
518
- target: 'resource' as const,
519
- logicalName: entry.name,
520
- artifact: entry.artifact,
521
- })
522
- ),
523
- ],
524
- });
525
- }
526
451
 
527
452
  this.params = {
528
- preparedPlan: payload.preparedPlan,
529
453
  chunkSize: payload.chunkSize,
530
454
  forcedUpdateRes: payload.forcedUpdateRes,
531
455
  bootloaderBinary: payload.bootloaderBinary,
@@ -540,12 +464,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
540
464
  resourceBundleFiles: payload.resourceBundleFiles,
541
465
  firmwareType: payload.firmwareType,
542
466
  targetsToUpdate: payload.targetsToUpdate,
543
- expectedTargetVersions: payload.expectedTargetVersions,
544
467
  platform: payload.platform,
545
- expectedDeviceId: payload.expectedDeviceId,
546
- artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
547
- componentArtifacts: payload.componentArtifacts,
548
- resourceBundleArtifacts: payload.resourceBundleArtifacts,
549
468
  };
550
469
  }
551
470
 
@@ -579,14 +498,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
579
498
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
580
499
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
581
500
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
582
- this.validateExpectedTargetVersions();
583
-
584
- if (
585
- (this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
586
- this.params.resourceBundleArtifacts?.length
587
- ) {
588
- return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
589
- }
590
501
 
591
502
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
592
503
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -597,18 +508,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
597
508
  fwBinaryMap = this.collectExplicitTargetBinaries();
598
509
  bootloaderBinary = this.prepareBootloaderBinary();
599
510
  if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
600
- if (
601
- this.params.artifactReader ||
602
- DataManager.getSettings('firmwareManifestMode') === 'external-only'
603
- ) {
604
- throw ERRORS.TypedError(
605
- HardwareErrorCode.RuntimeError,
606
- 'Protocol V2 firmware artifacts must be prepared by the external firmware host',
607
- {
608
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
609
- }
610
- );
611
- }
612
511
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
613
512
  firmwareType,
614
513
  deviceFeatures
@@ -617,16 +516,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
617
516
  fwBinaryMap = remoteBinaries.fwBinaryMap;
618
517
  installItems = remoteBinaries.installItems;
619
518
  }
620
- const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
621
- firmwareType,
622
- deviceFeatures
623
- );
624
- resourceBundles = resourceDescriptors?.length
625
- ? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
626
- : undefined;
519
+ resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
520
+ if (resourceBundles?.length) {
521
+ resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
522
+ }
627
523
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
628
524
  } catch (err) {
629
- throw normalizeFirmwarePreparationError(err);
525
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
630
526
  }
631
527
 
632
528
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
@@ -636,289 +532,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
636
532
  );
637
533
  }
638
534
 
639
- return this.executeProtocolV2Update({
535
+ await this.enterProtocolV2BootloaderMode();
536
+
537
+ await this.executeProtocolV2Update({
640
538
  fwBinaryMap,
641
539
  bootloaderBinary,
642
540
  ...(installItems ? { installItems } : undefined),
643
541
  ...(resourceBundles?.length ? { resourceBundles } : undefined),
644
542
  });
645
- }
646
543
 
647
- private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
648
- const source = await openFirmwareByteSource({
649
- artifact,
650
- reader: this.params.artifactReader,
651
- });
652
- if (!source) {
653
- throw ERRORS.TypedError(
654
- HardwareErrorCode.RuntimeError,
655
- `Firmware artifact ${artifact.artifactRef} is not prepared`,
656
- {
657
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
658
- }
659
- );
660
- }
661
- this.protocolV2PreparedSources.push(source);
662
- return source;
663
- }
544
+ await this.exitProtocolV2BootloaderToNormal();
664
545
 
665
- private async openProtocolV2MemorySource(binary: ArrayBuffer) {
666
- const source = await openFirmwareByteSource({ binary });
667
- if (!source) {
668
- throw ERRORS.TypedError(
669
- HardwareErrorCode.RuntimeError,
670
- 'Protocol V2 firmware binary is empty'
671
- );
672
- }
673
- this.protocolV2PreparedSources.push(source);
674
- return source;
675
- }
676
-
677
- private async closeProtocolV2PreparedSources() {
678
- const sources = this.protocolV2PreparedSources.splice(0);
679
- await Promise.all(sources.map(source => source.close().catch(() => undefined)));
680
- }
681
-
682
- private async prepareProtocolV2InstallSources(
683
- firmwareType: EFirmwareType,
684
- features: Features
685
- ): Promise<ProtocolV2InstallSource[]> {
686
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
687
- if (!release) {
688
- throw ERRORS.TypedError(
689
- HardwareErrorCode.RuntimeError,
690
- 'Protocol V2 firmware release is unavailable'
691
- );
692
- }
693
- const componentArtifacts = this.params.componentArtifacts ?? {};
694
- const requestedTargets = new Set(
695
- this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
696
- );
697
- const installSources: ProtocolV2InstallSource[] = [];
698
- const preparedTargets = new Set<FirmwareUpdateV4Target>();
699
-
700
- for (const [key, component] of this.getRemoteComponentEntries(release)) {
701
- const target = this.getRemoteComponentTarget(key, component);
702
- const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
703
- if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
704
- const artifact = componentArtifacts[updateTarget];
705
- if (!artifact) {
706
- throw ERRORS.TypedError(
707
- HardwareErrorCode.RuntimeError,
708
- `Protocol V2 ${updateTarget} artifact is not prepared`,
709
- {
710
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
711
- artifactName: updateTarget,
712
- }
713
- );
714
- }
715
- installSources.push({
716
- ...target,
717
- source: await this.openProtocolV2PreparedSource(artifact),
718
- });
719
- preparedTargets.add(updateTarget);
720
- }
721
- }
722
-
723
- const unknownTarget = Array.from(requestedTargets).find(
724
- target => target !== 'resource' && !preparedTargets.has(target)
725
- );
726
- if (unknownTarget) {
727
- throw ERRORS.TypedError(
728
- HardwareErrorCode.RuntimeError,
729
- `Protocol V2 release does not contain requested target ${unknownTarget}`
730
- );
731
- }
732
- return installSources;
733
- }
734
-
735
- private async prepareProtocolV2ResourceSources(
736
- firmwareType: EFirmwareType,
737
- features: Features
738
- ): Promise<ProtocolV2ResourceBundleSource[]> {
739
- const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
740
- const resourceRequested =
741
- this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
742
- if (!resourceRequested) {
743
- return [];
744
- }
745
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
746
- const descriptors = release?.resourceBundles ?? [];
747
- const artifactByName = new Map(
748
- preparedArtifacts.map(item => [item.name, item.artifact] as const)
749
- );
750
- const sources: ProtocolV2ResourceBundleSource[] = [];
751
- for (const descriptor of descriptors) {
752
- const artifact = artifactByName.get(descriptor.name);
753
- if (!artifact) {
754
- throw ERRORS.TypedError(
755
- HardwareErrorCode.RuntimeError,
756
- `Protocol V2 resource bundle ${descriptor.name} is not prepared`,
757
- {
758
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
759
- artifactName: descriptor.name,
760
- }
761
- );
762
- }
763
- const devicePath = validateProtocolV2FilesystemPath(
764
- descriptor.devicePath,
765
- 'resourceBundles[].devicePath'
766
- );
767
- sources.push({
768
- name: descriptor.name,
769
- source: await this.openProtocolV2PreparedSource(artifact),
770
- devicePath,
771
- version: descriptor.version,
772
- payloadHash: descriptor.payloadHash,
773
- headerHash: descriptor.headerHash,
774
- });
775
- artifactByName.delete(descriptor.name);
776
- }
777
- if (artifactByName.size > 0) {
778
- throw ERRORS.TypedError(
779
- HardwareErrorCode.RuntimeError,
780
- `Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
781
- );
782
- }
783
- return sources;
784
- }
785
-
786
- private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
787
- try {
788
- this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
789
- const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
790
- const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
791
- if (installSources.length === 0 && resourceSources.length === 0) {
792
- throw ERRORS.TypedError(
793
- HardwareErrorCode.FirmwareUpdateDownloadFailed,
794
- 'No firmware to update'
795
- );
796
- }
797
- this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
798
-
799
- return await this.executeProtocolV2SourceUpdate({
800
- installSources,
801
- resourceSources,
802
- });
803
- } finally {
804
- await this.closeProtocolV2PreparedSources();
805
- }
806
- }
807
-
808
- private validateExpectedTargetVersions() {
809
- const expected = this.params.expectedTargetVersions;
810
- if (expected === undefined) return;
811
- if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
812
- throw ERRORS.TypedError(
813
- HardwareErrorCode.RuntimeError,
814
- 'Protocol V2 expected target versions are invalid'
815
- );
816
- }
817
- for (const [target, version] of Object.entries(expected)) {
818
- if (
819
- !Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
820
- target as FirmwareUpdateV4Target
821
- ) ||
822
- typeof version !== 'string' ||
823
- !/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
824
- version.length > 64
825
- ) {
826
- throw ERRORS.TypedError(
827
- HardwareErrorCode.RuntimeError,
828
- 'Protocol V2 expected target version is invalid'
829
- );
830
- }
831
- }
832
- }
833
-
834
- private getExpectedProtocolV2TargetVersion(targetId: number) {
835
- const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
836
- const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
837
- if (!version) return undefined;
838
- const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
839
- if (
840
- parts.length !== 3 ||
841
- parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
842
- ) {
843
- return undefined;
844
- }
845
- return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
846
- }
847
-
848
- private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
849
- const expected = this.params?.expectedTargetVersions;
850
- if (!expected) return;
851
- const features = this.protocolV2LatestFinalFeatures;
852
- const requestedTargets = this.getProtocolV2RequestedTargets();
853
- const applicationTargets = requestedTargets.filter(
854
- target => target === 'app_v1' || target === 'app_v2'
855
- );
856
- const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
857
- boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
858
- coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
859
- se01: features?.se01Version ?? undefined,
860
- se02: features?.se02Version ?? undefined,
861
- se03: features?.se03Version ?? undefined,
862
- se04: features?.se04Version ?? undefined,
863
- };
864
- if (features && applicationTargets.length === 1) {
865
- visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
866
- }
867
- const expectedEntries = (
868
- Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
869
- ).filter(([target]) => !targets || targets.includes(target));
870
- for (const [target, expectedVersion] of expectedEntries) {
871
- const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
872
- ([, mappedTarget]) => mappedTarget === target
873
- )?.[0];
874
- const statusVersion =
875
- targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
876
- const observedVersion =
877
- statusVersion === undefined
878
- ? visibleVersions[target]
879
- : `${Math.floor(statusVersion / 0x10000) % 0x100}.${
880
- Math.floor(statusVersion / 0x100) % 0x100
881
- }.${statusVersion % 0x100}`;
882
- if (!observedVersion) {
883
- if (this.protocolV2FinalStatusVerified) {
884
- Log.warn(
885
- `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
886
- );
887
- } else {
888
- throw ERRORS.TypedError(
889
- HardwareErrorCode.FirmwareVerificationFailed,
890
- `Protocol V2 target ${target} has no observable final version after status fallback`
891
- );
892
- }
893
- } else if (observedVersion !== expectedVersion) {
894
- throw ERRORS.TypedError(
895
- HardwareErrorCode.FirmwareVerificationFailed,
896
- `Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
897
- );
898
- }
899
- }
900
- }
546
+ const versions = await this.waitForProtocolV2FinalFeatures();
547
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
548
+ DevicePool.resetState();
901
549
 
902
- private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
903
- if (this.params.targetsToUpdate?.length) {
904
- return [...new Set(this.params.targetsToUpdate)];
905
- }
906
- const targets = new Set<FirmwareUpdateV4Target>();
907
- Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
908
- targets.add(target as FirmwareUpdateV4Target)
909
- );
910
- if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
911
- targets.add('resource');
912
- }
913
- if (this.params.bootloaderBinary) targets.add('boot');
914
- if (this.params.applicationP1Binary) targets.add('app_v1');
915
- if (this.params.applicationP2Binary) targets.add('app_v2');
916
- if (this.params.coprocessorBinary) targets.add('coprocessor');
917
- if (this.params.se01Binary) targets.add('se01');
918
- if (this.params.se02Binary) targets.add('se02');
919
- if (this.params.se03Binary) targets.add('se03');
920
- if (this.params.se04Binary) targets.add('se04');
921
- return Array.from(targets);
550
+ return versions;
922
551
  }
923
552
 
924
553
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
@@ -954,16 +583,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
954
583
  private async captureProtocolV2PhysicalIdentity() {
955
584
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
956
585
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
957
- if (this.params?.preparedPlan) {
958
- assertFirmwareUpdatePreparedPlanDeviceIdentity({
959
- preparedPlan: this.params.preparedPlan,
960
- deviceIdentity: serialNumber,
961
- });
962
- } else if (this.params?.expectedDeviceId) {
963
- assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
964
- } else {
965
- assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
966
- }
586
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
967
587
  this.protocolV2ExpectedSerialNumber = serialNumber;
968
588
  }
969
589
 
@@ -1106,6 +726,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1106
726
  }
1107
727
 
1108
728
  const { binary } = await getSysResourceBinary(component.url);
729
+ if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
730
+ throw ERRORS.TypedError(
731
+ HardwareErrorCode.RuntimeError,
732
+ `Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
733
+ );
734
+ }
735
+ const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
736
+ if (expectedPayloadHash) {
737
+ const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
738
+ if (!header || header.payloadHash !== expectedPayloadHash) {
739
+ throw ERRORS.TypedError(
740
+ HardwareErrorCode.RuntimeError,
741
+ `Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
742
+ );
743
+ }
744
+ }
1109
745
  return {
1110
746
  ...target,
1111
747
  binary,
@@ -1216,47 +852,117 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1216
852
  })) as ProtocolV2ResourceBundleBinary[];
1217
853
  }
1218
854
 
1219
- private async materializeProtocolV2ResourceBundles(
855
+ private async prefetchProtocolV2ResourceBundles(
1220
856
  bundles: ProtocolV2ResourceBundleBinary[]
1221
857
  ): Promise<ProtocolV2ResourceBundleBinary[]> {
858
+ const prefetched: ProtocolV2ResourceBundleBinary[] = [];
1222
859
  for (const bundle of bundles) {
1223
- if (bundle.binary.byteLength === 0 && bundle.url) {
1224
- if (
1225
- this.params.artifactReader ||
1226
- DataManager.getSettings('firmwareManifestMode') === 'external-only'
1227
- ) {
1228
- throw ERRORS.TypedError(
1229
- HardwareErrorCode.RuntimeError,
1230
- `Firmware resource bundle ${bundle.name} is not prepared`,
1231
- {
1232
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1233
- artifactName: bundle.name,
1234
- }
1235
- );
860
+ let { binary } = bundle;
861
+ let downloadedFromRemote = false;
862
+ if (binary.byteLength === 0) {
863
+ if (!bundle.url) {
864
+ throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
1236
865
  }
1237
- Log.log(`[FirmwareUpdateV4] preloading RESC bundle ${bundle.name}`);
1238
- bundle.binary = (await getSysResourceBinary(bundle.url)).binary;
866
+ Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
867
+ ({ binary } = await getSysResourceBinary(bundle.url));
868
+ downloadedFromRemote = true;
1239
869
  }
1240
- if (bundle.binary.byteLength === 0) {
1241
- throw ERRORS.TypedError(
1242
- HardwareErrorCode.FirmwareUpdateDownloadFailed,
1243
- `Protocol V2 RESC bundle is empty: ${bundle.name}`
1244
- );
870
+ if (binary.byteLength === 0) {
871
+ throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
1245
872
  }
873
+ if (downloadedFromRemote && (bundle.version || bundle.payloadHash || bundle.headerHash)) {
874
+ const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
875
+ if (!header || header.type !== 'RESC') {
876
+ throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
877
+ }
878
+ if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
879
+ throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
880
+ }
881
+ const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
882
+ if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
883
+ throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
884
+ }
885
+ const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
886
+ if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
887
+ throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
888
+ }
889
+ }
890
+ prefetched.push({ ...bundle, binary });
1246
891
  }
1247
- return bundles;
892
+ return prefetched;
893
+ }
894
+
895
+ /**
896
+ * Synchronize RESC bundles to the device.
897
+ *
898
+ * Manual mode writes supplied binaries directly.
899
+ * Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
900
+ */
901
+ private async syncProtocolV2ResourceBundles(
902
+ bundles: ProtocolV2ResourceBundleBinary[],
903
+ firmwareSize: number
904
+ ): Promise<{ processedSize: number; totalSize: number }> {
905
+ const transferStartTime = Date.now();
906
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
907
+
908
+ const isRemoteMode = bundles.some(bundle => !!bundle.url);
909
+ let bundlesToSync = bundles;
910
+
911
+ // Remote binaries were prefetched before bootloader entry. Only device-local
912
+ // header comparison and transfer are allowed in this phase.
913
+ if (isRemoteMode) {
914
+ const filtered: ProtocolV2ResourceBundleBinary[] = [];
915
+ for (const bundle of bundles) {
916
+ // Compare the existing device header.
917
+ const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
918
+ if (upToDate) {
919
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
920
+ } else {
921
+ filtered.push(bundle);
922
+ }
923
+ }
924
+ bundlesToSync = filtered;
925
+ }
926
+
927
+ if (bundlesToSync.length === 0) {
928
+ Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
929
+ return { processedSize: 0, totalSize: firmwareSize };
930
+ }
931
+
932
+ // Write directly to the device through FileWrite.
933
+ let totalSize = 0;
934
+ for (const b of bundlesToSync) totalSize += b.binary.byteLength;
935
+
936
+ const transferTotalSize = totalSize + firmwareSize;
937
+ let processedSize = 0;
938
+ for (const bundle of bundlesToSync) {
939
+ Log.log(
940
+ `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
941
+ );
942
+ processedSize = await this.protocolV2CommonUpdateProcess({
943
+ payload: bundle.binary,
944
+ filePath: bundle.devicePath,
945
+ processedSize,
946
+ totalSize: transferTotalSize,
947
+ });
948
+ }
949
+
950
+ const elapsedMs = Date.now() - transferStartTime;
951
+ Log.log(
952
+ `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
953
+ elapsedMs / 1000
954
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
955
+ );
956
+ return { processedSize, totalSize: transferTotalSize };
1248
957
  }
1249
958
 
1250
959
  /**
1251
960
  * Compare the existing okpkg OKPP header in remote-config mode.
1252
961
  */
1253
962
  private async isProtocolV2ResourceBundleUpToDate(
1254
- bundle: Pick<
1255
- ProtocolV2ResourceBundleBinary,
1256
- 'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1257
- >
963
+ bundle: ProtocolV2ResourceBundleBinary
1258
964
  ): Promise<boolean> {
1259
- if (this.params?.forcedUpdateRes) return false;
965
+ if (this.params.forcedUpdateRes) return false;
1260
966
  if (!bundle.version && !bundle.payloadHash) return false;
1261
967
 
1262
968
  try {
@@ -1294,7 +1000,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1294
1000
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
1295
1001
  if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
1296
1002
  Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
1297
- this.protocolV2ExecutionInLoader = true;
1298
1003
  return false;
1299
1004
  }
1300
1005
 
@@ -1304,7 +1009,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1304
1009
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1305
1010
  await wait(1000);
1306
1011
  await this.waitForProtocolV2BootloaderMode();
1307
- this.protocolV2ExecutionInLoader = true;
1308
1012
  return true;
1309
1013
  } catch (error) {
1310
1014
  if (error instanceof HardwareError) {
@@ -1393,98 +1097,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1393
1097
  return entries;
1394
1098
  }
1395
1099
 
1396
- private buildProtocolV2ExecutionPhases({
1397
- installSources,
1398
- resourceSources,
1399
- }: {
1400
- installSources: ProtocolV2InstallSource[];
1401
- resourceSources: ProtocolV2ResourceBundleSource[];
1402
- }): ProtocolV2ExecutionPhase[] {
1403
- const phases: ProtocolV2ExecutionPhase[] = [];
1404
- if (resourceSources.length > 0) {
1405
- phases.push({
1406
- kind: 'resource-sync',
1407
- installSources: [],
1408
- resourceSources,
1409
- });
1410
- }
1411
- const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
1412
- if (bootloaderSources.length > 0) {
1413
- phases.push(
1414
- {
1415
- kind: 'bootloader-install',
1416
- installSources: bootloaderSources,
1417
- resourceSources: [],
1418
- },
1419
- {
1420
- kind: 'bootloader-verify',
1421
- installSources: [],
1422
- resourceSources: [],
1423
- }
1424
- );
1425
- }
1426
- const componentSources = installSources.filter(source => source.kind !== 'bootloader');
1427
- if (componentSources.length > 0) {
1428
- phases.push({
1429
- kind: 'component-install',
1430
- installSources: componentSources,
1431
- resourceSources: [],
1432
- });
1433
- }
1434
- phases.push({
1435
- kind: 'final-verify',
1436
- installSources: [],
1437
- resourceSources: [],
1438
- });
1439
- return phases;
1440
- }
1441
-
1442
- private async executeProtocolV2Phases({
1443
- installSources,
1444
- resourceSources,
1445
- }: {
1446
- installSources: ProtocolV2InstallSource[];
1447
- resourceSources: ProtocolV2ResourceBundleSource[];
1448
- }) {
1449
- const phases = this.buildProtocolV2ExecutionPhases({
1450
- installSources,
1451
- resourceSources,
1452
- });
1453
- for (const phase of phases) {
1454
- if (phase.kind === 'final-verify') {
1455
- return this.completeProtocolV2FinalVerification();
1456
- }
1457
- if (phase.kind === 'resource-sync') {
1458
- await this.enterProtocolV2BootloaderMode();
1459
- await this.executeProtocolV2TransferPhase(phase);
1460
- } else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
1461
- await this.enterProtocolV2BootloaderMode();
1462
- await this.executeProtocolV2TransferPhase(phase);
1463
- await this.exitProtocolV2BootloaderToNormal();
1464
- } else if (phase.kind === 'bootloader-verify') {
1465
- await this.waitForProtocolV2FinalFeatures();
1466
- this.assertExpectedProtocolV2Versions(['boot']);
1467
- }
1468
- }
1469
- throw ERRORS.TypedError(
1470
- HardwareErrorCode.RuntimeError,
1471
- 'Protocol V2 execution has no final verification phase'
1472
- );
1473
- }
1474
-
1475
- private async executeProtocolV2SourceUpdate({
1476
- installSources,
1477
- resourceSources,
1478
- }: {
1479
- installSources: ProtocolV2InstallSource[];
1480
- resourceSources: ProtocolV2ResourceBundleSource[];
1481
- }) {
1482
- return this.executeProtocolV2Phases({
1483
- installSources,
1484
- resourceSources,
1485
- });
1486
- }
1487
-
1488
1100
  private async executeProtocolV2Update({
1489
1101
  fwBinaryMap,
1490
1102
  bootloaderBinary,
@@ -1496,158 +1108,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1496
1108
  installItems?: ProtocolV2InstallItem[];
1497
1109
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1498
1110
  }) {
1499
- const memoryInstallItems =
1111
+ const orderedInstallItems =
1500
1112
  installItems ??
1501
1113
  this.buildProtocolV2InstallItems({
1502
- fwBinaryMap: fwBinaryMap ?? [],
1503
1114
  bootloaderBinary: bootloaderBinary ?? null,
1115
+ fwBinaryMap: fwBinaryMap ?? [],
1504
1116
  });
1505
- try {
1506
- const installSources = await Promise.all(
1507
- memoryInstallItems.map(async item => ({
1508
- fileName: item.fileName,
1509
- source: await this.openProtocolV2MemorySource(item.binary),
1510
- targetId: item.targetId,
1511
- kind: item.kind,
1512
- }))
1513
- );
1514
- const resourceSources = await Promise.all(
1515
- (resourceBundles ?? []).map(async bundle => ({
1516
- name: bundle.name,
1517
- source: await this.openProtocolV2MemorySource(bundle.binary),
1518
- devicePath: bundle.devicePath,
1519
- version: bundle.version,
1520
- payloadHash: bundle.payloadHash,
1521
- headerHash: bundle.headerHash,
1522
- }))
1523
- );
1524
- return await this.executeProtocolV2Phases({
1525
- installSources,
1526
- resourceSources,
1527
- });
1528
- } finally {
1529
- await this.closeProtocolV2PreparedSources();
1530
- }
1531
- }
1532
1117
 
1533
- private async executeProtocolV2TransferPhase({
1534
- installSources,
1535
- resourceSources,
1536
- }: ProtocolV2ExecutionPhase) {
1537
- let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
1538
- const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
1539
- for (const resource of resourceSources) {
1540
- if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
1541
- Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
1542
- } else {
1543
- resourcesToSync.push(resource);
1544
- totalSize += resource.source.size;
1545
- }
1546
- }
1118
+ let firmwareSize = 0;
1119
+ for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1547
1120
 
1548
1121
  this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1122
+
1549
1123
  let processedSize = 0;
1550
- for (const resource of resourcesToSync) {
1551
- processedSize = await this.protocolV2SourceUpdateProcess({
1552
- source: resource.source,
1553
- filePath: resource.devicePath,
1554
- processedSize,
1555
- totalSize,
1556
- });
1557
- await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
1124
+ let totalSize = firmwareSize;
1125
+ if (resourceBundles?.length) {
1126
+ const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1127
+ resourceBundles,
1128
+ firmwareSize
1129
+ );
1130
+ processedSize = resourceTransfer.processedSize;
1131
+ totalSize = resourceTransfer.totalSize;
1558
1132
  }
1559
1133
 
1560
- const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
1561
- for (const item of installSources) {
1562
- const filePath = this.getProtocolV2InstallItemStagingPath(item);
1563
- processedSize = await this.protocolV2SourceUpdateProcess({
1564
- source: item.source,
1565
- filePath,
1566
- processedSize,
1567
- totalSize,
1568
- });
1569
- await this.verifyProtocolV2StagedFile(filePath, item.source.size);
1570
- stagedInstallTargets.push({
1571
- targetId: item.targetId,
1572
- path: filePath,
1573
- });
1134
+ // Skip staging and installation when the update contains RESC bundles only.
1135
+ if (orderedInstallItems.length === 0) {
1136
+ Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1137
+ if (totalSize > 0) {
1138
+ this.postProgressMessage(100, 'transferData');
1139
+ }
1140
+ return;
1574
1141
  }
1142
+ let transferredSize = processedSize;
1143
+ const transferStartTime = Date.now();
1144
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1145
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1146
+ const onTransferredBytes = (bytes: number) => {
1147
+ transferredSize = bytes;
1148
+ };
1149
+ Log.log(
1150
+ `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1151
+ );
1575
1152
 
1576
- if (totalSize > 0) {
1577
- this.postProgressMessage(100, 'transferData');
1578
- }
1579
- if (stagedInstallTargets.length === 0) {
1580
- return;
1153
+ const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1154
+
1155
+ try {
1156
+ for (const item of orderedInstallItems) {
1157
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1158
+ Log.log(
1159
+ `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1160
+ );
1161
+ processedSize = await this.protocolV2CommonUpdateProcess({
1162
+ payload: item.binary,
1163
+ filePath,
1164
+ processedSize,
1165
+ totalSize,
1166
+ onTransferredBytes,
1167
+ });
1168
+ transferredSize = processedSize;
1169
+ await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1170
+
1171
+ stagedInstallTargets.push({
1172
+ ...item,
1173
+ path: filePath,
1174
+ });
1175
+ }
1176
+
1177
+ if (totalSize > 0) {
1178
+ this.postProgressMessage(100, 'transferData');
1179
+ }
1180
+
1181
+ const elapsedMs = Date.now() - transferStartTime;
1182
+ Log.log(
1183
+ `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1184
+ elapsedMs / 1000
1185
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1186
+ );
1187
+ } catch (error) {
1188
+ const elapsedMs = Date.now() - transferStartTime;
1189
+ Log.warn(
1190
+ `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1191
+ elapsedMs / 1000
1192
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1193
+ );
1194
+ throw error;
1581
1195
  }
1582
1196
 
1583
1197
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1584
- const targets = stagedInstallTargets.map(item => ({
1198
+
1199
+ const allTargets = stagedInstallTargets.map(item => ({
1585
1200
  target_id: item.targetId,
1586
1201
  path: item.path,
1587
1202
  }));
1588
- await this.protocolV2StartFirmwareUpdate({ targets });
1589
- await this.waitForProtocolV2FirmwareUpdateComplete(targets);
1203
+ Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1204
+ await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1205
+ await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1590
1206
  }
1591
1207
 
1592
- private async protocolV2SourceUpdateProcess({
1593
- source,
1594
- filePath,
1595
- processedSize,
1596
- totalSize,
1597
- }: {
1598
- source: FirmwareByteSource;
1599
- filePath: string;
1600
- processedSize: number;
1601
- totalSize: number;
1602
- }) {
1603
- let lastError: unknown;
1604
- for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1605
- try {
1606
- await writeFirmwareByteSource({
1607
- source,
1608
- chunkSize: this.getProtocolV2FirmwareChunkSize(),
1609
- write: async ({ data, sourceOffset, length, first }) => {
1610
- const chunkEnd = sourceOffset + length;
1611
- const progress = getProtocolV2DeviceTransferProgress(
1612
- processedSize + sourceOffset,
1613
- processedSize + chunkEnd,
1614
- totalSize
1615
- );
1616
- const response = await this.fileWriteChunk(
1617
- filePath,
1618
- source.size,
1619
- sourceOffset,
1620
- data,
1621
- first,
1622
- progress
1623
- );
1624
- const rawProcessedByte = response.message.processed_byte;
1625
- const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
1626
- if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
1627
- throw ERRORS.TypedError(
1628
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1629
- `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
1630
- );
1631
- }
1632
- this.postProgressMessage(progress, 'transferData');
1633
- return length;
1634
- },
1635
- });
1636
- return processedSize + source.size;
1637
- } catch (error) {
1638
- lastError = error;
1639
- if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1640
- await this.recoverProtocolV2FileTransfer();
1641
- }
1642
- }
1643
- }
1644
- throw ERRORS.TypedError(
1645
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1646
- `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1647
- );
1648
- }
1649
-
1650
- private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1208
+ private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1651
1209
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1652
1210
  }
1653
1211
 
@@ -1666,8 +1224,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1666
1224
 
1667
1225
  private assertProtocolV2TargetStatus(
1668
1226
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1669
- expectedTargetIds: Set<number>,
1670
- expectedPaths = new Map<number, string>()
1227
+ expectedTargetIds: Set<number>
1671
1228
  ) {
1672
1229
  Log.log(
1673
1230
  `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
@@ -1698,47 +1255,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1698
1255
  const matchingTargets = statusTargets.filter(target =>
1699
1256
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1700
1257
  );
1701
- const seenTargetIds = new Set<number>();
1702
- for (const target of matchingTargets) {
1703
- const targetId = normalizeProtocolV2TargetId(target.target_id);
1704
- if (
1705
- targetId === undefined ||
1706
- seenTargetIds.has(targetId) ||
1707
- (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
1708
- ) {
1709
- throw ERRORS.TypedError(
1710
- HardwareErrorCode.RuntimeError,
1711
- `Protocol V2 install status conflicts with target ${target.target_id}`
1712
- );
1713
- }
1714
- seenTargetIds.add(targetId);
1715
- }
1716
1258
  const completedTargets = matchingTargets.filter(target =>
1717
1259
  isProtocolV2TargetStatusFinished(target.status)
1718
1260
  );
1719
1261
  if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
1720
- for (const target of completedTargets) {
1721
- const targetId = normalizeProtocolV2TargetId(target.target_id);
1722
- const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
1723
- if (targetId === undefined) {
1724
- throw ERRORS.TypedError(
1725
- HardwareErrorCode.RuntimeError,
1726
- 'Protocol V2 completed target identity is invalid'
1727
- );
1728
- }
1729
- const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
1730
- if (expectedVersion !== undefined && payloadVersion !== undefined) {
1731
- if (payloadVersion !== expectedVersion) {
1732
- throw ERRORS.TypedError(
1733
- HardwareErrorCode.FirmwareVerificationFailed,
1734
- `Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
1735
- );
1736
- }
1737
- }
1738
- if (payloadVersion !== undefined) {
1739
- this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
1740
- }
1741
- }
1742
1262
  this.postProgressMessage(100, 'installingFirmware');
1743
1263
  return true;
1744
1264
  }
@@ -1762,9 +1282,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1762
1282
  private async waitForProtocolV2FirmwareUpdateComplete(
1763
1283
  targets: Array<{ target_id: number; path: string }>
1764
1284
  ) {
1765
- this.protocolV2FinalStatusVerified = false;
1766
1285
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1767
- const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1768
1286
  const startTime = Date.now();
1769
1287
  let lastError: unknown;
1770
1288
 
@@ -1788,11 +1306,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1788
1306
  if (
1789
1307
  this.assertProtocolV2TargetStatus(
1790
1308
  (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1791
- expectedTargetIds,
1792
- expectedPaths
1309
+ expectedTargetIds
1793
1310
  )
1794
1311
  ) {
1795
- this.protocolV2FinalStatusVerified = true;
1796
1312
  return;
1797
1313
  }
1798
1314
  lastError = new Error('Protocol V2 firmware targets are still installing');
@@ -1807,34 +1323,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1807
1323
  // device already rebooted into App, this endpoint error signals that the
1808
1324
  // install phase ended; the later phase performs the final readiness check.
1809
1325
  if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
1810
- try {
1811
- const deviceInfo = await requestProtocolV2DeviceInfo({
1812
- commands: this.device.getCommands(),
1813
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1814
- });
1815
- this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1816
- const observed = await this.device.probeProtocolV2RuntimeState(
1817
- deviceInfo,
1818
- PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
1819
- );
1820
- if (observed.mode === 'normal' && !observed.bootloaderMode) {
1821
- return;
1822
- }
1823
- lastError = new Error(
1824
- 'Protocol V2 status endpoint is unavailable outside normal mode'
1825
- );
1826
- } catch (probeError) {
1827
- lastError = probeError;
1828
- }
1829
- throw ERRORS.TypedError(
1830
- HardwareErrorCode.RuntimeError,
1831
- `Protocol V2 firmware install status is unavailable: ${this.normalizeErrorMessage(
1832
- lastError
1833
- )}`,
1834
- {
1835
- firmwareUpdateCode: 'FirmwareInstallStatusUnavailable',
1836
- }
1326
+ Log.log(
1327
+ '[FirmwareUpdateV4] firmware status endpoint unavailable after reboot; continue with normal-mode verification'
1837
1328
  );
1329
+ return;
1838
1330
  }
1839
1331
  lastError = error;
1840
1332
  Log.log(
@@ -1850,12 +1342,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1850
1342
  if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1851
1343
  throw error;
1852
1344
  }
1853
- if (
1854
- error instanceof HardwareError &&
1855
- error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
1856
- ) {
1857
- throw error;
1858
- }
1859
1345
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1860
1346
  }
1861
1347
  await wait(1000);
@@ -1873,7 +1359,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1873
1359
  // The connection may still be in bootloader. Request a Normal reboot directly;
1874
1360
  // repeating it after an automatic App reboot is idempotent.
1875
1361
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1876
- this.protocolV2ExecutionInLoader = false;
1877
1362
  }
1878
1363
 
1879
1364
  private async waitForProtocolV2FinalFeatures() {
@@ -1884,7 +1369,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1884
1369
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1885
1370
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1886
1371
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
1887
- this.protocolV2LatestFinalFeatures = features;
1888
1372
  if (firmwareVersion === '0.0.0') {
1889
1373
  Log.warn(
1890
1374
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1898,17 +1382,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1898
1382
  };
1899
1383
  }
1900
1384
 
1901
- private async completeProtocolV2FinalVerification() {
1902
- if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
1903
- await this.exitProtocolV2BootloaderToNormal();
1904
- }
1905
- const versions = await this.waitForProtocolV2FinalFeatures();
1906
- this.assertExpectedProtocolV2Versions();
1907
- this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
1908
- DevicePool.resetState();
1909
- return versions;
1910
- }
1911
-
1912
1385
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1913
1386
  const startTime = Date.now();
1914
1387
  let lastError: unknown;
@@ -2015,6 +1488,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2015
1488
  return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
2016
1489
  }
2017
1490
 
1491
+ private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1492
+ let lastError: unknown;
1493
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1494
+ try {
1495
+ return await this.protocolV2WriteWholeFile(params);
1496
+ } catch (error) {
1497
+ lastError = error;
1498
+ Log.error(
1499
+ `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1500
+ error
1501
+ );
1502
+ if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1503
+ break;
1504
+ }
1505
+ await this.recoverProtocolV2FileTransfer();
1506
+ }
1507
+ }
1508
+
1509
+ throw ERRORS.TypedError(
1510
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1511
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1512
+ );
1513
+ }
1514
+
1515
+ private async protocolV2WriteWholeFile({
1516
+ payload,
1517
+ filePath,
1518
+ processedSize,
1519
+ totalSize,
1520
+ onTransferredBytes,
1521
+ }: ProtocolV2FileTransferParams) {
1522
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1523
+ let offset = 0;
1524
+ const getUploadProgress = (fileOffset: number) => {
1525
+ if (totalSize !== undefined && processedSize !== undefined) {
1526
+ return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1527
+ }
1528
+ return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1529
+ };
1530
+
1531
+ while (offset < payload.byteLength) {
1532
+ const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
1533
+ const chunkLength = chunkEnd - offset;
1534
+ const chunk = payload.slice(offset, chunkEnd);
1535
+ const overwrite = offset === 0;
1536
+ const progress = getProtocolV2DeviceTransferProgress(
1537
+ (processedSize ?? 0) + offset,
1538
+ (processedSize ?? 0) + chunkEnd,
1539
+ totalSize ?? payload.byteLength
1540
+ );
1541
+
1542
+ const writeRes = await this.fileWriteChunk(
1543
+ filePath,
1544
+ payload.byteLength,
1545
+ offset,
1546
+ chunk,
1547
+ overwrite,
1548
+ progress
1549
+ );
1550
+ const rawProcessedByte = writeRes.message.processed_byte;
1551
+ const processedByte = Number(rawProcessedByte);
1552
+ const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
1553
+ if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
1554
+ throw ERRORS.TypedError(
1555
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1556
+ `invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
1557
+ );
1558
+ }
1559
+ offset = nextOffset;
1560
+ onTransferredBytes?.((processedSize ?? 0) + offset);
1561
+ this.postProgressMessage(getUploadProgress(offset), 'transferData');
1562
+ }
1563
+
1564
+ return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
1565
+ }
1566
+
1567
+ private getProtocolV2FirmwareTransferTransport() {
1568
+ const env = DataManager.getSettings('env');
1569
+ if (env && DataManager.isBleConnect(env)) {
1570
+ return 'BLE';
1571
+ }
1572
+ if (
1573
+ env &&
1574
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1575
+ ) {
1576
+ return 'WebUSB';
1577
+ }
1578
+ return env ?? 'unknown';
1579
+ }
1580
+
2018
1581
  private async fileWriteChunk(
2019
1582
  filePath: string,
2020
1583
  totalFileSize: number,