@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.41

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 (195) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -78
  2. package/__tests__/DeviceEventRegistration.test.ts +0 -6
  3. package/__tests__/connectSettings.test.ts +47 -5
  4. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  5. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  6. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  7. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  8. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  9. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  10. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  11. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  12. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
  13. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  14. package/__tests__/get-device-state.test.ts +12 -132
  15. package/__tests__/homescreen.test.ts +1 -41
  16. package/__tests__/logBlockEvent.test.ts +1 -1
  17. package/__tests__/open-wallet-session.test.ts +18 -259
  18. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -86
  19. package/__tests__/protocol-v2.test.ts +632 -333
  20. package/__tests__/public-pro2-api-boundary.test.ts +0 -1
  21. package/dist/api/BaseMethod.d.ts +1 -3
  22. package/dist/api/BaseMethod.d.ts.map +1 -1
  23. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  25. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  27. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV4.d.ts +22 -5
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  31. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  32. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  33. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  34. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  35. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  36. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  37. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  38. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  39. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  40. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  41. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  42. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  43. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  44. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  45. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  46. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  47. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
  48. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  49. package/dist/api/firmware/getBinary.d.ts +7 -0
  50. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  51. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  52. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  53. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  54. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  55. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  56. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  57. package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
  58. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  59. package/dist/api/index.d.ts +0 -1
  60. package/dist/api/index.d.ts.map +1 -1
  61. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  62. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  63. package/dist/core/deviceEventRegistration.d.ts +0 -2
  64. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/data-manager/DataManager.d.ts +1 -0
  67. package/dist/data-manager/DataManager.d.ts.map +1 -1
  68. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  69. package/dist/device/Device.d.ts +4 -22
  70. package/dist/device/Device.d.ts.map +1 -1
  71. package/dist/events/device.d.ts +0 -4
  72. package/dist/events/device.d.ts.map +1 -1
  73. package/dist/events/logBlockEvent.d.ts.map +1 -1
  74. package/dist/events/ui-request.d.ts +2 -27
  75. package/dist/events/ui-request.d.ts.map +1 -1
  76. package/dist/index.d.ts +181 -88
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3162 -1614
  79. package/dist/inject.d.ts.map +1 -1
  80. package/dist/lowLevelInject.d.ts.map +1 -1
  81. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  82. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  83. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  84. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  85. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  86. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
  87. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  88. package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
  89. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  90. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -4
  91. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  92. package/dist/topLevelInject.d.ts.map +1 -1
  93. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  94. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  95. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  96. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  97. package/dist/types/api/export.d.ts +4 -1
  98. package/dist/types/api/export.d.ts.map +1 -1
  99. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  100. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  101. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  102. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  103. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  104. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  105. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  106. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  107. package/dist/types/api/index.d.ts +10 -3
  108. package/dist/types/api/index.d.ts.map +1 -1
  109. package/dist/types/api/protocolV2.d.ts +0 -3
  110. package/dist/types/api/protocolV2.d.ts.map +1 -1
  111. package/dist/types/settings.d.ts +13 -0
  112. package/dist/types/settings.d.ts.map +1 -1
  113. package/dist/utils/deviceFeaturesUtils.d.ts +0 -2
  114. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  115. package/dist/utils/homescreen.d.ts +0 -4
  116. package/dist/utils/homescreen.d.ts.map +1 -1
  117. package/dist/utils/index.d.ts +1 -1
  118. package/dist/utils/index.d.ts.map +1 -1
  119. package/dist/utils/patch.d.ts +1 -1
  120. package/dist/utils/patch.d.ts.map +1 -1
  121. package/dist/utils/pro2Wallpaper.d.ts +0 -10
  122. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  123. package/package.json +4 -4
  124. package/src/api/BaseMethod.ts +10 -8
  125. package/src/api/CheckAllFirmwareRelease.ts +51 -6
  126. package/src/api/FirmwareUpdateV2.ts +298 -120
  127. package/src/api/FirmwareUpdateV3.ts +263 -57
  128. package/src/api/FirmwareUpdateV4.ts +748 -281
  129. package/src/api/GetPassphraseState.ts +1 -7
  130. package/src/api/OpenWalletSession.ts +17 -54
  131. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
  132. package/src/api/device/DeviceUnlock.ts +1 -1
  133. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  134. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  135. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  136. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  137. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +57 -0
  138. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  139. package/src/api/firmware/FirmwareUpdatePlan.ts +745 -0
  140. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
  141. package/src/api/firmware/progressThrottle.ts +44 -0
  142. package/src/api/firmware/updateBootloader.ts +19 -4
  143. package/src/api/firmware/uploadFirmware.ts +140 -22
  144. package/src/api/helpers/protocolV2FileWrite.ts +4 -11
  145. package/src/api/index.ts +0 -1
  146. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
  147. package/src/core/deviceEventRegistration.ts +0 -4
  148. package/src/core/index.ts +75 -118
  149. package/src/data/messages/messages-protocol-v2.json +0 -10
  150. package/src/data-manager/DataManager.ts +33 -33
  151. package/src/data-manager/connectSettings.ts +11 -0
  152. package/src/device/Device.ts +21 -193
  153. package/src/events/device.ts +0 -4
  154. package/src/events/logBlockEvent.ts +0 -1
  155. package/src/events/ui-request.ts +2 -32
  156. package/src/index.ts +5 -0
  157. package/src/inject.ts +22 -3
  158. package/src/lowLevelInject.ts +5 -1
  159. package/src/protocols/protocol-v2/features.ts +2 -9
  160. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  161. package/src/protocols/protocol-v2/uiInteraction.ts +5 -31
  162. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
  163. package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
  164. package/src/protocols/protocol-v2/walletSession.ts +18 -93
  165. package/src/topLevelInject.ts +5 -1
  166. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  167. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  168. package/src/types/api/export.ts +18 -0
  169. package/src/types/api/firmwareUpdate.ts +76 -0
  170. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  171. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  172. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  173. package/src/types/api/index.ts +14 -2
  174. package/src/types/api/protocolV2.ts +0 -13
  175. package/src/types/settings.ts +13 -0
  176. package/src/utils/deviceFeaturesUtils.ts +0 -4
  177. package/src/utils/homescreen.ts +6 -32
  178. package/src/utils/index.ts +1 -6
  179. package/src/utils/pro2Wallpaper.ts +8 -35
  180. package/__tests__/deviceUploadNft.test.ts +0 -187
  181. package/__tests__/pro2Nft.test.ts +0 -108
  182. package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -56
  183. package/__tests__/uiProgressThrottle.test.ts +0 -74
  184. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -30
  185. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
  186. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
  187. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
  188. package/dist/utils/pro2Nft.d.ts +0 -29
  189. package/dist/utils/pro2Nft.d.ts.map +0 -1
  190. package/dist/utils/uiProgressThrottle.d.ts +0 -3
  191. package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
  192. package/src/api/protocol-v2/DeviceUploadNft.ts +0 -166
  193. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -117
  194. package/src/utils/pro2Nft.ts +0 -91
  195. package/src/utils/uiProgressThrottle.ts +0 -63
@@ -19,6 +19,7 @@ import {
19
19
  getLogger,
20
20
  } from '../utils';
21
21
  import { getSysResourceBinary } from './firmware/getBinary';
22
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
22
23
  import { DataManager } from '../data-manager';
23
24
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
24
25
  import { DevicePool } from '../device/DevicePool';
@@ -31,11 +32,20 @@ import {
31
32
  getProtocolV2UnknownErrorText,
32
33
  isProtocolV2DeviceDisconnectedError,
33
34
  } from './protocol-v2/helpers';
35
+ import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
36
+ import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
37
+ import {
38
+ assertFirmwareUpdatePreparedPlanBinding,
39
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
40
+ } from './firmware/FirmwareUpdatePreparedPlan';
34
41
 
35
- import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
42
+ import type {
43
+ FirmwareArtifactReference,
44
+ FirmwareUpdateV4Params,
45
+ FirmwareUpdateV4Target,
46
+ } from '../types/api/firmwareUpdate';
36
47
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
37
48
  import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
38
- import type { PROTO } from '../constants';
39
49
  import type { TypedResponseMessage } from '../device/DeviceCommands';
40
50
  import type {
41
51
  Features,
@@ -43,6 +53,7 @@ import type {
43
53
  IProtocolV2FirmwareComponent,
44
54
  IVersionArray,
45
55
  } from '../types';
56
+ import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
46
57
 
47
58
  const Log = getLogger(LoggerNames.Method);
48
59
 
@@ -86,11 +97,6 @@ const getProtocolV2DeviceTransferProgress = (
86
97
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
87
98
  };
88
99
 
89
- const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
90
- const safeElapsedMs = Math.max(elapsedMs, 1);
91
- return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
92
- };
93
-
94
100
  type ProtocolV2FirmwareUpdateStatusTarget = {
95
101
  target_id: number | string;
96
102
  status?: number | string;
@@ -104,17 +110,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
104
110
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
105
111
  kind: ProtocolV2RemoteComponentTarget['kind'];
106
112
  };
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
-
118
113
  /** RESC bundle okpkg written independently to devicePath through FileWrite. */
119
114
  type ProtocolV2ResourceBundleBinary = {
120
115
  name: string;
@@ -137,6 +132,35 @@ type ProtocolV2RemoteComponentTarget = {
137
132
  kind: 'bootloader' | 'firmware';
138
133
  };
139
134
 
135
+ type ProtocolV2InstallSource = {
136
+ fileName: string;
137
+ source: FirmwareByteSource;
138
+ targetId: number;
139
+ kind: ProtocolV2RemoteComponentTarget['kind'];
140
+ };
141
+
142
+ type ProtocolV2ResourceBundleSource = {
143
+ name: string;
144
+ source: FirmwareByteSource;
145
+ devicePath: string;
146
+ version?: IVersionArray;
147
+ payloadHash?: string;
148
+ headerHash?: string;
149
+ };
150
+
151
+ type ProtocolV2ExecutionPhaseKind =
152
+ | 'resource-sync'
153
+ | 'bootloader-install'
154
+ | 'bootloader-verify'
155
+ | 'component-install'
156
+ | 'final-verify';
157
+
158
+ type ProtocolV2ExecutionPhase = {
159
+ kind: ProtocolV2ExecutionPhaseKind;
160
+ installSources: ProtocolV2InstallSource[];
161
+ resourceSources: ProtocolV2ResourceBundleSource[];
162
+ };
163
+
140
164
  type ProtocolV2OkppHeader = {
141
165
  type: string;
142
166
  version: IVersionArray;
@@ -409,6 +433,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
409
433
  return ['V2'] as const;
410
434
  }
411
435
 
436
+ private protocolV2PreparedSources: FirmwareByteSource[] = [];
437
+
438
+ private protocolV2ExecutionInLoader = false;
439
+
440
+ private protocolV2CompletedTargetVersions = new Map<number, number>();
441
+
442
+ private protocolV2LatestFinalFeatures?: Features;
443
+
444
+ private protocolV2FinalStatusVerified = false;
445
+
412
446
  init() {
413
447
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
414
448
  this.requireDeviceMode = [];
@@ -446,10 +480,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
446
480
  { name: 'firmwareType', type: 'string' },
447
481
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
448
482
  { name: 'platform', type: 'string' },
483
+ { name: 'expectedDeviceId', type: 'string' },
449
484
  { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
450
485
  ]);
486
+ if (
487
+ payload.expectedDeviceId !== undefined &&
488
+ (payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
489
+ ) {
490
+ throw ERRORS.TypedError(
491
+ HardwareErrorCode.CallMethodInvalidParameter,
492
+ 'Protocol V2 expected device identity is invalid'
493
+ );
494
+ }
495
+ const hostBinding =
496
+ payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
497
+ ? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
498
+ : undefined;
499
+ if (hostBinding) {
500
+ assertFirmwareUpdatePreparedPlanBinding({
501
+ preparedPlan: payload.preparedPlan,
502
+ executor: 'v4',
503
+ platform: payload.platform,
504
+ scopeTargets: [
505
+ 'boot',
506
+ 'app_v1',
507
+ 'app_v2',
508
+ 'coprocessor',
509
+ 'resource',
510
+ 'se01',
511
+ 'se02',
512
+ 'se03',
513
+ 'se04',
514
+ ],
515
+ bindings: [
516
+ ...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
517
+ artifact
518
+ ? [
519
+ {
520
+ target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
521
+ artifact,
522
+ },
523
+ ]
524
+ : []
525
+ ),
526
+ ...(payload.resourceBundleArtifacts ?? []).map(
527
+ (entry: { name: string; artifact: FirmwareArtifactReference }) => ({
528
+ target: 'resource' as const,
529
+ logicalName: entry.name,
530
+ artifact: entry.artifact,
531
+ })
532
+ ),
533
+ ],
534
+ });
535
+ }
451
536
 
452
537
  this.params = {
538
+ preparedPlan: payload.preparedPlan,
453
539
  chunkSize: payload.chunkSize,
454
540
  forcedUpdateRes: payload.forcedUpdateRes,
455
541
  bootloaderBinary: payload.bootloaderBinary,
@@ -464,7 +550,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
464
550
  resourceBundleFiles: payload.resourceBundleFiles,
465
551
  firmwareType: payload.firmwareType,
466
552
  targetsToUpdate: payload.targetsToUpdate,
553
+ expectedTargetVersions: payload.expectedTargetVersions,
467
554
  platform: payload.platform,
555
+ expectedDeviceId: payload.expectedDeviceId,
556
+ artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
557
+ componentArtifacts: payload.componentArtifacts,
558
+ resourceBundleArtifacts: payload.resourceBundleArtifacts,
468
559
  };
469
560
  }
470
561
 
@@ -498,6 +589,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
498
589
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
499
590
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
500
591
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
592
+ this.validateExpectedTargetVersions();
593
+
594
+ if (
595
+ (this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
596
+ this.params.resourceBundleArtifacts?.length
597
+ ) {
598
+ return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
599
+ }
501
600
 
502
601
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
503
602
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -508,6 +607,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
508
607
  fwBinaryMap = this.collectExplicitTargetBinaries();
509
608
  bootloaderBinary = this.prepareBootloaderBinary();
510
609
  if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
610
+ if (
611
+ this.params.artifactReader ||
612
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
613
+ ) {
614
+ throw ERRORS.TypedError(
615
+ HardwareErrorCode.RuntimeError,
616
+ 'Protocol V2 firmware artifacts must be prepared by the external firmware host',
617
+ {
618
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
619
+ }
620
+ );
621
+ }
511
622
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
512
623
  firmwareType,
513
624
  deviceFeatures
@@ -516,13 +627,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
516
627
  fwBinaryMap = remoteBinaries.fwBinaryMap;
517
628
  installItems = remoteBinaries.installItems;
518
629
  }
519
- resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
520
- if (resourceBundles?.length) {
521
- resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
522
- }
630
+ const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
631
+ firmwareType,
632
+ deviceFeatures
633
+ );
634
+ resourceBundles = resourceDescriptors?.length
635
+ ? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
636
+ : undefined;
523
637
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
524
638
  } catch (err) {
525
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
639
+ throw normalizeFirmwarePreparationError(err);
526
640
  }
527
641
 
528
642
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
@@ -532,22 +646,289 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
532
646
  );
533
647
  }
534
648
 
535
- await this.enterProtocolV2BootloaderMode();
536
-
537
- await this.executeProtocolV2Update({
649
+ return this.executeProtocolV2Update({
538
650
  fwBinaryMap,
539
651
  bootloaderBinary,
540
652
  ...(installItems ? { installItems } : undefined),
541
653
  ...(resourceBundles?.length ? { resourceBundles } : undefined),
542
654
  });
655
+ }
543
656
 
544
- await this.exitProtocolV2BootloaderToNormal();
657
+ private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
658
+ const source = await openFirmwareByteSource({
659
+ artifact,
660
+ reader: this.params.artifactReader,
661
+ });
662
+ if (!source) {
663
+ throw ERRORS.TypedError(
664
+ HardwareErrorCode.RuntimeError,
665
+ `Firmware artifact ${artifact.artifactRef} is not prepared`,
666
+ {
667
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
668
+ }
669
+ );
670
+ }
671
+ this.protocolV2PreparedSources.push(source);
672
+ return source;
673
+ }
545
674
 
546
- const versions = await this.waitForProtocolV2FinalFeatures();
547
- this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
548
- DevicePool.resetState();
675
+ private async openProtocolV2MemorySource(binary: ArrayBuffer) {
676
+ const source = await openFirmwareByteSource({ binary });
677
+ if (!source) {
678
+ throw ERRORS.TypedError(
679
+ HardwareErrorCode.RuntimeError,
680
+ 'Protocol V2 firmware binary is empty'
681
+ );
682
+ }
683
+ this.protocolV2PreparedSources.push(source);
684
+ return source;
685
+ }
549
686
 
550
- return versions;
687
+ private async closeProtocolV2PreparedSources() {
688
+ const sources = this.protocolV2PreparedSources.splice(0);
689
+ await Promise.all(sources.map(source => source.close().catch(() => undefined)));
690
+ }
691
+
692
+ private async prepareProtocolV2InstallSources(
693
+ firmwareType: EFirmwareType,
694
+ features: Features
695
+ ): Promise<ProtocolV2InstallSource[]> {
696
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
697
+ if (!release) {
698
+ throw ERRORS.TypedError(
699
+ HardwareErrorCode.RuntimeError,
700
+ 'Protocol V2 firmware release is unavailable'
701
+ );
702
+ }
703
+ const componentArtifacts = this.params.componentArtifacts ?? {};
704
+ const requestedTargets = new Set(
705
+ this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
706
+ );
707
+ const installSources: ProtocolV2InstallSource[] = [];
708
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
709
+
710
+ for (const [key, component] of this.getRemoteComponentEntries(release)) {
711
+ const target = this.getRemoteComponentTarget(key, component);
712
+ const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
713
+ if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
714
+ const artifact = componentArtifacts[updateTarget];
715
+ if (!artifact) {
716
+ throw ERRORS.TypedError(
717
+ HardwareErrorCode.RuntimeError,
718
+ `Protocol V2 ${updateTarget} artifact is not prepared`,
719
+ {
720
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
721
+ artifactName: updateTarget,
722
+ }
723
+ );
724
+ }
725
+ installSources.push({
726
+ ...target,
727
+ source: await this.openProtocolV2PreparedSource(artifact),
728
+ });
729
+ preparedTargets.add(updateTarget);
730
+ }
731
+ }
732
+
733
+ const unknownTarget = Array.from(requestedTargets).find(
734
+ target => target !== 'resource' && !preparedTargets.has(target)
735
+ );
736
+ if (unknownTarget) {
737
+ throw ERRORS.TypedError(
738
+ HardwareErrorCode.RuntimeError,
739
+ `Protocol V2 release does not contain requested target ${unknownTarget}`
740
+ );
741
+ }
742
+ return installSources;
743
+ }
744
+
745
+ private async prepareProtocolV2ResourceSources(
746
+ firmwareType: EFirmwareType,
747
+ features: Features
748
+ ): Promise<ProtocolV2ResourceBundleSource[]> {
749
+ const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
750
+ const resourceRequested =
751
+ this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
752
+ if (!resourceRequested) {
753
+ return [];
754
+ }
755
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
756
+ const descriptors = release?.resourceBundles ?? [];
757
+ const artifactByName = new Map(
758
+ preparedArtifacts.map(item => [item.name, item.artifact] as const)
759
+ );
760
+ const sources: ProtocolV2ResourceBundleSource[] = [];
761
+ for (const descriptor of descriptors) {
762
+ const artifact = artifactByName.get(descriptor.name);
763
+ if (!artifact) {
764
+ throw ERRORS.TypedError(
765
+ HardwareErrorCode.RuntimeError,
766
+ `Protocol V2 resource bundle ${descriptor.name} is not prepared`,
767
+ {
768
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
769
+ artifactName: descriptor.name,
770
+ }
771
+ );
772
+ }
773
+ const devicePath = validateProtocolV2FilesystemPath(
774
+ descriptor.devicePath,
775
+ 'resourceBundles[].devicePath'
776
+ );
777
+ sources.push({
778
+ name: descriptor.name,
779
+ source: await this.openProtocolV2PreparedSource(artifact),
780
+ devicePath,
781
+ version: descriptor.version,
782
+ payloadHash: descriptor.payloadHash,
783
+ headerHash: descriptor.headerHash,
784
+ });
785
+ artifactByName.delete(descriptor.name);
786
+ }
787
+ if (artifactByName.size > 0) {
788
+ throw ERRORS.TypedError(
789
+ HardwareErrorCode.RuntimeError,
790
+ `Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
791
+ );
792
+ }
793
+ return sources;
794
+ }
795
+
796
+ private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
797
+ try {
798
+ this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
799
+ const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
800
+ const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
801
+ if (installSources.length === 0 && resourceSources.length === 0) {
802
+ throw ERRORS.TypedError(
803
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
804
+ 'No firmware to update'
805
+ );
806
+ }
807
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
808
+
809
+ return await this.executeProtocolV2SourceUpdate({
810
+ installSources,
811
+ resourceSources,
812
+ });
813
+ } finally {
814
+ await this.closeProtocolV2PreparedSources();
815
+ }
816
+ }
817
+
818
+ private validateExpectedTargetVersions() {
819
+ const expected = this.params.expectedTargetVersions;
820
+ if (expected === undefined) return;
821
+ if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
822
+ throw ERRORS.TypedError(
823
+ HardwareErrorCode.RuntimeError,
824
+ 'Protocol V2 expected target versions are invalid'
825
+ );
826
+ }
827
+ for (const [target, version] of Object.entries(expected)) {
828
+ if (
829
+ !Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
830
+ target as FirmwareUpdateV4Target
831
+ ) ||
832
+ typeof version !== 'string' ||
833
+ !/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
834
+ version.length > 64
835
+ ) {
836
+ throw ERRORS.TypedError(
837
+ HardwareErrorCode.RuntimeError,
838
+ 'Protocol V2 expected target version is invalid'
839
+ );
840
+ }
841
+ }
842
+ }
843
+
844
+ private getExpectedProtocolV2TargetVersion(targetId: number) {
845
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
846
+ const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
847
+ if (!version) return undefined;
848
+ const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
849
+ if (
850
+ parts.length !== 3 ||
851
+ parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
852
+ ) {
853
+ return undefined;
854
+ }
855
+ return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
856
+ }
857
+
858
+ private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
859
+ const expected = this.params?.expectedTargetVersions;
860
+ if (!expected) return;
861
+ const features = this.protocolV2LatestFinalFeatures;
862
+ const requestedTargets = this.getProtocolV2RequestedTargets();
863
+ const applicationTargets = requestedTargets.filter(
864
+ target => target === 'app_v1' || target === 'app_v2'
865
+ );
866
+ const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
867
+ boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
868
+ coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
869
+ se01: features?.se01Version ?? undefined,
870
+ se02: features?.se02Version ?? undefined,
871
+ se03: features?.se03Version ?? undefined,
872
+ se04: features?.se04Version ?? undefined,
873
+ };
874
+ if (features && applicationTargets.length === 1) {
875
+ visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
876
+ }
877
+ const expectedEntries = (
878
+ Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
879
+ ).filter(([target]) => !targets || targets.includes(target));
880
+ for (const [target, expectedVersion] of expectedEntries) {
881
+ const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
882
+ ([, mappedTarget]) => mappedTarget === target
883
+ )?.[0];
884
+ const statusVersion =
885
+ targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
886
+ const observedVersion =
887
+ statusVersion === undefined
888
+ ? visibleVersions[target]
889
+ : `${Math.floor(statusVersion / 0x10000) % 0x100}.${
890
+ Math.floor(statusVersion / 0x100) % 0x100
891
+ }.${statusVersion % 0x100}`;
892
+ if (!observedVersion) {
893
+ if (this.protocolV2FinalStatusVerified) {
894
+ Log.warn(
895
+ `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
896
+ );
897
+ } else {
898
+ throw ERRORS.TypedError(
899
+ HardwareErrorCode.FirmwareVerificationFailed,
900
+ `Protocol V2 target ${target} has no observable final version after status fallback`
901
+ );
902
+ }
903
+ } else if (observedVersion !== expectedVersion) {
904
+ throw ERRORS.TypedError(
905
+ HardwareErrorCode.FirmwareVerificationFailed,
906
+ `Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
907
+ );
908
+ }
909
+ }
910
+ }
911
+
912
+ private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
913
+ if (this.params.targetsToUpdate?.length) {
914
+ return [...new Set(this.params.targetsToUpdate)];
915
+ }
916
+ const targets = new Set<FirmwareUpdateV4Target>();
917
+ Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
918
+ targets.add(target as FirmwareUpdateV4Target)
919
+ );
920
+ if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
921
+ targets.add('resource');
922
+ }
923
+ if (this.params.bootloaderBinary) targets.add('boot');
924
+ if (this.params.applicationP1Binary) targets.add('app_v1');
925
+ if (this.params.applicationP2Binary) targets.add('app_v2');
926
+ if (this.params.coprocessorBinary) targets.add('coprocessor');
927
+ if (this.params.se01Binary) targets.add('se01');
928
+ if (this.params.se02Binary) targets.add('se02');
929
+ if (this.params.se03Binary) targets.add('se03');
930
+ if (this.params.se04Binary) targets.add('se04');
931
+ return Array.from(targets);
551
932
  }
552
933
 
553
934
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
@@ -583,7 +964,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
583
964
  private async captureProtocolV2PhysicalIdentity() {
584
965
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
585
966
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
586
- assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
967
+ if (this.params?.preparedPlan) {
968
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
969
+ preparedPlan: this.params.preparedPlan,
970
+ deviceIdentity: serialNumber,
971
+ });
972
+ } else if (this.params?.expectedDeviceId) {
973
+ assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
974
+ } else {
975
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
976
+ }
587
977
  this.protocolV2ExpectedSerialNumber = serialNumber;
588
978
  }
589
979
 
@@ -854,23 +1244,42 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
854
1244
  })) as ProtocolV2ResourceBundleBinary[];
855
1245
  }
856
1246
 
857
- private async prefetchProtocolV2ResourceBundles(
1247
+ private async materializeProtocolV2ResourceBundles(
858
1248
  bundles: ProtocolV2ResourceBundleBinary[]
859
1249
  ): Promise<ProtocolV2ResourceBundleBinary[]> {
860
- const prefetched: ProtocolV2ResourceBundleBinary[] = [];
861
1250
  for (const bundle of bundles) {
862
1251
  let { binary } = bundle;
863
1252
  let downloadedFromRemote = false;
864
1253
  if (binary.byteLength === 0) {
865
1254
  if (!bundle.url) {
866
- throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
1255
+ throw ERRORS.TypedError(
1256
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1257
+ `Missing Protocol V2 RESC bundle binary: ${bundle.name}`
1258
+ );
1259
+ }
1260
+ if (
1261
+ this.params.artifactReader ||
1262
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
1263
+ ) {
1264
+ throw ERRORS.TypedError(
1265
+ HardwareErrorCode.RuntimeError,
1266
+ `Firmware resource bundle ${bundle.name} is not prepared`,
1267
+ {
1268
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1269
+ artifactName: bundle.name,
1270
+ }
1271
+ );
867
1272
  }
868
1273
  Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
869
1274
  ({ binary } = await getSysResourceBinary(bundle.url));
1275
+ bundle.binary = binary;
870
1276
  downloadedFromRemote = true;
871
1277
  }
872
- if (binary.byteLength === 0) {
873
- throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
1278
+ if (bundle.binary.byteLength === 0) {
1279
+ throw ERRORS.TypedError(
1280
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1281
+ `Protocol V2 RESC bundle is empty: ${bundle.name}`
1282
+ );
874
1283
  }
875
1284
  if (downloadedFromRemote) {
876
1285
  const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
@@ -889,82 +1298,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
889
1298
  throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
890
1299
  }
891
1300
  }
892
- prefetched.push({ ...bundle, binary });
893
1301
  }
894
- return prefetched;
895
- }
896
-
897
- /**
898
- * Synchronize RESC bundles to the device.
899
- *
900
- * Manual mode writes supplied binaries directly.
901
- * Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
902
- */
903
- private async syncProtocolV2ResourceBundles(
904
- bundles: ProtocolV2ResourceBundleBinary[],
905
- firmwareSize: number
906
- ): Promise<{ processedSize: number; totalSize: number }> {
907
- const transferStartTime = Date.now();
908
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
909
-
910
- const isRemoteMode = bundles.some(bundle => !!bundle.url);
911
- let bundlesToSync = bundles;
912
-
913
- // Remote binaries were prefetched before bootloader entry. Only device-local
914
- // header comparison and transfer are allowed in this phase.
915
- if (isRemoteMode) {
916
- const filtered: ProtocolV2ResourceBundleBinary[] = [];
917
- for (const bundle of bundles) {
918
- // Compare the existing device header.
919
- const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
920
- if (upToDate) {
921
- Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
922
- } else {
923
- filtered.push(bundle);
924
- }
925
- }
926
- bundlesToSync = filtered;
927
- }
928
-
929
- if (bundlesToSync.length === 0) {
930
- Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
931
- return { processedSize: 0, totalSize: firmwareSize };
932
- }
933
-
934
- // Write directly to the device through FileWrite.
935
- let totalSize = 0;
936
- for (const b of bundlesToSync) totalSize += b.binary.byteLength;
937
-
938
- const transferTotalSize = totalSize + firmwareSize;
939
- let processedSize = 0;
940
- for (const bundle of bundlesToSync) {
941
- Log.log(
942
- `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
943
- );
944
- processedSize = await this.protocolV2CommonUpdateProcess({
945
- payload: bundle.binary,
946
- filePath: bundle.devicePath,
947
- processedSize,
948
- totalSize: transferTotalSize,
949
- });
950
- }
951
-
952
- const elapsedMs = Date.now() - transferStartTime;
953
- Log.log(
954
- `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
955
- elapsedMs / 1000
956
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
957
- );
958
- return { processedSize, totalSize: transferTotalSize };
1302
+ return bundles;
959
1303
  }
960
1304
 
961
1305
  /**
962
1306
  * Compare the existing okpkg OKPP header in remote-config mode.
963
1307
  */
964
1308
  private async isProtocolV2ResourceBundleUpToDate(
965
- bundle: ProtocolV2ResourceBundleBinary
1309
+ bundle: Pick<
1310
+ ProtocolV2ResourceBundleBinary,
1311
+ 'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1312
+ >
966
1313
  ): Promise<boolean> {
967
- if (this.params.forcedUpdateRes) return false;
1314
+ if (this.params?.forcedUpdateRes) return false;
968
1315
  if (!bundle.version && !bundle.payloadHash) return false;
969
1316
 
970
1317
  try {
@@ -1015,10 +1362,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1015
1362
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
1016
1363
  if (this.isProtocolV2RomloaderMode()) {
1017
1364
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
1365
+ this.protocolV2ExecutionInLoader = true;
1018
1366
  return false;
1019
1367
  }
1020
1368
  if (this.isProtocolV2BootloaderMode()) {
1021
1369
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
1370
+ this.protocolV2ExecutionInLoader = true;
1022
1371
  return false;
1023
1372
  }
1024
1373
 
@@ -1028,6 +1377,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1028
1377
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1029
1378
  await wait(1000);
1030
1379
  await this.waitForProtocolV2BootloaderMode();
1380
+ this.protocolV2ExecutionInLoader = true;
1031
1381
  return true;
1032
1382
  } catch (error) {
1033
1383
  if (error instanceof HardwareError) {
@@ -1117,6 +1467,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1117
1467
  return entries;
1118
1468
  }
1119
1469
 
1470
+ private buildProtocolV2ExecutionPhases({
1471
+ installSources,
1472
+ resourceSources,
1473
+ }: {
1474
+ installSources: ProtocolV2InstallSource[];
1475
+ resourceSources: ProtocolV2ResourceBundleSource[];
1476
+ }): ProtocolV2ExecutionPhase[] {
1477
+ const phases: ProtocolV2ExecutionPhase[] = [];
1478
+ if (resourceSources.length > 0) {
1479
+ phases.push({
1480
+ kind: 'resource-sync',
1481
+ installSources: [],
1482
+ resourceSources,
1483
+ });
1484
+ }
1485
+ const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
1486
+ if (bootloaderSources.length > 0) {
1487
+ phases.push(
1488
+ {
1489
+ kind: 'bootloader-install',
1490
+ installSources: bootloaderSources,
1491
+ resourceSources: [],
1492
+ },
1493
+ {
1494
+ kind: 'bootloader-verify',
1495
+ installSources: [],
1496
+ resourceSources: [],
1497
+ }
1498
+ );
1499
+ }
1500
+ const componentSources = installSources.filter(source => source.kind !== 'bootloader');
1501
+ if (componentSources.length > 0) {
1502
+ phases.push({
1503
+ kind: 'component-install',
1504
+ installSources: componentSources,
1505
+ resourceSources: [],
1506
+ });
1507
+ }
1508
+ phases.push({
1509
+ kind: 'final-verify',
1510
+ installSources: [],
1511
+ resourceSources: [],
1512
+ });
1513
+ return phases;
1514
+ }
1515
+
1516
+ private async executeProtocolV2Phases({
1517
+ installSources,
1518
+ resourceSources,
1519
+ }: {
1520
+ installSources: ProtocolV2InstallSource[];
1521
+ resourceSources: ProtocolV2ResourceBundleSource[];
1522
+ }) {
1523
+ const phases = this.buildProtocolV2ExecutionPhases({
1524
+ installSources,
1525
+ resourceSources,
1526
+ });
1527
+ for (const phase of phases) {
1528
+ if (phase.kind === 'final-verify') {
1529
+ return this.completeProtocolV2FinalVerification();
1530
+ }
1531
+ if (phase.kind === 'resource-sync') {
1532
+ await this.enterProtocolV2BootloaderMode();
1533
+ await this.executeProtocolV2TransferPhase(phase);
1534
+ } else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
1535
+ await this.enterProtocolV2BootloaderMode();
1536
+ await this.executeProtocolV2TransferPhase(phase);
1537
+ await this.exitProtocolV2BootloaderToNormal();
1538
+ } else if (phase.kind === 'bootloader-verify') {
1539
+ await this.waitForProtocolV2FinalFeatures();
1540
+ this.assertExpectedProtocolV2Versions(['boot']);
1541
+ }
1542
+ }
1543
+ throw ERRORS.TypedError(
1544
+ HardwareErrorCode.RuntimeError,
1545
+ 'Protocol V2 execution has no final verification phase'
1546
+ );
1547
+ }
1548
+
1549
+ private async executeProtocolV2SourceUpdate({
1550
+ installSources,
1551
+ resourceSources,
1552
+ }: {
1553
+ installSources: ProtocolV2InstallSource[];
1554
+ resourceSources: ProtocolV2ResourceBundleSource[];
1555
+ }) {
1556
+ return this.executeProtocolV2Phases({
1557
+ installSources,
1558
+ resourceSources,
1559
+ });
1560
+ }
1561
+
1120
1562
  private async executeProtocolV2Update({
1121
1563
  fwBinaryMap,
1122
1564
  bootloaderBinary,
@@ -1128,104 +1570,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1128
1570
  installItems?: ProtocolV2InstallItem[];
1129
1571
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1130
1572
  }) {
1131
- const orderedInstallItems =
1573
+ const memoryInstallItems =
1132
1574
  installItems ??
1133
1575
  this.buildProtocolV2InstallItems({
1134
- bootloaderBinary: bootloaderBinary ?? null,
1135
1576
  fwBinaryMap: fwBinaryMap ?? [],
1577
+ bootloaderBinary: bootloaderBinary ?? null,
1136
1578
  });
1137
-
1138
- let firmwareSize = 0;
1139
- for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1140
-
1141
- this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1142
-
1143
- let processedSize = 0;
1144
- let totalSize = firmwareSize;
1145
- if (resourceBundles?.length) {
1146
- const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1147
- resourceBundles,
1148
- firmwareSize
1579
+ try {
1580
+ const installSources = await Promise.all(
1581
+ memoryInstallItems.map(async item => ({
1582
+ fileName: item.fileName,
1583
+ source: await this.openProtocolV2MemorySource(item.binary),
1584
+ targetId: item.targetId,
1585
+ kind: item.kind,
1586
+ }))
1587
+ );
1588
+ const resourceSources = await Promise.all(
1589
+ (resourceBundles ?? []).map(async bundle => ({
1590
+ name: bundle.name,
1591
+ source: await this.openProtocolV2MemorySource(bundle.binary),
1592
+ devicePath: bundle.devicePath,
1593
+ version: bundle.version,
1594
+ payloadHash: bundle.payloadHash,
1595
+ headerHash: bundle.headerHash,
1596
+ }))
1149
1597
  );
1150
- processedSize = resourceTransfer.processedSize;
1151
- totalSize = resourceTransfer.totalSize;
1598
+ return await this.executeProtocolV2Phases({
1599
+ installSources,
1600
+ resourceSources,
1601
+ });
1602
+ } finally {
1603
+ await this.closeProtocolV2PreparedSources();
1152
1604
  }
1605
+ }
1153
1606
 
1154
- // Skip staging and installation when the update contains RESC bundles only.
1155
- if (orderedInstallItems.length === 0) {
1156
- Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1157
- if (totalSize > 0) {
1158
- this.postProgressMessage(100, 'transferData');
1607
+ private async executeProtocolV2TransferPhase({
1608
+ installSources,
1609
+ resourceSources,
1610
+ }: ProtocolV2ExecutionPhase) {
1611
+ let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
1612
+ const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
1613
+ for (const resource of resourceSources) {
1614
+ if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
1615
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
1616
+ } else {
1617
+ resourcesToSync.push(resource);
1618
+ totalSize += resource.source.size;
1159
1619
  }
1160
- return;
1161
1620
  }
1162
- let transferredSize = processedSize;
1163
- const transferStartTime = Date.now();
1164
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1165
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1166
- const onTransferredBytes = (bytes: number) => {
1167
- transferredSize = bytes;
1168
- };
1169
- Log.log(
1170
- `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1171
- );
1172
1621
 
1173
- const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1174
-
1175
- try {
1176
- for (const item of orderedInstallItems) {
1177
- const filePath = this.getProtocolV2InstallItemStagingPath(item);
1178
- Log.log(
1179
- `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1180
- );
1181
- processedSize = await this.protocolV2CommonUpdateProcess({
1182
- payload: item.binary,
1183
- filePath,
1184
- processedSize,
1185
- totalSize,
1186
- onTransferredBytes,
1187
- });
1188
- transferredSize = processedSize;
1189
- await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1190
-
1191
- stagedInstallTargets.push({
1192
- ...item,
1193
- path: filePath,
1194
- });
1195
- }
1622
+ this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1623
+ let processedSize = 0;
1624
+ for (const resource of resourcesToSync) {
1625
+ processedSize = await this.protocolV2SourceUpdateProcess({
1626
+ source: resource.source,
1627
+ filePath: resource.devicePath,
1628
+ processedSize,
1629
+ totalSize,
1630
+ });
1631
+ await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
1632
+ }
1196
1633
 
1197
- if (totalSize > 0) {
1198
- this.postProgressMessage(100, 'transferData');
1199
- }
1634
+ const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
1635
+ for (const item of installSources) {
1636
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1637
+ processedSize = await this.protocolV2SourceUpdateProcess({
1638
+ source: item.source,
1639
+ filePath,
1640
+ processedSize,
1641
+ totalSize,
1642
+ });
1643
+ await this.verifyProtocolV2StagedFile(filePath, item.source.size);
1644
+ stagedInstallTargets.push({
1645
+ targetId: item.targetId,
1646
+ path: filePath,
1647
+ });
1648
+ }
1200
1649
 
1201
- const elapsedMs = Date.now() - transferStartTime;
1202
- Log.log(
1203
- `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1204
- elapsedMs / 1000
1205
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1206
- );
1207
- } catch (error) {
1208
- const elapsedMs = Date.now() - transferStartTime;
1209
- Log.warn(
1210
- `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1211
- elapsedMs / 1000
1212
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1213
- );
1214
- throw error;
1650
+ if (totalSize > 0) {
1651
+ this.postProgressMessage(100, 'transferData');
1652
+ }
1653
+ if (stagedInstallTargets.length === 0) {
1654
+ return;
1215
1655
  }
1216
1656
 
1217
1657
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1218
-
1219
- const allTargets = stagedInstallTargets.map(item => ({
1658
+ const targets = stagedInstallTargets.map(item => ({
1220
1659
  target_id: item.targetId,
1221
1660
  path: item.path,
1222
1661
  }));
1223
- Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1224
- await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1225
- await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1662
+ await this.protocolV2StartFirmwareUpdate({ targets });
1663
+ await this.waitForProtocolV2FirmwareUpdateComplete(targets);
1664
+ }
1665
+
1666
+ private async protocolV2SourceUpdateProcess({
1667
+ source,
1668
+ filePath,
1669
+ processedSize,
1670
+ totalSize,
1671
+ }: {
1672
+ source: FirmwareByteSource;
1673
+ filePath: string;
1674
+ processedSize: number;
1675
+ totalSize: number;
1676
+ }) {
1677
+ let lastError: unknown;
1678
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1679
+ try {
1680
+ await writeFirmwareByteSource({
1681
+ source,
1682
+ chunkSize: this.getProtocolV2FirmwareChunkSize(),
1683
+ write: async ({ data, sourceOffset, length, first }) => {
1684
+ const chunkEnd = sourceOffset + length;
1685
+ const progress = getProtocolV2DeviceTransferProgress(
1686
+ processedSize + sourceOffset,
1687
+ processedSize + chunkEnd,
1688
+ totalSize
1689
+ );
1690
+ const response = await this.fileWriteChunk(
1691
+ filePath,
1692
+ source.size,
1693
+ sourceOffset,
1694
+ data,
1695
+ first,
1696
+ progress
1697
+ );
1698
+ const rawProcessedByte = response.message.processed_byte;
1699
+ const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
1700
+ if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
1701
+ throw ERRORS.TypedError(
1702
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1703
+ `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
1704
+ );
1705
+ }
1706
+ this.postProgressMessage(progress, 'transferData');
1707
+ return length;
1708
+ },
1709
+ });
1710
+ return processedSize + source.size;
1711
+ } catch (error) {
1712
+ lastError = error;
1713
+ if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1714
+ await this.recoverProtocolV2FileTransfer();
1715
+ }
1716
+ }
1717
+ }
1718
+ throw ERRORS.TypedError(
1719
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1720
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1721
+ );
1226
1722
  }
1227
1723
 
1228
- private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1724
+ private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1229
1725
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1230
1726
  }
1231
1727
 
@@ -1244,7 +1740,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1244
1740
 
1245
1741
  private assertProtocolV2TargetStatus(
1246
1742
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1247
- expectedTargetIds: Set<number>
1743
+ expectedTargetIds: Set<number>,
1744
+ expectedPaths = new Map<number, string>()
1248
1745
  ) {
1249
1746
  Log.log(
1250
1747
  `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
@@ -1275,17 +1772,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1275
1772
  const matchingTargets = statusTargets.filter(target =>
1276
1773
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1277
1774
  );
1775
+ const seenTargetIds = new Set<number>();
1776
+ for (const target of matchingTargets) {
1777
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
1778
+ if (
1779
+ targetId === undefined ||
1780
+ seenTargetIds.has(targetId) ||
1781
+ (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
1782
+ ) {
1783
+ throw ERRORS.TypedError(
1784
+ HardwareErrorCode.RuntimeError,
1785
+ `Protocol V2 install status conflicts with target ${target.target_id}`
1786
+ );
1787
+ }
1788
+ seenTargetIds.add(targetId);
1789
+ }
1790
+ const completedTargets = matchingTargets.filter(target =>
1791
+ isProtocolV2TargetStatusFinished(target.status)
1792
+ );
1278
1793
  const completedTargetIds = new Set<number>();
1279
- matchingTargets.forEach(target => {
1794
+ completedTargets.forEach(target => {
1280
1795
  const targetId = normalizeProtocolV2TargetId(target.target_id);
1281
- if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
1796
+ if (targetId !== undefined) {
1282
1797
  completedTargetIds.add(targetId);
1283
1798
  }
1284
1799
  });
1285
- const allExpectedTargetsCompleted =
1286
- expectedTargetIds.size > 0 &&
1287
- Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
1288
- if (allExpectedTargetsCompleted) {
1800
+ if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
1801
+ for (const target of completedTargets) {
1802
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
1803
+ const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
1804
+ if (targetId === undefined) {
1805
+ throw ERRORS.TypedError(
1806
+ HardwareErrorCode.RuntimeError,
1807
+ 'Protocol V2 completed target identity is invalid'
1808
+ );
1809
+ }
1810
+ const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
1811
+ if (expectedVersion !== undefined && payloadVersion !== undefined) {
1812
+ if (payloadVersion !== expectedVersion) {
1813
+ throw ERRORS.TypedError(
1814
+ HardwareErrorCode.FirmwareVerificationFailed,
1815
+ `Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
1816
+ );
1817
+ }
1818
+ }
1819
+ if (payloadVersion !== undefined) {
1820
+ this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
1821
+ }
1822
+ }
1289
1823
  this.postProgressMessage(100, 'installingFirmware');
1290
1824
  return true;
1291
1825
  }
@@ -1309,7 +1843,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1309
1843
  private async waitForProtocolV2FirmwareUpdateComplete(
1310
1844
  targets: Array<{ target_id: number; path: string }>
1311
1845
  ) {
1846
+ this.protocolV2FinalStatusVerified = false;
1312
1847
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1848
+ const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1313
1849
  const startTime = Date.now();
1314
1850
  let lastError: unknown;
1315
1851
 
@@ -1333,9 +1869,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1333
1869
  if (
1334
1870
  this.assertProtocolV2TargetStatus(
1335
1871
  (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1336
- expectedTargetIds
1872
+ expectedTargetIds,
1873
+ expectedPaths
1337
1874
  )
1338
1875
  ) {
1876
+ this.protocolV2FinalStatusVerified = true;
1339
1877
  return;
1340
1878
  }
1341
1879
  lastError = new Error('Protocol V2 firmware targets are still installing');
@@ -1374,6 +1912,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1374
1912
  if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1375
1913
  throw error;
1376
1914
  }
1915
+ if (
1916
+ error instanceof HardwareError &&
1917
+ error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
1918
+ ) {
1919
+ throw error;
1920
+ }
1377
1921
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1378
1922
  }
1379
1923
  await wait(1000);
@@ -1391,6 +1935,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1391
1935
  // The connection may still be in bootloader. Request a Normal reboot directly;
1392
1936
  // repeating it after an automatic App reboot is idempotent.
1393
1937
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1938
+ this.protocolV2ExecutionInLoader = false;
1394
1939
  }
1395
1940
 
1396
1941
  private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
@@ -1409,6 +1954,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1409
1954
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1410
1955
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1411
1956
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
1957
+ this.protocolV2LatestFinalFeatures = features;
1412
1958
  if (firmwareVersion === '0.0.0') {
1413
1959
  Log.warn(
1414
1960
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1422,6 +1968,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1422
1968
  };
1423
1969
  }
1424
1970
 
1971
+ private async completeProtocolV2FinalVerification() {
1972
+ if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
1973
+ await this.exitProtocolV2BootloaderToNormal();
1974
+ }
1975
+ const versions = await this.waitForProtocolV2FinalFeatures();
1976
+ this.assertExpectedProtocolV2Versions();
1977
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
1978
+ DevicePool.resetState();
1979
+ return versions;
1980
+ }
1981
+
1425
1982
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1426
1983
  const startTime = Date.now();
1427
1984
  let lastError: unknown;
@@ -1528,96 +2085,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1528
2085
  return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
1529
2086
  }
1530
2087
 
1531
- private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1532
- let lastError: unknown;
1533
- for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1534
- try {
1535
- return await this.protocolV2WriteWholeFile(params);
1536
- } catch (error) {
1537
- lastError = error;
1538
- Log.error(
1539
- `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1540
- error
1541
- );
1542
- if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1543
- break;
1544
- }
1545
- await this.recoverProtocolV2FileTransfer();
1546
- }
1547
- }
1548
-
1549
- throw ERRORS.TypedError(
1550
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1551
- `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1552
- );
1553
- }
1554
-
1555
- private async protocolV2WriteWholeFile({
1556
- payload,
1557
- filePath,
1558
- processedSize,
1559
- totalSize,
1560
- onTransferredBytes,
1561
- }: ProtocolV2FileTransferParams) {
1562
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1563
- let offset = 0;
1564
- const getUploadProgress = (fileOffset: number) => {
1565
- if (totalSize !== undefined && processedSize !== undefined) {
1566
- return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1567
- }
1568
- return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1569
- };
1570
-
1571
- while (offset < payload.byteLength) {
1572
- const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
1573
- const chunkLength = chunkEnd - offset;
1574
- const chunk = payload.slice(offset, chunkEnd);
1575
- const overwrite = offset === 0;
1576
- const progress = getProtocolV2DeviceTransferProgress(
1577
- (processedSize ?? 0) + offset,
1578
- (processedSize ?? 0) + chunkEnd,
1579
- totalSize ?? payload.byteLength
1580
- );
1581
-
1582
- const writeRes = await this.fileWriteChunk(
1583
- filePath,
1584
- payload.byteLength,
1585
- offset,
1586
- chunk,
1587
- overwrite,
1588
- progress
1589
- );
1590
- const rawProcessedByte = writeRes.message.processed_byte;
1591
- const processedByte = Number(rawProcessedByte);
1592
- const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
1593
- if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
1594
- throw ERRORS.TypedError(
1595
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1596
- `invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
1597
- );
1598
- }
1599
- offset = nextOffset;
1600
- onTransferredBytes?.((processedSize ?? 0) + offset);
1601
- this.postProgressMessage(getUploadProgress(offset), 'transferData');
1602
- }
1603
-
1604
- return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
1605
- }
1606
-
1607
- private getProtocolV2FirmwareTransferTransport() {
1608
- const env = DataManager.getSettings('env');
1609
- if (env && DataManager.isBleConnect(env)) {
1610
- return 'BLE';
1611
- }
1612
- if (
1613
- env &&
1614
- (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1615
- ) {
1616
- return 'WebUSB';
1617
- }
1618
- return env ?? 'unknown';
1619
- }
1620
-
1621
2088
  private async fileWriteChunk(
1622
2089
  filePath: string,
1623
2090
  totalFileSize: number,