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

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 (202) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
  2. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +14 -1
  4. package/__tests__/connectSettings.test.ts +5 -47
  5. package/__tests__/deviceUploadNft.test.ts +272 -0
  6. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  7. package/__tests__/get-device-state.test.ts +132 -12
  8. package/__tests__/homescreen.test.ts +41 -1
  9. package/__tests__/logBlockEvent.test.ts +1 -1
  10. package/__tests__/open-wallet-session.test.ts +366 -31
  11. package/__tests__/pro2Nft.test.ts +108 -0
  12. package/__tests__/protocol-v2-resources.test.ts +204 -0
  13. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  14. package/__tests__/protocol-v2-unlock-policy.test.ts +196 -35
  15. package/__tests__/protocol-v2.test.ts +568 -957
  16. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  17. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  18. package/__tests__/uiProgressThrottle.test.ts +74 -0
  19. package/dist/api/BaseMethod.d.ts +3 -1
  20. package/dist/api/BaseMethod.d.ts.map +1 -1
  21. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  22. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  23. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  25. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV4.d.ts +6 -25
  27. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  28. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  29. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  30. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  32. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -8
  34. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  35. package/dist/api/firmware/getBinary.d.ts +0 -7
  36. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  37. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  38. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  39. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  40. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  41. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  42. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  43. package/dist/api/index.d.ts +1 -0
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  46. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  48. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  49. package/dist/core/deviceEventRegistration.d.ts +2 -0
  50. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/data-manager/DataManager.d.ts +3 -2
  53. package/dist/data-manager/DataManager.d.ts.map +1 -1
  54. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  55. package/dist/device/Device.d.ts +24 -4
  56. package/dist/device/Device.d.ts.map +1 -1
  57. package/dist/device/DeviceCommands.d.ts +4 -4
  58. package/dist/events/device.d.ts +4 -0
  59. package/dist/events/device.d.ts.map +1 -1
  60. package/dist/events/logBlockEvent.d.ts.map +1 -1
  61. package/dist/events/ui-request.d.ts +28 -2
  62. package/dist/events/ui-request.d.ts.map +1 -1
  63. package/dist/index.d.ts +115 -184
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2051 -3349
  66. package/dist/inject.d.ts.map +1 -1
  67. package/dist/lowLevelInject.d.ts.map +1 -1
  68. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  69. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  70. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  71. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  72. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  73. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  74. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  75. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  76. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  77. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  78. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  79. package/dist/topLevelInject.d.ts.map +1 -1
  80. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -4
  81. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  82. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  83. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  84. package/dist/types/api/export.d.ts +1 -4
  85. package/dist/types/api/export.d.ts.map +1 -1
  86. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  87. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  88. package/dist/types/api/index.d.ts +3 -10
  89. package/dist/types/api/index.d.ts.map +1 -1
  90. package/dist/types/api/protocolV2.d.ts +3 -0
  91. package/dist/types/api/protocolV2.d.ts.map +1 -1
  92. package/dist/types/settings.d.ts +12 -13
  93. package/dist/types/settings.d.ts.map +1 -1
  94. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  95. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  96. package/dist/utils/homescreen.d.ts +4 -0
  97. package/dist/utils/homescreen.d.ts.map +1 -1
  98. package/dist/utils/index.d.ts +1 -1
  99. package/dist/utils/index.d.ts.map +1 -1
  100. package/dist/utils/patch.d.ts +1 -1
  101. package/dist/utils/patch.d.ts.map +1 -1
  102. package/dist/utils/pro2Nft.d.ts +31 -0
  103. package/dist/utils/pro2Nft.d.ts.map +1 -0
  104. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  105. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  106. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  107. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  108. package/package.json +4 -4
  109. package/src/api/BaseMethod.ts +8 -10
  110. package/src/api/CheckAllFirmwareRelease.ts +40 -53
  111. package/src/api/FirmwareUpdateV2.ts +120 -298
  112. package/src/api/FirmwareUpdateV3.ts +57 -263
  113. package/src/api/FirmwareUpdateV4.ts +307 -912
  114. package/src/api/GetPassphraseState.ts +7 -1
  115. package/src/api/OpenWalletSession.ts +54 -17
  116. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  117. package/src/api/device/DeviceUnlock.ts +1 -1
  118. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  119. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -57
  120. package/src/api/firmware/updateBootloader.ts +4 -19
  121. package/src/api/firmware/uploadFirmware.ts +22 -140
  122. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  123. package/src/api/index.ts +1 -0
  124. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  125. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  126. package/src/core/deviceEventRegistration.ts +4 -0
  127. package/src/core/index.ts +136 -77
  128. package/src/data/messages/messages-protocol-v2.json +68 -0
  129. package/src/data-manager/DataManager.ts +63 -38
  130. package/src/data-manager/connectSettings.ts +0 -11
  131. package/src/device/Device.ts +203 -21
  132. package/src/events/device.ts +4 -0
  133. package/src/events/logBlockEvent.ts +1 -0
  134. package/src/events/ui-request.ts +33 -2
  135. package/src/index.ts +0 -5
  136. package/src/inject.ts +3 -22
  137. package/src/lowLevelInject.ts +1 -5
  138. package/src/protocols/protocol-v2/features.ts +9 -2
  139. package/src/protocols/protocol-v2/resources.ts +233 -0
  140. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  141. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  142. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +124 -0
  143. package/src/protocols/protocol-v2/walletSession.ts +154 -30
  144. package/src/topLevelInject.ts +1 -5
  145. package/src/types/api/checkAllFirmwareRelease.ts +1 -4
  146. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  147. package/src/types/api/export.ts +0 -18
  148. package/src/types/api/firmwareUpdate.ts +0 -76
  149. package/src/types/api/index.ts +2 -14
  150. package/src/types/api/protocolV2.ts +13 -0
  151. package/src/types/settings.ts +26 -13
  152. package/src/utils/deviceFeaturesUtils.ts +4 -0
  153. package/src/utils/homescreen.ts +32 -6
  154. package/src/utils/index.ts +6 -1
  155. package/src/utils/pro2Nft.ts +142 -0
  156. package/src/utils/pro2Wallpaper.ts +35 -8
  157. package/src/utils/uiProgressThrottle.ts +63 -0
  158. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  159. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  160. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  161. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  162. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  163. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  164. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  165. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  166. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  167. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  168. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  169. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  170. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  171. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  172. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  173. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  174. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  175. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  176. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  177. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  178. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  179. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  180. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  181. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  182. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  183. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  184. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  185. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  186. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  187. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  188. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  189. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  190. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  191. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  192. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  193. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  194. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  195. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  196. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  197. package/src/api/firmware/progressThrottle.ts +0 -44
  198. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  199. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  200. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  201. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  202. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -2,7 +2,6 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
2
2
  import {
3
3
  DeviceRebootType,
4
4
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
5
- PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
6
5
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
7
6
  } from '@onekeyfe/hd-transport';
8
7
  import { sha256 } from '@noble/hashes/sha256';
@@ -19,7 +18,6 @@ import {
19
18
  getLogger,
20
19
  } from '../utils';
21
20
  import { getSysResourceBinary } from './firmware/getBinary';
22
- import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
23
21
  import { DataManager } from '../data-manager';
24
22
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
25
23
  import { DevicePool } from '../device/DevicePool';
@@ -28,32 +26,29 @@ import {
28
26
  ProtocolV2FirmwareTargetType,
29
27
  } from '../protocols/protocol-v2';
30
28
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
29
+ import {
30
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
31
+ buildProtocolV2ResourceUpdatePlan,
32
+ isProtocolV2ResourceFileValid,
33
+ requestProtocolV2ResourceInventory,
34
+ } from '../protocols/protocol-v2/resources';
31
35
  import {
32
36
  getProtocolV2UnknownErrorText,
33
37
  isProtocolV2DeviceDisconnectedError,
34
38
  } 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';
41
39
 
42
- import type {
43
- FirmwareArtifactReference,
44
- FirmwareUpdateV4Params,
45
- FirmwareUpdateV4Target,
46
- } from '../types/api/firmwareUpdate';
40
+ import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
47
41
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
48
42
  import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
43
+ import type { PROTO } from '../constants';
49
44
  import type { TypedResponseMessage } from '../device/DeviceCommands';
50
45
  import type {
51
46
  Features,
52
47
  IFirmwareReleaseInfo,
53
48
  IProtocolV2FirmwareComponent,
49
+ IProtocolV2Resource,
54
50
  IVersionArray,
55
51
  } from '../types';
56
- import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
57
52
 
58
53
  const Log = getLogger(LoggerNames.Method);
59
54
 
@@ -97,6 +92,11 @@ const getProtocolV2DeviceTransferProgress = (
97
92
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
98
93
  };
99
94
 
95
+ const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
96
+ const safeElapsedMs = Math.max(elapsedMs, 1);
97
+ return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
98
+ };
99
+
100
100
  type ProtocolV2FirmwareUpdateStatusTarget = {
101
101
  target_id: number | string;
102
102
  status?: number | string;
@@ -110,16 +110,22 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
110
110
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
111
111
  kind: ProtocolV2RemoteComponentTarget['kind'];
112
112
  };
113
+ type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
114
+ path: string;
115
+ };
116
+
117
+ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
118
+ filePath: string;
119
+ processedSize?: number;
120
+ totalSize?: number;
121
+ onTransferredBytes?: (transferredBytes: number) => void;
122
+ };
123
+
113
124
  /** RESC bundle okpkg written independently to devicePath through FileWrite. */
114
125
  type ProtocolV2ResourceBundleBinary = {
115
126
  name: string;
116
127
  binary: ArrayBuffer;
117
128
  devicePath: string;
118
- /** Download URL for remote-config mode; omitted in manual mode. */
119
- url?: string;
120
- version?: IVersionArray;
121
- payloadHash?: string;
122
- headerHash?: string;
123
129
  };
124
130
 
125
131
  type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
@@ -132,35 +138,6 @@ type ProtocolV2RemoteComponentTarget = {
132
138
  kind: 'bootloader' | 'firmware';
133
139
  };
134
140
 
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
-
164
141
  type ProtocolV2OkppHeader = {
165
142
  type: string;
166
143
  version: IVersionArray;
@@ -300,18 +277,6 @@ const normalizeProtocolV2TargetStatus = (
300
277
 
301
278
  const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
302
279
 
303
- const versionArrayToNumber = (version?: IVersionArray) => {
304
- if (!version) return undefined;
305
- return version[0] * 0x10000 + version[1] * 0x100 + version[2];
306
- };
307
-
308
- const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
309
- const currentNumber = versionArrayToNumber(current);
310
- const targetNumber = versionArrayToNumber(target);
311
- if (currentNumber === undefined || targetNumber === undefined) return undefined;
312
- return currentNumber - targetNumber;
313
- };
314
-
315
280
  const bytesToHex = (bytes: Uint8Array) =>
316
281
  Array.from(bytes)
317
282
  .map(byte => byte.toString(16).padStart(2, '0'))
@@ -433,16 +398,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
433
398
  return ['V2'] as const;
434
399
  }
435
400
 
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
-
446
401
  init() {
447
402
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
448
403
  this.requireDeviceMode = [];
@@ -480,62 +435,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
480
435
  { name: 'firmwareType', type: 'string' },
481
436
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
482
437
  { name: 'platform', type: 'string' },
483
- { name: 'expectedDeviceId', type: 'string' },
484
438
  { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
485
439
  ]);
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
- }
536
440
 
537
441
  this.params = {
538
- preparedPlan: payload.preparedPlan,
539
442
  chunkSize: payload.chunkSize,
540
443
  forcedUpdateRes: payload.forcedUpdateRes,
541
444
  bootloaderBinary: payload.bootloaderBinary,
@@ -550,25 +453,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
550
453
  resourceBundleFiles: payload.resourceBundleFiles,
551
454
  firmwareType: payload.firmwareType,
552
455
  targetsToUpdate: payload.targetsToUpdate,
553
- expectedTargetVersions: payload.expectedTargetVersions,
554
456
  platform: payload.platform,
555
- expectedDeviceId: payload.expectedDeviceId,
556
- artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
557
- componentArtifacts: payload.componentArtifacts,
558
- resourceBundleArtifacts: payload.resourceBundleArtifacts,
559
457
  };
560
458
  }
561
459
 
562
- private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
460
+ private getProtocolV2FirmwareChunkSize() {
563
461
  const payloadChunkSize = Number(this.params?.chunkSize);
564
462
  const env = DataManager.getSettings('env');
565
463
  const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
566
464
  let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
567
465
  if (isBle) {
568
- maxChunkSize =
569
- direction === 'read'
570
- ? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
571
- : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
466
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
572
467
  }
573
468
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
574
469
  return maxChunkSize;
@@ -589,14 +484,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
589
484
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
590
485
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
591
486
  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
- }
487
+ const resourceRecoveryMode = Boolean(
488
+ this.isProtocolV2BootloaderMode() || this.isProtocolV2RomloaderMode()
489
+ );
600
490
 
601
491
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
602
492
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -606,19 +496,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
606
496
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
607
497
  fwBinaryMap = this.collectExplicitTargetBinaries();
608
498
  bootloaderBinary = this.prepareBootloaderBinary();
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
- }
499
+ const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
500
+ const needsRemoteResources =
501
+ !this.params.resourceBundleFiles?.length &&
502
+ !!this.params.targetsToUpdate?.includes('resource');
503
+ if (needsRemoteFirmware || needsRemoteResources) {
504
+ // Remote updates must use a freshly fetched config before any reboot or file write.
505
+ await DataManager.forceReloadData();
506
+ }
507
+ if (needsRemoteFirmware) {
622
508
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
623
509
  firmwareType,
624
510
  deviceFeatures
@@ -627,308 +513,39 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
627
513
  fwBinaryMap = remoteBinaries.fwBinaryMap;
628
514
  installItems = remoteBinaries.installItems;
629
515
  }
630
- const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
631
- firmwareType,
632
- deviceFeatures
633
- );
634
- resourceBundles = resourceDescriptors?.length
635
- ? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
636
- : undefined;
516
+ resourceBundles = await this.prepareProtocolV2ResourceBundles(resourceRecoveryMode);
637
517
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
638
518
  } catch (err) {
639
- throw normalizeFirmwarePreparationError(err);
519
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
640
520
  }
641
521
 
642
522
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
523
+ if (resourceBundles !== undefined) {
524
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
525
+ return this.getProtocolV2VersionResult(deviceFeatures);
526
+ }
643
527
  throw ERRORS.TypedError(
644
528
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
645
529
  'No firmware to update'
646
530
  );
647
531
  }
648
532
 
649
- return this.executeProtocolV2Update({
533
+ await this.enterProtocolV2BootloaderMode();
534
+
535
+ await this.executeProtocolV2Update({
650
536
  fwBinaryMap,
651
537
  bootloaderBinary,
652
538
  ...(installItems ? { installItems } : undefined),
653
539
  ...(resourceBundles?.length ? { resourceBundles } : undefined),
654
540
  });
655
- }
656
-
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
- }
674
-
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
- }
686
-
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
541
 
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
- }
542
+ await this.exitProtocolV2BootloaderToNormal();
857
543
 
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
- }
544
+ const versions = await this.waitForProtocolV2FinalFeatures();
545
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
546
+ DevicePool.resetState();
911
547
 
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);
548
+ return versions;
932
549
  }
933
550
 
934
551
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
@@ -964,16 +581,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
964
581
  private async captureProtocolV2PhysicalIdentity() {
965
582
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
966
583
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
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
- }
584
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
977
585
  this.protocolV2ExpectedSerialNumber = serialNumber;
978
586
  }
979
587
 
@@ -1052,57 +660,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1052
660
  return target;
1053
661
  }
1054
662
 
1055
- private getProtocolV2ResourceFilePath(path: string) {
1056
- if (path.startsWith('vol')) return path;
1057
- if (path.startsWith('/')) return `vol0:${path}`;
1058
- return `vol0:/${path}`;
1059
- }
1060
-
1061
- private async readProtocolV2DeviceFileHeader(path: string) {
1062
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1063
- const filePath = this.getProtocolV2ResourceFilePath(path);
1064
- const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
1065
- path: filePath,
1066
- });
1067
- const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1068
- if (
1069
- !pathInfoRes.message?.exist ||
1070
- pathInfoRes.message?.directory ||
1071
- fileSize === undefined ||
1072
- fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
1073
- ) {
1074
- return null;
1075
- }
1076
-
1077
- const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
1078
- const chunks: Uint8Array[] = [];
1079
- let offset = 0;
1080
- while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
1081
- const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
1082
- const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
1083
- file: {
1084
- path: filePath,
1085
- offset,
1086
- total_size: 0,
1087
- },
1088
- chunk_len: readLen,
1089
- ui_percentage: undefined,
1090
- });
1091
- const data = toProtocolV2Bytes(res.message?.data);
1092
- if (data.byteLength === 0) return null;
1093
- chunks.push(data);
1094
- offset += data.byteLength;
1095
- }
1096
-
1097
- const headerBytes = new Uint8Array(offset);
1098
- let cursor = 0;
1099
- chunks.forEach(chunk => {
1100
- headerBytes.set(chunk, cursor);
1101
- cursor += chunk.byteLength;
1102
- });
1103
- return parseProtocolV2OkppHeader(headerBytes);
1104
- }
1105
-
1106
663
  private async downloadRemoteProtocolV2Component(
1107
664
  key: string,
1108
665
  component: IProtocolV2FirmwareComponent
@@ -1191,23 +748,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1191
748
  };
1192
749
  }
1193
750
 
1194
- // ============================================================
1195
- // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
1196
- // ============================================================
1197
-
1198
- /**
1199
- * Prepare the RESC bundle list.
1200
- *
1201
- * Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
1202
- * - resourceBundleFiles supplied: use binaries directly without version checks.
1203
- * - Otherwise load release.resourceBundles from remote config.json; the binaries
1204
- * are prefetched before reboot and synchronization skips matching device headers.
1205
- */
1206
- private prepareProtocolV2ResourceBundles(
1207
- firmwareType: EFirmwareType,
1208
- features: Features
1209
- ): ProtocolV2ResourceBundleBinary[] | undefined {
1210
- // Manual binaries are installed directly without comparison.
751
+ private async prepareProtocolV2ResourceBundles(
752
+ recoveryMode: boolean
753
+ ): Promise<ProtocolV2ResourceBundleBinary[] | undefined> {
1211
754
  if (this.params.resourceBundleFiles?.length) {
1212
755
  return this.params.resourceBundleFiles.map((file, index) => {
1213
756
  const devicePath = validateProtocolV2FilesystemPath(
@@ -1226,120 +769,88 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1226
769
  return undefined;
1227
770
  }
1228
771
 
1229
- // Remote-config bundles are downloaded and compared later.
1230
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
1231
- if (!release?.resourceBundles?.length) return undefined;
1232
-
1233
- return release.resourceBundles.map((bundle, index) => ({
1234
- name: bundle.name,
1235
- binary: new ArrayBuffer(0),
1236
- devicePath: validateProtocolV2FilesystemPath(
1237
- bundle.devicePath,
1238
- `resourceBundles[${index}].devicePath`
1239
- ),
1240
- url: bundle.url,
1241
- version: bundle.version,
1242
- payloadHash: bundle.payloadHash,
1243
- headerHash: bundle.headerHash,
1244
- })) as ProtocolV2ResourceBundleBinary[];
1245
- }
772
+ const resources = DataManager.getProtocolV2Resources();
773
+ if (!resources?.length) {
774
+ throw new Error('Missing Pro2 stable resource configuration');
775
+ }
1246
776
 
1247
- private async materializeProtocolV2ResourceBundles(
1248
- bundles: ProtocolV2ResourceBundleBinary[]
1249
- ): Promise<ProtocolV2ResourceBundleBinary[]> {
1250
- for (const bundle of bundles) {
1251
- let { binary } = bundle;
1252
- let downloadedFromRemote = false;
1253
- if (binary.byteLength === 0) {
1254
- if (!bundle.url) {
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
- );
1272
- }
1273
- Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
1274
- ({ binary } = await getSysResourceBinary(bundle.url));
1275
- bundle.binary = binary;
1276
- downloadedFromRemote = true;
1277
- }
1278
- if (bundle.binary.byteLength === 0) {
1279
- throw ERRORS.TypedError(
1280
- HardwareErrorCode.FirmwareUpdateDownloadFailed,
1281
- `Protocol V2 RESC bundle is empty: ${bundle.name}`
1282
- );
1283
- }
1284
- if (downloadedFromRemote) {
1285
- const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
1286
- if (!header || header.type !== 'RESC') {
1287
- throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
1288
- }
1289
- if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
1290
- throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
1291
- }
1292
- const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
1293
- if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
1294
- throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
1295
- }
1296
- const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
1297
- if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
1298
- throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
1299
- }
1300
- }
777
+ const inventory = recoveryMode
778
+ ? undefined
779
+ : await requestProtocolV2ResourceInventory({
780
+ commands: this.device.getCommands(),
781
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
782
+ });
783
+ const plan = buildProtocolV2ResourceUpdatePlan({
784
+ resources,
785
+ inventory,
786
+ mode: recoveryMode ? 'bootloader-recovery' : 'application',
787
+ forced: this.params.forcedUpdateRes,
788
+ });
789
+ Log.log(
790
+ `[FirmwareUpdateV4] Pro2 resource plan mode=${
791
+ recoveryMode ? 'bootloader-recovery' : 'application'
792
+ } status=${plan.status} count=${plan.resources.length}`
793
+ );
794
+
795
+ const bundles: ProtocolV2ResourceBundleBinary[] = [];
796
+ for (const resource of plan.resources) {
797
+ bundles.push(await this.downloadProtocolV2Resource(resource));
1301
798
  }
1302
799
  return bundles;
1303
800
  }
1304
801
 
1305
- /**
1306
- * Compare the existing okpkg OKPP header in remote-config mode.
1307
- */
1308
- private async isProtocolV2ResourceBundleUpToDate(
1309
- bundle: Pick<
1310
- ProtocolV2ResourceBundleBinary,
1311
- 'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1312
- >
1313
- ): Promise<boolean> {
1314
- if (this.params?.forcedUpdateRes) return false;
1315
- if (!bundle.version && !bundle.payloadHash) return false;
802
+ private async downloadProtocolV2Resource(
803
+ resource: IProtocolV2Resource
804
+ ): Promise<ProtocolV2ResourceBundleBinary> {
805
+ Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
806
+ const { binary } = await getSysResourceBinary(resource.url);
807
+ if (!isProtocolV2ResourceFileValid(binary, resource)) {
808
+ throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
809
+ }
810
+ return {
811
+ name: `${resource.type}.okpkg`,
812
+ binary,
813
+ devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
814
+ };
815
+ }
1316
816
 
1317
- try {
1318
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
1319
- if (!header) return false;
817
+ private async syncProtocolV2ResourceBundles(
818
+ bundles: ProtocolV2ResourceBundleBinary[],
819
+ firmwareSize: number
820
+ ): Promise<{ processedSize: number; totalSize: number }> {
821
+ const transferStartTime = Date.now();
822
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1320
823
 
1321
- if (bundle.version) {
1322
- const cmp = compareProtocolV2Versions(header.version, bundle.version);
1323
- if (cmp === undefined || cmp !== 0) return false;
1324
- }
1325
- if (bundle.payloadHash) {
1326
- const expected = normalizeProtocolV2Hex(bundle.payloadHash);
1327
- if (expected && header.payloadHash !== expected) return false;
1328
- }
1329
- if (bundle.headerHash) {
1330
- const expected = normalizeProtocolV2Hex(bundle.headerHash);
1331
- if (expected && header.headerHash !== expected) return false;
1332
- }
1333
- return true;
1334
- } catch (error) {
1335
- Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
1336
- return false;
824
+ // Write directly to the device through FileWrite.
825
+ let totalSize = 0;
826
+ for (const b of bundles) totalSize += b.binary.byteLength;
827
+
828
+ const transferTotalSize = totalSize + firmwareSize;
829
+ let processedSize = 0;
830
+ for (const bundle of bundles) {
831
+ Log.log(
832
+ `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
833
+ );
834
+ processedSize = await this.protocolV2CommonUpdateProcess({
835
+ payload: bundle.binary,
836
+ filePath: bundle.devicePath,
837
+ processedSize,
838
+ totalSize: transferTotalSize,
839
+ });
1337
840
  }
841
+
842
+ const elapsedMs = Date.now() - transferStartTime;
843
+ Log.log(
844
+ `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
845
+ elapsedMs / 1000
846
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
847
+ );
848
+ return { processedSize, totalSize: transferTotalSize };
1338
849
  }
1339
850
 
1340
851
  private isProtocolV2BootloaderMode() {
1341
852
  if (typeof this.device.isBootloader === 'function') {
1342
- return this.device.isBootloader();
853
+ return !!this.device.isBootloader();
1343
854
  }
1344
855
  return (
1345
856
  this.device.features?.mode === 'bootloader' ||
@@ -1362,12 +873,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1362
873
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
1363
874
  if (this.isProtocolV2RomloaderMode()) {
1364
875
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
1365
- this.protocolV2ExecutionInLoader = true;
1366
876
  return false;
1367
877
  }
1368
878
  if (this.isProtocolV2BootloaderMode()) {
1369
879
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
1370
- this.protocolV2ExecutionInLoader = true;
1371
880
  return false;
1372
881
  }
1373
882
 
@@ -1377,7 +886,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1377
886
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1378
887
  await wait(1000);
1379
888
  await this.waitForProtocolV2BootloaderMode();
1380
- this.protocolV2ExecutionInLoader = true;
1381
889
  return true;
1382
890
  } catch (error) {
1383
891
  if (error instanceof HardwareError) {
@@ -1467,98 +975,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1467
975
  return entries;
1468
976
  }
1469
977
 
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
-
1562
978
  private async executeProtocolV2Update({
1563
979
  fwBinaryMap,
1564
980
  bootloaderBinary,
@@ -1570,158 +986,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1570
986
  installItems?: ProtocolV2InstallItem[];
1571
987
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1572
988
  }) {
1573
- const memoryInstallItems =
989
+ const orderedInstallItems =
1574
990
  installItems ??
1575
991
  this.buildProtocolV2InstallItems({
1576
- fwBinaryMap: fwBinaryMap ?? [],
1577
992
  bootloaderBinary: bootloaderBinary ?? null,
993
+ fwBinaryMap: fwBinaryMap ?? [],
1578
994
  });
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
- }))
1597
- );
1598
- return await this.executeProtocolV2Phases({
1599
- installSources,
1600
- resourceSources,
1601
- });
1602
- } finally {
1603
- await this.closeProtocolV2PreparedSources();
1604
- }
1605
- }
1606
995
 
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;
1619
- }
1620
- }
996
+ let firmwareSize = 0;
997
+ for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1621
998
 
1622
999
  this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1000
+
1623
1001
  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);
1002
+ let totalSize = firmwareSize;
1003
+ if (resourceBundles?.length) {
1004
+ const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1005
+ resourceBundles,
1006
+ firmwareSize
1007
+ );
1008
+ processedSize = resourceTransfer.processedSize;
1009
+ totalSize = resourceTransfer.totalSize;
1632
1010
  }
1633
1011
 
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
- });
1012
+ // Skip staging and installation when the update contains RESC bundles only.
1013
+ if (orderedInstallItems.length === 0) {
1014
+ Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1015
+ if (totalSize > 0) {
1016
+ this.postProgressMessage(100, 'transferData');
1017
+ }
1018
+ return;
1648
1019
  }
1020
+ let transferredSize = processedSize;
1021
+ const transferStartTime = Date.now();
1022
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1023
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1024
+ const onTransferredBytes = (bytes: number) => {
1025
+ transferredSize = bytes;
1026
+ };
1027
+ Log.log(
1028
+ `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1029
+ );
1649
1030
 
1650
- if (totalSize > 0) {
1651
- this.postProgressMessage(100, 'transferData');
1652
- }
1653
- if (stagedInstallTargets.length === 0) {
1654
- return;
1031
+ const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1032
+
1033
+ try {
1034
+ for (const item of orderedInstallItems) {
1035
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1036
+ Log.log(
1037
+ `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1038
+ );
1039
+ processedSize = await this.protocolV2CommonUpdateProcess({
1040
+ payload: item.binary,
1041
+ filePath,
1042
+ processedSize,
1043
+ totalSize,
1044
+ onTransferredBytes,
1045
+ });
1046
+ transferredSize = processedSize;
1047
+ await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1048
+
1049
+ stagedInstallTargets.push({
1050
+ ...item,
1051
+ path: filePath,
1052
+ });
1053
+ }
1054
+
1055
+ if (totalSize > 0) {
1056
+ this.postProgressMessage(100, 'transferData');
1057
+ }
1058
+
1059
+ const elapsedMs = Date.now() - transferStartTime;
1060
+ Log.log(
1061
+ `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1062
+ elapsedMs / 1000
1063
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1064
+ );
1065
+ } catch (error) {
1066
+ const elapsedMs = Date.now() - transferStartTime;
1067
+ Log.warn(
1068
+ `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1069
+ elapsedMs / 1000
1070
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1071
+ );
1072
+ throw error;
1655
1073
  }
1656
1074
 
1657
1075
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1658
- const targets = stagedInstallTargets.map(item => ({
1076
+
1077
+ const allTargets = stagedInstallTargets.map(item => ({
1659
1078
  target_id: item.targetId,
1660
1079
  path: item.path,
1661
1080
  }));
1662
- await this.protocolV2StartFirmwareUpdate({ targets });
1663
- await this.waitForProtocolV2FirmwareUpdateComplete(targets);
1081
+ Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1082
+ await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1083
+ await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1664
1084
  }
1665
1085
 
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
- );
1722
- }
1723
-
1724
- private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1086
+ private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1725
1087
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1726
1088
  }
1727
1089
 
@@ -1740,8 +1102,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1740
1102
 
1741
1103
  private assertProtocolV2TargetStatus(
1742
1104
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1743
- expectedTargetIds: Set<number>,
1744
- expectedPaths = new Map<number, string>()
1105
+ expectedTargetIds: Set<number>
1745
1106
  ) {
1746
1107
  Log.log(
1747
1108
  `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
@@ -1772,54 +1133,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1772
1133
  const matchingTargets = statusTargets.filter(target =>
1773
1134
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1774
1135
  );
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
- );
1793
1136
  const completedTargetIds = new Set<number>();
1794
- completedTargets.forEach(target => {
1137
+ matchingTargets.forEach(target => {
1795
1138
  const targetId = normalizeProtocolV2TargetId(target.target_id);
1796
- if (targetId !== undefined) {
1139
+ if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
1797
1140
  completedTargetIds.add(targetId);
1798
1141
  }
1799
1142
  });
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
- }
1143
+ const allExpectedTargetsCompleted =
1144
+ expectedTargetIds.size > 0 &&
1145
+ Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
1146
+ if (allExpectedTargetsCompleted) {
1823
1147
  this.postProgressMessage(100, 'installingFirmware');
1824
1148
  return true;
1825
1149
  }
@@ -1843,9 +1167,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1843
1167
  private async waitForProtocolV2FirmwareUpdateComplete(
1844
1168
  targets: Array<{ target_id: number; path: string }>
1845
1169
  ) {
1846
- this.protocolV2FinalStatusVerified = false;
1847
1170
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1848
- const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1849
1171
  const startTime = Date.now();
1850
1172
  let lastError: unknown;
1851
1173
 
@@ -1869,11 +1191,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1869
1191
  if (
1870
1192
  this.assertProtocolV2TargetStatus(
1871
1193
  (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1872
- expectedTargetIds,
1873
- expectedPaths
1194
+ expectedTargetIds
1874
1195
  )
1875
1196
  ) {
1876
- this.protocolV2FinalStatusVerified = true;
1877
1197
  return;
1878
1198
  }
1879
1199
  lastError = new Error('Protocol V2 firmware targets are still installing');
@@ -1912,12 +1232,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1912
1232
  if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1913
1233
  throw error;
1914
1234
  }
1915
- if (
1916
- error instanceof HardwareError &&
1917
- error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
1918
- ) {
1919
- throw error;
1920
- }
1921
1235
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1922
1236
  }
1923
1237
  await wait(1000);
@@ -1935,7 +1249,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1935
1249
  // The connection may still be in bootloader. Request a Normal reboot directly;
1936
1250
  // repeating it after an automatic App reboot is idempotent.
1937
1251
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1938
- this.protocolV2ExecutionInLoader = false;
1939
1252
  }
1940
1253
 
1941
1254
  private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
@@ -1951,10 +1264,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1951
1264
  PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1952
1265
  );
1953
1266
 
1267
+ return this.getProtocolV2VersionResult(features);
1268
+ }
1269
+
1270
+ private getProtocolV2VersionResult(features: Features) {
1954
1271
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1955
1272
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1956
1273
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
1957
- this.protocolV2LatestFinalFeatures = features;
1958
1274
  if (firmwareVersion === '0.0.0') {
1959
1275
  Log.warn(
1960
1276
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1968,17 +1284,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1968
1284
  };
1969
1285
  }
1970
1286
 
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
-
1982
1287
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1983
1288
  const startTime = Date.now();
1984
1289
  let lastError: unknown;
@@ -2085,6 +1390,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2085
1390
  return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
2086
1391
  }
2087
1392
 
1393
+ private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1394
+ let lastError: unknown;
1395
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1396
+ try {
1397
+ return await this.protocolV2WriteWholeFile(params);
1398
+ } catch (error) {
1399
+ lastError = error;
1400
+ Log.error(
1401
+ `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1402
+ error
1403
+ );
1404
+ if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1405
+ break;
1406
+ }
1407
+ await this.recoverProtocolV2FileTransfer();
1408
+ }
1409
+ }
1410
+
1411
+ throw ERRORS.TypedError(
1412
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1413
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1414
+ );
1415
+ }
1416
+
1417
+ private async protocolV2WriteWholeFile({
1418
+ payload,
1419
+ filePath,
1420
+ processedSize,
1421
+ totalSize,
1422
+ onTransferredBytes,
1423
+ }: ProtocolV2FileTransferParams) {
1424
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1425
+ let offset = 0;
1426
+ const getUploadProgress = (fileOffset: number) => {
1427
+ if (totalSize !== undefined && processedSize !== undefined) {
1428
+ return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1429
+ }
1430
+ return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1431
+ };
1432
+
1433
+ while (offset < payload.byteLength) {
1434
+ const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
1435
+ const chunkLength = chunkEnd - offset;
1436
+ const chunk = payload.slice(offset, chunkEnd);
1437
+ const overwrite = offset === 0;
1438
+ const progress = getProtocolV2DeviceTransferProgress(
1439
+ (processedSize ?? 0) + offset,
1440
+ (processedSize ?? 0) + chunkEnd,
1441
+ totalSize ?? payload.byteLength
1442
+ );
1443
+
1444
+ const writeRes = await this.fileWriteChunk(
1445
+ filePath,
1446
+ payload.byteLength,
1447
+ offset,
1448
+ chunk,
1449
+ overwrite,
1450
+ progress
1451
+ );
1452
+ const rawProcessedByte = writeRes.message.processed_byte;
1453
+ const processedByte = Number(rawProcessedByte);
1454
+ const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
1455
+ if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
1456
+ throw ERRORS.TypedError(
1457
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1458
+ `invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
1459
+ );
1460
+ }
1461
+ offset = nextOffset;
1462
+ onTransferredBytes?.((processedSize ?? 0) + offset);
1463
+ this.postProgressMessage(getUploadProgress(offset), 'transferData');
1464
+ }
1465
+
1466
+ return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
1467
+ }
1468
+
1469
+ private getProtocolV2FirmwareTransferTransport() {
1470
+ const env = DataManager.getSettings('env');
1471
+ if (env && DataManager.isBleConnect(env)) {
1472
+ return 'BLE';
1473
+ }
1474
+ if (
1475
+ env &&
1476
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1477
+ ) {
1478
+ return 'WebUSB';
1479
+ }
1480
+ return env ?? 'unknown';
1481
+ }
1482
+
2088
1483
  private async fileWriteChunk(
2089
1484
  filePath: string,
2090
1485
  totalFileSize: number,