@onekeyfe/hd-core 1.2.0-alpha.66 → 1.2.0-alpha.67

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 (315) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -313
  2. package/__tests__/DeviceCommands.test.ts +13 -59
  3. package/__tests__/DeviceEventRegistration.test.ts +0 -6
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -198
  5. package/__tests__/connectSettings.test.ts +47 -5
  6. package/__tests__/device-initialize-timeout.test.ts +56 -0
  7. package/__tests__/device-lifecycle-events.test.ts +3 -105
  8. package/__tests__/device-pool-state.test.ts +0 -22
  9. package/__tests__/device-settings.test.ts +18 -39
  10. package/__tests__/device-state-contract.test.ts +0 -1
  11. package/__tests__/device-state-mapper.test.ts +1 -13
  12. package/__tests__/device-utils.test.ts +1 -48
  13. package/__tests__/deviceSettings.test.ts +1 -11
  14. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  15. package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
  16. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  17. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  18. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  19. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  20. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  21. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
  22. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  23. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
  24. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  25. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
  26. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  27. package/__tests__/get-device-state.test.ts +12 -132
  28. package/__tests__/homescreen.test.ts +1 -41
  29. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  30. package/__tests__/kaspaSignTransaction.test.ts +26 -3
  31. package/__tests__/logBlockEvent.test.ts +1 -1
  32. package/__tests__/method-protocol-support.test.ts +0 -19
  33. package/__tests__/open-wallet-session.test.ts +37 -568
  34. package/__tests__/protocol-v2-unlock-policy.test.ts +37 -300
  35. package/__tests__/protocol-v2.test.ts +1153 -1458
  36. package/__tests__/protocolV2FileWrite.test.ts +0 -152
  37. package/__tests__/protocolV2UiInteraction.test.ts +0 -63
  38. package/__tests__/public-pro2-api-boundary.test.ts +0 -1
  39. package/__tests__/search-devices.test.ts +3 -12
  40. package/__tests__/tron-sign-message-init.test.ts +2 -2
  41. package/dist/api/BaseMethod.d.ts +1 -3
  42. package/dist/api/BaseMethod.d.ts.map +1 -1
  43. package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
  44. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  45. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  46. package/dist/api/FirmwareUpdateV2.d.ts +10 -0
  47. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  48. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  49. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  50. package/dist/api/FirmwareUpdateV4.d.ts +27 -13
  51. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  52. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  53. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  54. package/dist/api/SearchDevices.d.ts.map +1 -1
  55. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  56. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  57. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  58. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  59. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  60. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  61. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  62. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  63. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  64. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  65. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  66. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  67. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +11 -4
  68. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  69. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  70. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  71. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  72. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  73. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  74. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  75. package/dist/api/firmware/getBinary.d.ts +7 -0
  76. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  77. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  78. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  79. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  80. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  81. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  82. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  83. package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -9
  84. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  85. package/dist/api/index.d.ts +0 -2
  86. package/dist/api/index.d.ts.map +1 -1
  87. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  88. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  89. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  90. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  91. package/dist/api/polkadot/networks.d.ts +1 -1
  92. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  93. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  94. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  95. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  96. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  97. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  98. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  99. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  100. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  101. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  102. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  103. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  104. package/dist/api/ton/TonSignProof.d.ts +1 -1
  105. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  106. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  107. package/dist/core/deviceEventRegistration.d.ts +0 -2
  108. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  109. package/dist/core/index.d.ts.map +1 -1
  110. package/dist/data-manager/DataManager.d.ts +4 -9
  111. package/dist/data-manager/DataManager.d.ts.map +1 -1
  112. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  113. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  114. package/dist/device/Device.d.ts +6 -29
  115. package/dist/device/Device.d.ts.map +1 -1
  116. package/dist/device/DeviceCommands.d.ts +4 -4
  117. package/dist/device/DeviceCommands.d.ts.map +1 -1
  118. package/dist/device/DeviceConnector.d.ts +1 -2
  119. package/dist/device/DeviceConnector.d.ts.map +1 -1
  120. package/dist/device/DevicePool.d.ts.map +1 -1
  121. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  122. package/dist/events/device.d.ts +0 -4
  123. package/dist/events/device.d.ts.map +1 -1
  124. package/dist/events/logBlockEvent.d.ts.map +1 -1
  125. package/dist/events/ui-promise.d.ts +2 -4
  126. package/dist/events/ui-promise.d.ts.map +1 -1
  127. package/dist/events/ui-request.d.ts +2 -31
  128. package/dist/events/ui-request.d.ts.map +1 -1
  129. package/dist/events/ui-response.d.ts +2 -10
  130. package/dist/events/ui-response.d.ts.map +1 -1
  131. package/dist/index.d.ts +247 -243
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +3773 -3054
  134. package/dist/inject.d.ts +1 -6
  135. package/dist/inject.d.ts.map +1 -1
  136. package/dist/lowLevelInject.d.ts.map +1 -1
  137. package/dist/protocols/protocol-v2/features.d.ts +4 -0
  138. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  139. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  140. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  141. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  142. package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
  143. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  144. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
  145. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  146. package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
  147. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  148. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -6
  149. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  150. package/dist/topLevelInject.d.ts.map +1 -1
  151. package/dist/types/api/checkAllFirmwareRelease.d.ts +5 -3
  152. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  153. package/dist/types/api/deviceUnlock.d.ts +1 -5
  154. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  155. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  156. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  157. package/dist/types/api/export.d.ts +4 -2
  158. package/dist/types/api/export.d.ts.map +1 -1
  159. package/dist/types/api/firmwareUpdate.d.ts +70 -4
  160. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  161. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  162. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  163. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  164. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  165. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  166. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  167. package/dist/types/api/index.d.ts +10 -7
  168. package/dist/types/api/index.d.ts.map +1 -1
  169. package/dist/types/api/protocolV2.d.ts +0 -3
  170. package/dist/types/api/protocolV2.d.ts.map +1 -1
  171. package/dist/types/device.d.ts +2 -2
  172. package/dist/types/device.d.ts.map +1 -1
  173. package/dist/types/params.d.ts +0 -1
  174. package/dist/types/params.d.ts.map +1 -1
  175. package/dist/types/settings.d.ts +21 -38
  176. package/dist/types/settings.d.ts.map +1 -1
  177. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  178. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  179. package/dist/utils/deviceFeaturesUtils.d.ts +0 -4
  180. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  181. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  182. package/dist/utils/deviceSettings.d.ts.map +1 -1
  183. package/dist/utils/homescreen.d.ts +0 -4
  184. package/dist/utils/homescreen.d.ts.map +1 -1
  185. package/dist/utils/index.d.ts +1 -1
  186. package/dist/utils/index.d.ts.map +1 -1
  187. package/dist/utils/patch.d.ts +1 -1
  188. package/dist/utils/patch.d.ts.map +1 -1
  189. package/dist/utils/pro2Wallpaper.d.ts +0 -10
  190. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  191. package/package.json +4 -4
  192. package/src/api/BaseMethod.ts +10 -8
  193. package/src/api/CheckAllFirmwareRelease.ts +60 -59
  194. package/src/api/FirmwareUpdate.ts +15 -7
  195. package/src/api/FirmwareUpdateV2.ts +298 -126
  196. package/src/api/FirmwareUpdateV3.ts +265 -63
  197. package/src/api/FirmwareUpdateV4.ts +1017 -602
  198. package/src/api/GetPassphraseState.ts +1 -7
  199. package/src/api/OpenWalletSession.ts +17 -71
  200. package/src/api/SearchDevices.ts +1 -3
  201. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  202. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -36
  203. package/src/api/device/DeviceSettings.ts +4 -5
  204. package/src/api/device/DeviceUnlock.ts +3 -8
  205. package/src/api/device/DeviceUpdateBootloader.ts +125 -6
  206. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  207. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  208. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  209. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -27
  210. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  211. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  212. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
  213. package/src/api/firmware/progressThrottle.ts +44 -0
  214. package/src/api/firmware/updateBootloader.ts +19 -4
  215. package/src/api/firmware/uploadFirmware.ts +144 -39
  216. package/src/api/helpers/protocolV2FileWrite.ts +63 -202
  217. package/src/api/index.ts +0 -2
  218. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  219. package/src/api/kaspa/KaspaSignTransaction.ts +13 -2
  220. package/src/api/polkadot/networks.ts +3 -3
  221. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
  222. package/src/api/protocol-v2/helpers.ts +1 -1
  223. package/src/api/solana/SolSignMessage.ts +1 -1
  224. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  225. package/src/api/solana/SolSignTransaction.ts +2 -2
  226. package/src/api/sui/SuiGetAddress.ts +1 -1
  227. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  228. package/src/api/sui/SuiSignMessage.ts +1 -1
  229. package/src/api/sui/SuiSignTransaction.ts +1 -1
  230. package/src/api/ton/TonGetAddress.ts +1 -1
  231. package/src/api/ton/TonSignMessage.ts +1 -1
  232. package/src/api/ton/TonSignProof.ts +1 -1
  233. package/src/api/tron/TronSignMessage.ts +2 -2
  234. package/src/api/tron/TronSignTransaction.ts +1 -1
  235. package/src/core/deviceEventRegistration.ts +0 -4
  236. package/src/core/index.ts +111 -213
  237. package/src/data/messages/messages-protocol-v2.json +0 -25
  238. package/src/data-manager/DataManager.ts +41 -113
  239. package/src/data-manager/TransportManager.ts +0 -6
  240. package/src/data-manager/connectSettings.ts +11 -0
  241. package/src/device/Device.ts +49 -274
  242. package/src/device/DeviceCommands.ts +4 -31
  243. package/src/device/DeviceConnector.ts +13 -33
  244. package/src/device/DevicePool.ts +7 -21
  245. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  246. package/src/deviceProfile/protocolV2DeviceIdentity.ts +0 -3
  247. package/src/events/device.ts +0 -4
  248. package/src/events/logBlockEvent.ts +0 -1
  249. package/src/events/ui-promise.ts +2 -4
  250. package/src/events/ui-request.ts +2 -36
  251. package/src/events/ui-response.ts +2 -12
  252. package/src/index.ts +5 -0
  253. package/src/inject.ts +24 -60
  254. package/src/lowLevelInject.ts +8 -7
  255. package/src/protocols/protocol-v2/features.ts +7 -10
  256. package/src/protocols/protocol-v2/firmware.ts +0 -2
  257. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  258. package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
  259. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
  260. package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
  261. package/src/protocols/protocol-v2/walletSession.ts +36 -214
  262. package/src/topLevelInject.ts +8 -7
  263. package/src/types/api/checkAllFirmwareRelease.ts +5 -3
  264. package/src/types/api/deviceUnlock.ts +1 -12
  265. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  266. package/src/types/api/export.ts +18 -1
  267. package/src/types/api/firmwareUpdate.ts +78 -6
  268. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  269. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  270. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  271. package/src/types/api/index.ts +14 -13
  272. package/src/types/api/protocolV2.ts +0 -13
  273. package/src/types/device.ts +3 -15
  274. package/src/types/params.ts +1 -7
  275. package/src/types/settings.ts +22 -60
  276. package/src/utils/deviceFeaturesCompat.ts +6 -5
  277. package/src/utils/deviceFeaturesUtils.ts +3 -14
  278. package/src/utils/deviceInfoUtils.ts +0 -2
  279. package/src/utils/deviceSettings.ts +0 -3
  280. package/src/utils/homescreen.ts +6 -32
  281. package/src/utils/index.ts +1 -6
  282. package/src/utils/pro2Wallpaper.ts +8 -35
  283. package/__tests__/core-initialization.test.ts +0 -23
  284. package/__tests__/device-connector-protocol.test.ts +0 -81
  285. package/__tests__/deviceUploadNft.test.ts +0 -316
  286. package/__tests__/pro2Nft.test.ts +0 -108
  287. package/__tests__/protocol-binding.test.ts +0 -89
  288. package/__tests__/protocol-v2-resources.test.ts +0 -340
  289. package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
  290. package/__tests__/uiProgressThrottle.test.ts +0 -74
  291. package/__tests__/uiPromiseRegistry.test.ts +0 -115
  292. package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
  293. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
  294. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
  295. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
  296. package/dist/core/uiPromiseRegistry.d.ts +0 -6
  297. package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
  298. package/dist/protocols/protocol-v2/resources.d.ts +0 -30
  299. package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
  300. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
  301. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
  302. package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
  303. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
  304. package/dist/utils/pro2Nft.d.ts +0 -31
  305. package/dist/utils/pro2Nft.d.ts.map +0 -1
  306. package/dist/utils/uiProgressThrottle.d.ts +0 -3
  307. package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
  308. package/src/api/DetectDeviceConnectProtocol.ts +0 -18
  309. package/src/api/protocol-v2/DeviceUploadNft.ts +0 -197
  310. package/src/core/uiPromiseRegistry.ts +0 -63
  311. package/src/protocols/protocol-v2/resources.ts +0 -390
  312. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -131
  313. package/src/types/api/detectDeviceConnectProtocol.ts +0 -6
  314. package/src/utils/pro2Nft.ts +0 -142
  315. package/src/utils/uiProgressThrottle.ts +0 -63
@@ -2,13 +2,13 @@ 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,
5
6
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
6
7
  } from '@onekeyfe/hd-transport';
7
8
  import { sha256 } from '@noble/hashes/sha256';
8
9
 
9
10
  import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
10
11
  import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
11
- import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
12
12
  import { validateParams } from './helpers/paramsValidator';
13
13
  import {
14
14
  LoggerNames,
@@ -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';
@@ -27,40 +28,41 @@ import {
27
28
  ProtocolV2FirmwareTargetType,
28
29
  } from '../protocols/protocol-v2';
29
30
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
30
- import {
31
- PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
32
- buildProtocolV2ResourceUpdatePlan,
33
- isProtocolV2ResourceFileValid,
34
- readProtocolV2ResourceInventory,
35
- } from '../protocols/protocol-v2/resources';
36
31
  import {
37
32
  getProtocolV2UnknownErrorText,
38
33
  isProtocolV2DeviceDisconnectedError,
39
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';
40
41
 
41
- import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
42
+ import type {
43
+ FirmwareArtifactReference,
44
+ FirmwareUpdateV4Params,
45
+ FirmwareUpdateV4Target,
46
+ } from '../types/api/firmwareUpdate';
42
47
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
43
48
  import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
44
- import type { PROTO } from '../constants';
45
49
  import type { TypedResponseMessage } from '../device/DeviceCommands';
46
50
  import type {
47
51
  Features,
48
52
  IFirmwareReleaseInfo,
49
53
  IProtocolV2FirmwareComponent,
50
- IProtocolV2Resource,
51
- IProtocolV2ResourceFile,
52
54
  IVersionArray,
53
55
  } from '../types';
56
+ import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
54
57
 
55
58
  const Log = getLogger(LoggerNames.Method);
56
59
 
57
- const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
58
- const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
60
+ const SESSION_ERROR = 'session not found';
61
+ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
62
+ const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
59
63
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
60
- const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
61
64
  const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
62
- const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
63
- const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
65
+ const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
64
66
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
65
67
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
66
68
  const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
@@ -71,8 +73,9 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
71
73
  const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
72
74
  const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
73
75
  const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
74
- const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
76
+ const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
75
77
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
78
+ const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
76
79
  const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
77
80
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
78
81
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
@@ -95,11 +98,6 @@ const getProtocolV2DeviceTransferProgress = (
95
98
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
96
99
  };
97
100
 
98
- const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
99
- const safeElapsedMs = Math.max(elapsedMs, 1);
100
- return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
101
- };
102
-
103
101
  type ProtocolV2FirmwareUpdateStatusTarget = {
104
102
  target_id: number | string;
105
103
  status?: number | string;
@@ -113,22 +111,16 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
113
111
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
114
112
  kind: ProtocolV2RemoteComponentTarget['kind'];
115
113
  };
116
- type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
117
- path: string;
118
- };
119
-
120
- type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
121
- filePath: string;
122
- processedSize?: number;
123
- totalSize?: number;
124
- onTransferredBytes?: (transferredBytes: number) => void;
125
- };
126
-
127
- /** Protocol V2 resource file written independently to devicePath through FileWrite. */
114
+ /** RESC bundle okpkg written independently to devicePath through FileWrite. */
128
115
  type ProtocolV2ResourceBundleBinary = {
129
116
  name: string;
130
117
  binary: ArrayBuffer;
131
118
  devicePath: string;
119
+ /** Download URL for remote-config mode; omitted in manual mode. */
120
+ url?: string;
121
+ version?: IVersionArray;
122
+ payloadHash?: string;
123
+ headerHash?: string;
132
124
  };
133
125
 
134
126
  type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
@@ -141,6 +133,35 @@ type ProtocolV2RemoteComponentTarget = {
141
133
  kind: 'bootloader' | 'firmware';
142
134
  };
143
135
 
136
+ type ProtocolV2InstallSource = {
137
+ fileName: string;
138
+ source: FirmwareByteSource;
139
+ targetId: number;
140
+ kind: ProtocolV2RemoteComponentTarget['kind'];
141
+ };
142
+
143
+ type ProtocolV2ResourceBundleSource = {
144
+ name: string;
145
+ source: FirmwareByteSource;
146
+ devicePath: string;
147
+ version?: IVersionArray;
148
+ payloadHash?: string;
149
+ headerHash?: string;
150
+ };
151
+
152
+ type ProtocolV2ExecutionPhaseKind =
153
+ | 'resource-sync'
154
+ | 'bootloader-install'
155
+ | 'bootloader-verify'
156
+ | 'component-install'
157
+ | 'final-verify';
158
+
159
+ type ProtocolV2ExecutionPhase = {
160
+ kind: ProtocolV2ExecutionPhaseKind;
161
+ installSources: ProtocolV2InstallSource[];
162
+ resourceSources: ProtocolV2ResourceBundleSource[];
163
+ };
164
+
144
165
  type ProtocolV2OkppHeader = {
145
166
  type: string;
146
167
  version: IVersionArray;
@@ -243,6 +264,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
243
264
  );
244
265
  };
245
266
 
267
+ const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
268
+ error instanceof HardwareError &&
269
+ (error.errorCode === HardwareErrorCode.FirmwareError ||
270
+ error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
271
+ error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
272
+
246
273
  const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
247
274
  normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
248
275
 
@@ -280,6 +307,18 @@ const normalizeProtocolV2TargetStatus = (
280
307
 
281
308
  const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
282
309
 
310
+ const versionArrayToNumber = (version?: IVersionArray) => {
311
+ if (!version) return undefined;
312
+ return version[0] * 0x10000 + version[1] * 0x100 + version[2];
313
+ };
314
+
315
+ const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
316
+ const currentNumber = versionArrayToNumber(current);
317
+ const targetNumber = versionArrayToNumber(target);
318
+ if (currentNumber === undefined || targetNumber === undefined) return undefined;
319
+ return currentNumber - targetNumber;
320
+ };
321
+
283
322
  const bytesToHex = (bytes: Uint8Array) =>
284
323
  Array.from(bytes)
285
324
  .map(byte => byte.toString(16).padStart(2, '0'))
@@ -370,30 +409,20 @@ export const isProtocolV2FirmwareFingerprintValid = (
370
409
 
371
410
  export const assertProtocolV2ReconnectIdentity = (
372
411
  expectedSerialNumber?: string,
373
- actualSerialNumber?: string,
374
- expectedPath?: string,
375
- actualPath?: string
412
+ actualSerialNumber?: string
376
413
  ) => {
377
- if (expectedSerialNumber && actualSerialNumber) {
378
- if (actualSerialNumber !== expectedSerialNumber) {
379
- throw ERRORS.TypedError(
380
- HardwareErrorCode.DeviceNotFound,
381
- `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
382
- );
383
- }
384
- return;
414
+ if (!expectedSerialNumber || !actualSerialNumber) {
415
+ throw ERRORS.TypedError(
416
+ HardwareErrorCode.DeviceNotFound,
417
+ 'Protocol V2 reconnect physical identity is unavailable'
418
+ );
385
419
  }
386
-
387
- if (expectedPath && actualPath && expectedPath === actualPath) {
388
- return;
420
+ if (actualSerialNumber !== expectedSerialNumber) {
421
+ throw ERRORS.TypedError(
422
+ HardwareErrorCode.DeviceNotFound,
423
+ `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
424
+ );
389
425
  }
390
-
391
- throw ERRORS.TypedError(
392
- HardwareErrorCode.DeviceNotFound,
393
- `Protocol V2 reconnect physical identity unavailable: expected path ${
394
- expectedPath ?? 'unknown'
395
- }, received ${actualPath ?? 'unknown'}`
396
- );
397
426
  };
398
427
 
399
428
  /**
@@ -407,12 +436,20 @@ export const assertProtocolV2ReconnectIdentity = (
407
436
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
408
437
  private protocolV2ExpectedSerialNumber?: string;
409
438
 
410
- private protocolV2ExpectedPath?: string;
411
-
412
439
  getSupportedProtocols() {
413
440
  return ['V2'] as const;
414
441
  }
415
442
 
443
+ private protocolV2PreparedSources: FirmwareByteSource[] = [];
444
+
445
+ private protocolV2ExecutionInLoader = false;
446
+
447
+ private protocolV2CompletedTargetVersions = new Map<number, number>();
448
+
449
+ private protocolV2LatestFinalFeatures?: Features;
450
+
451
+ private protocolV2FinalStatusVerified = false;
452
+
416
453
  init() {
417
454
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
418
455
  this.requireDeviceMode = [];
@@ -450,10 +487,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
450
487
  { name: 'firmwareType', type: 'string' },
451
488
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
452
489
  { name: 'platform', type: 'string' },
453
- { name: 'resourceFiles', type: 'array', allowEmpty: true },
490
+ { name: 'expectedDeviceId', type: 'string' },
491
+ { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
454
492
  ]);
493
+ if (
494
+ payload.expectedDeviceId !== undefined &&
495
+ (payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
496
+ ) {
497
+ throw ERRORS.TypedError(
498
+ HardwareErrorCode.CallMethodInvalidParameter,
499
+ 'Protocol V2 expected device identity is invalid'
500
+ );
501
+ }
502
+ const hostBinding =
503
+ payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
504
+ ? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
505
+ : undefined;
506
+ if (hostBinding) {
507
+ assertFirmwareUpdatePreparedPlanBinding({
508
+ preparedPlan: payload.preparedPlan,
509
+ executor: 'v4',
510
+ platform: payload.platform,
511
+ scopeTargets: [
512
+ 'boot',
513
+ 'app_v1',
514
+ 'app_v2',
515
+ 'coprocessor',
516
+ 'resource',
517
+ 'se01',
518
+ 'se02',
519
+ 'se03',
520
+ 'se04',
521
+ ],
522
+ bindings: [
523
+ ...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
524
+ artifact
525
+ ? [
526
+ {
527
+ target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
528
+ artifact,
529
+ },
530
+ ]
531
+ : []
532
+ ),
533
+ ...(payload.resourceBundleArtifacts ?? []).map(
534
+ (entry: { name: string; artifact: FirmwareArtifactReference }) => ({
535
+ target: 'resource' as const,
536
+ logicalName: entry.name,
537
+ artifact: entry.artifact,
538
+ })
539
+ ),
540
+ ],
541
+ });
542
+ }
455
543
 
456
544
  this.params = {
545
+ preparedPlan: payload.preparedPlan,
457
546
  chunkSize: payload.chunkSize,
458
547
  forcedUpdateRes: payload.forcedUpdateRes,
459
548
  bootloaderBinary: payload.bootloaderBinary,
@@ -465,20 +554,28 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
465
554
  se02Binary: payload.se02Binary,
466
555
  se03Binary: payload.se03Binary,
467
556
  se04Binary: payload.se04Binary,
468
- resourceFiles: payload.resourceFiles,
557
+ resourceBundleFiles: payload.resourceBundleFiles,
469
558
  firmwareType: payload.firmwareType,
470
559
  targetsToUpdate: payload.targetsToUpdate,
560
+ expectedTargetVersions: payload.expectedTargetVersions,
471
561
  platform: payload.platform,
562
+ expectedDeviceId: payload.expectedDeviceId,
563
+ artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
564
+ componentArtifacts: payload.componentArtifacts,
565
+ resourceBundleArtifacts: payload.resourceBundleArtifacts,
472
566
  };
473
567
  }
474
568
 
475
- private getProtocolV2FirmwareChunkSize() {
569
+ private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
476
570
  const payloadChunkSize = Number(this.params?.chunkSize);
477
571
  const env = DataManager.getSettings('env');
478
572
  const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
479
573
  let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
480
574
  if (isBle) {
481
- maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
575
+ maxChunkSize =
576
+ direction === 'read'
577
+ ? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
578
+ : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
482
579
  }
483
580
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
484
581
  return maxChunkSize;
@@ -499,8 +596,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
499
596
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
500
597
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
501
598
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
502
- const needsRemoteResources =
503
- !this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
599
+ this.validateExpectedTargetVersions();
600
+
601
+ if (
602
+ (this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
603
+ this.params.resourceBundleArtifacts?.length
604
+ ) {
605
+ return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
606
+ }
504
607
 
505
608
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
506
609
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -508,18 +611,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
508
611
  let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
509
612
  try {
510
613
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
511
- resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
512
614
  fwBinaryMap = this.collectExplicitTargetBinaries();
513
615
  bootloaderBinary = this.prepareBootloaderBinary();
514
- const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
515
- const needsRemoteBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
516
- if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
517
- // Remote updates must use a freshly fetched config before any reboot or file write.
518
- await DataManager.forceReloadData({
519
- requireResources: needsRemoteResources || needsRemoteBootResources,
520
- });
521
- }
522
- if (needsRemoteFirmware) {
616
+ if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
617
+ if (
618
+ this.params.artifactReader ||
619
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
620
+ ) {
621
+ throw ERRORS.TypedError(
622
+ HardwareErrorCode.RuntimeError,
623
+ 'Protocol V2 firmware artifacts must be prepared by the external firmware host',
624
+ {
625
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
626
+ }
627
+ );
628
+ }
523
629
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
524
630
  firmwareType,
525
631
  deviceFeatures
@@ -528,70 +634,308 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
528
634
  fwBinaryMap = remoteBinaries.fwBinaryMap;
529
635
  installItems = remoteBinaries.installItems;
530
636
  }
531
- const bootResourceFiles = await this.prepareProtocolV2BootResources();
532
- if (bootResourceFiles?.length) {
533
- resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
534
- }
535
- if (!needsRemoteResources) {
536
- this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
537
- }
637
+ const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
638
+ firmwareType,
639
+ deviceFeatures
640
+ );
641
+ resourceBundles = resourceDescriptors?.length
642
+ ? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
643
+ : undefined;
644
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
538
645
  } catch (err) {
539
- if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
540
- throw err;
541
- }
542
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
646
+ throw normalizeFirmwarePreparationError(err);
543
647
  }
544
648
 
545
- if (
546
- !bootloaderBinary &&
547
- fwBinaryMap.length === 0 &&
548
- !installItems?.length &&
549
- !resourceBundles?.length &&
550
- !needsRemoteResources
551
- ) {
649
+ if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
552
650
  throw ERRORS.TypedError(
553
651
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
554
652
  'No firmware to update'
555
653
  );
556
654
  }
557
655
 
558
- const enteredBootloader = await this.enterProtocolV2BootloaderMode();
559
-
560
- if (needsRemoteResources) {
561
- try {
562
- const stableResources = await this.prepareProtocolV2ResourceBundles();
563
- resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
564
- this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
565
- } catch (err) {
566
- if (enteredBootloader) {
567
- try {
568
- await this.exitProtocolV2BootloaderToNormal();
569
- } catch (restoreError) {
570
- Log.warn(
571
- '[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
572
- restoreError
573
- );
574
- }
575
- }
576
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
577
- }
578
- }
579
-
580
- await this.executeProtocolV2Update({
656
+ return this.executeProtocolV2Update({
581
657
  fwBinaryMap,
582
658
  bootloaderBinary,
583
659
  ...(installItems ? { installItems } : undefined),
584
660
  ...(resourceBundles?.length ? { resourceBundles } : undefined),
585
661
  });
662
+ }
586
663
 
587
- this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
588
- await this.exitProtocolV2BootloaderToNormal();
664
+ private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
665
+ const source = await openFirmwareByteSource({
666
+ artifact,
667
+ reader: this.params.artifactReader,
668
+ });
669
+ if (!source) {
670
+ throw ERRORS.TypedError(
671
+ HardwareErrorCode.RuntimeError,
672
+ `Firmware artifact ${artifact.artifactRef} is not prepared`,
673
+ {
674
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
675
+ }
676
+ );
677
+ }
678
+ this.protocolV2PreparedSources.push(source);
679
+ return source;
680
+ }
589
681
 
590
- const versions = await this.waitForProtocolV2FinalFeatures();
591
- this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
592
- DevicePool.resetState();
682
+ private async openProtocolV2MemorySource(binary: ArrayBuffer) {
683
+ const source = await openFirmwareByteSource({ binary });
684
+ if (!source) {
685
+ throw ERRORS.TypedError(
686
+ HardwareErrorCode.RuntimeError,
687
+ 'Protocol V2 firmware binary is empty'
688
+ );
689
+ }
690
+ this.protocolV2PreparedSources.push(source);
691
+ return source;
692
+ }
593
693
 
594
- return versions;
694
+ private async closeProtocolV2PreparedSources() {
695
+ const sources = this.protocolV2PreparedSources.splice(0);
696
+ await Promise.all(sources.map(source => source.close().catch(() => undefined)));
697
+ }
698
+
699
+ private async prepareProtocolV2InstallSources(
700
+ firmwareType: EFirmwareType,
701
+ features: Features
702
+ ): Promise<ProtocolV2InstallSource[]> {
703
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
704
+ if (!release) {
705
+ throw ERRORS.TypedError(
706
+ HardwareErrorCode.RuntimeError,
707
+ 'Protocol V2 firmware release is unavailable'
708
+ );
709
+ }
710
+ const componentArtifacts = this.params.componentArtifacts ?? {};
711
+ const requestedTargets = new Set(
712
+ this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
713
+ );
714
+ const installSources: ProtocolV2InstallSource[] = [];
715
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
716
+
717
+ for (const [key, component] of this.getRemoteComponentEntries(release)) {
718
+ const target = this.getRemoteComponentTarget(key, component);
719
+ const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
720
+ if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
721
+ const artifact = componentArtifacts[updateTarget];
722
+ if (!artifact) {
723
+ throw ERRORS.TypedError(
724
+ HardwareErrorCode.RuntimeError,
725
+ `Protocol V2 ${updateTarget} artifact is not prepared`,
726
+ {
727
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
728
+ artifactName: updateTarget,
729
+ }
730
+ );
731
+ }
732
+ installSources.push({
733
+ ...target,
734
+ source: await this.openProtocolV2PreparedSource(artifact),
735
+ });
736
+ preparedTargets.add(updateTarget);
737
+ }
738
+ }
739
+
740
+ const unknownTarget = Array.from(requestedTargets).find(
741
+ target => target !== 'resource' && !preparedTargets.has(target)
742
+ );
743
+ if (unknownTarget) {
744
+ throw ERRORS.TypedError(
745
+ HardwareErrorCode.RuntimeError,
746
+ `Protocol V2 release does not contain requested target ${unknownTarget}`
747
+ );
748
+ }
749
+ return installSources;
750
+ }
751
+
752
+ private async prepareProtocolV2ResourceSources(
753
+ firmwareType: EFirmwareType,
754
+ features: Features
755
+ ): Promise<ProtocolV2ResourceBundleSource[]> {
756
+ const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
757
+ const resourceRequested =
758
+ this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
759
+ if (!resourceRequested) {
760
+ return [];
761
+ }
762
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
763
+ const descriptors = release?.resourceBundles ?? [];
764
+ const artifactByName = new Map(
765
+ preparedArtifacts.map(item => [item.name, item.artifact] as const)
766
+ );
767
+ const sources: ProtocolV2ResourceBundleSource[] = [];
768
+ for (const descriptor of descriptors) {
769
+ const artifact = artifactByName.get(descriptor.name);
770
+ if (!artifact) {
771
+ throw ERRORS.TypedError(
772
+ HardwareErrorCode.RuntimeError,
773
+ `Protocol V2 resource bundle ${descriptor.name} is not prepared`,
774
+ {
775
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
776
+ artifactName: descriptor.name,
777
+ }
778
+ );
779
+ }
780
+ const devicePath = validateProtocolV2FilesystemPath(
781
+ descriptor.devicePath,
782
+ 'resourceBundles[].devicePath'
783
+ );
784
+ sources.push({
785
+ name: descriptor.name,
786
+ source: await this.openProtocolV2PreparedSource(artifact),
787
+ devicePath,
788
+ version: descriptor.version,
789
+ payloadHash: descriptor.payloadHash,
790
+ headerHash: descriptor.headerHash,
791
+ });
792
+ artifactByName.delete(descriptor.name);
793
+ }
794
+ if (artifactByName.size > 0) {
795
+ throw ERRORS.TypedError(
796
+ HardwareErrorCode.RuntimeError,
797
+ `Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
798
+ );
799
+ }
800
+ return sources;
801
+ }
802
+
803
+ private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
804
+ try {
805
+ this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
806
+ const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
807
+ const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
808
+ if (installSources.length === 0 && resourceSources.length === 0) {
809
+ throw ERRORS.TypedError(
810
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
811
+ 'No firmware to update'
812
+ );
813
+ }
814
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
815
+
816
+ return await this.executeProtocolV2SourceUpdate({
817
+ installSources,
818
+ resourceSources,
819
+ });
820
+ } finally {
821
+ await this.closeProtocolV2PreparedSources();
822
+ }
823
+ }
824
+
825
+ private validateExpectedTargetVersions() {
826
+ const expected = this.params.expectedTargetVersions;
827
+ if (expected === undefined) return;
828
+ if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
829
+ throw ERRORS.TypedError(
830
+ HardwareErrorCode.RuntimeError,
831
+ 'Protocol V2 expected target versions are invalid'
832
+ );
833
+ }
834
+ for (const [target, version] of Object.entries(expected)) {
835
+ if (
836
+ !Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
837
+ target as FirmwareUpdateV4Target
838
+ ) ||
839
+ typeof version !== 'string' ||
840
+ !/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
841
+ version.length > 64
842
+ ) {
843
+ throw ERRORS.TypedError(
844
+ HardwareErrorCode.RuntimeError,
845
+ 'Protocol V2 expected target version is invalid'
846
+ );
847
+ }
848
+ }
849
+ }
850
+
851
+ private getExpectedProtocolV2TargetVersion(targetId: number) {
852
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
853
+ const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
854
+ if (!version) return undefined;
855
+ const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
856
+ if (
857
+ parts.length !== 3 ||
858
+ parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
859
+ ) {
860
+ return undefined;
861
+ }
862
+ return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
863
+ }
864
+
865
+ private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
866
+ const expected = this.params?.expectedTargetVersions;
867
+ if (!expected) return;
868
+ const features = this.protocolV2LatestFinalFeatures;
869
+ const requestedTargets = this.getProtocolV2RequestedTargets();
870
+ const applicationTargets = requestedTargets.filter(
871
+ target => target === 'app_v1' || target === 'app_v2'
872
+ );
873
+ const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
874
+ boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
875
+ coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
876
+ se01: features?.se01Version ?? undefined,
877
+ se02: features?.se02Version ?? undefined,
878
+ se03: features?.se03Version ?? undefined,
879
+ se04: features?.se04Version ?? undefined,
880
+ };
881
+ if (features && applicationTargets.length === 1) {
882
+ visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
883
+ }
884
+ const expectedEntries = (
885
+ Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
886
+ ).filter(([target]) => !targets || targets.includes(target));
887
+ for (const [target, expectedVersion] of expectedEntries) {
888
+ const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
889
+ ([, mappedTarget]) => mappedTarget === target
890
+ )?.[0];
891
+ const statusVersion =
892
+ targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
893
+ const observedVersion =
894
+ statusVersion === undefined
895
+ ? visibleVersions[target]
896
+ : `${Math.floor(statusVersion / 0x10000) % 0x100}.${
897
+ Math.floor(statusVersion / 0x100) % 0x100
898
+ }.${statusVersion % 0x100}`;
899
+ if (!observedVersion) {
900
+ if (this.protocolV2FinalStatusVerified) {
901
+ Log.warn(
902
+ `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
903
+ );
904
+ } else {
905
+ throw ERRORS.TypedError(
906
+ HardwareErrorCode.FirmwareVerificationFailed,
907
+ `Protocol V2 target ${target} has no observable final version after status fallback`
908
+ );
909
+ }
910
+ } else if (observedVersion !== expectedVersion) {
911
+ throw ERRORS.TypedError(
912
+ HardwareErrorCode.FirmwareVerificationFailed,
913
+ `Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
914
+ );
915
+ }
916
+ }
917
+ }
918
+
919
+ private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
920
+ if (this.params.targetsToUpdate?.length) {
921
+ return [...new Set(this.params.targetsToUpdate)];
922
+ }
923
+ const targets = new Set<FirmwareUpdateV4Target>();
924
+ Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
925
+ targets.add(target as FirmwareUpdateV4Target)
926
+ );
927
+ if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
928
+ targets.add('resource');
929
+ }
930
+ if (this.params.bootloaderBinary) targets.add('boot');
931
+ if (this.params.applicationP1Binary) targets.add('app_v1');
932
+ if (this.params.applicationP2Binary) targets.add('app_v2');
933
+ if (this.params.coprocessorBinary) targets.add('coprocessor');
934
+ if (this.params.se01Binary) targets.add('se01');
935
+ if (this.params.se02Binary) targets.add('se02');
936
+ if (this.params.se03Binary) targets.add('se03');
937
+ if (this.params.se04Binary) targets.add('se04');
938
+ return Array.from(targets);
595
939
  }
596
940
 
597
941
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
@@ -613,32 +957,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
613
957
  private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
614
958
  assertProtocolV2ReconnectIdentity(
615
959
  this.protocolV2ExpectedSerialNumber,
616
- this.getProtocolV2SerialNumber(deviceInfo),
617
- this.protocolV2ExpectedPath,
618
- this.device.originalDescriptor.path
960
+ this.getProtocolV2SerialNumber(deviceInfo)
619
961
  );
620
962
  }
621
963
 
622
- private getProtocolV2DeviceInfoTimeout() {
623
- return this.isBleReconnect()
624
- ? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
625
- : PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
626
- }
627
-
628
964
  private async requestProtocolV2PhysicalIdentity() {
629
965
  return requestProtocolV2DeviceInfo({
630
966
  commands: this.device.getCommands(),
631
- timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
967
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
632
968
  });
633
969
  }
634
970
 
635
971
  private async captureProtocolV2PhysicalIdentity() {
636
972
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
637
973
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
638
- const path = this.device.originalDescriptor.path?.trim() || undefined;
639
- assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
974
+ if (this.params?.preparedPlan) {
975
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
976
+ preparedPlan: this.params.preparedPlan,
977
+ deviceIdentity: serialNumber,
978
+ });
979
+ } else if (this.params?.expectedDeviceId) {
980
+ assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
981
+ } else {
982
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
983
+ }
640
984
  this.protocolV2ExpectedSerialNumber = serialNumber;
641
- this.protocolV2ExpectedPath = path;
642
985
  }
643
986
 
644
987
  private async verifyProtocolV2ReconnectIdentity() {
@@ -653,7 +996,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
653
996
 
654
997
  private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
655
998
  return (
656
- !!this.params.resourceFiles?.length ||
999
+ !!this.params.resourceBundleFiles?.length ||
657
1000
  !!this.params.bootloaderBinary ||
658
1001
  fwBinaryMap.length > 0
659
1002
  );
@@ -716,6 +1059,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
716
1059
  return target;
717
1060
  }
718
1061
 
1062
+ private getProtocolV2ResourceFilePath(path: string) {
1063
+ if (path.startsWith('vol')) return path;
1064
+ if (path.startsWith('/')) return `vol0:${path}`;
1065
+ return `vol0:/${path}`;
1066
+ }
1067
+
1068
+ private async readProtocolV2DeviceFileHeader(path: string) {
1069
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1070
+ const filePath = this.getProtocolV2ResourceFilePath(path);
1071
+ const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
1072
+ path: filePath,
1073
+ });
1074
+ const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1075
+ if (
1076
+ !pathInfoRes.message?.exist ||
1077
+ pathInfoRes.message?.directory ||
1078
+ fileSize === undefined ||
1079
+ fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
1080
+ ) {
1081
+ return null;
1082
+ }
1083
+
1084
+ const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
1085
+ const chunks: Uint8Array[] = [];
1086
+ let offset = 0;
1087
+ while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
1088
+ const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
1089
+ const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
1090
+ file: {
1091
+ path: filePath,
1092
+ offset,
1093
+ total_size: 0,
1094
+ },
1095
+ chunk_len: readLen,
1096
+ ui_percentage: undefined,
1097
+ });
1098
+ const data = toProtocolV2Bytes(res.message?.data);
1099
+ if (data.byteLength === 0) return null;
1100
+ chunks.push(data);
1101
+ offset += data.byteLength;
1102
+ }
1103
+
1104
+ const headerBytes = new Uint8Array(offset);
1105
+ let cursor = 0;
1106
+ chunks.forEach(chunk => {
1107
+ headerBytes.set(chunk, cursor);
1108
+ cursor += chunk.byteLength;
1109
+ });
1110
+ return parseProtocolV2OkppHeader(headerBytes);
1111
+ }
1112
+
719
1113
  private async downloadRemoteProtocolV2Component(
720
1114
  key: string,
721
1115
  component: IProtocolV2FirmwareComponent
@@ -804,159 +1198,155 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
804
1198
  };
805
1199
  }
806
1200
 
807
- private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
808
- const deviceType = this.device.getCurrentDeviceType();
809
- if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
810
- throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
811
- }
812
-
813
- private async prepareProtocolV2BootResources(): Promise<
814
- ProtocolV2ResourceBundleBinary[] | undefined
815
- > {
816
- if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
817
- const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
818
- if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
1201
+ // ============================================================
1202
+ // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
1203
+ // ============================================================
819
1204
 
820
- const files: ProtocolV2ResourceBundleBinary[] = [];
821
- for (const file of resource.files) {
822
- Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
823
- const { binary } = await getSysResourceBinary(file.url);
824
- if (!isProtocolV2ResourceFileValid(binary, file)) {
825
- throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
826
- }
827
- files.push({
828
- name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
829
- binary,
830
- devicePath: file.devicePath,
1205
+ /**
1206
+ * Prepare the RESC bundle list.
1207
+ *
1208
+ * Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
1209
+ * - resourceBundleFiles supplied: use binaries directly without version checks.
1210
+ * - Otherwise load release.resourceBundles from remote config.json; the binaries
1211
+ * are prefetched before reboot and synchronization skips matching device headers.
1212
+ */
1213
+ private prepareProtocolV2ResourceBundles(
1214
+ firmwareType: EFirmwareType,
1215
+ features: Features
1216
+ ): ProtocolV2ResourceBundleBinary[] | undefined {
1217
+ // Manual binaries are installed directly without comparison.
1218
+ if (this.params.resourceBundleFiles?.length) {
1219
+ return this.params.resourceBundleFiles.map((file, index) => {
1220
+ const devicePath = validateProtocolV2FilesystemPath(
1221
+ file.devicePath,
1222
+ `resourceBundleFiles[${index}].devicePath`
1223
+ );
1224
+ return {
1225
+ name: devicePath.split('/').pop() ?? devicePath,
1226
+ binary: file.binary,
1227
+ devicePath,
1228
+ };
831
1229
  });
832
1230
  }
833
- return files;
834
- }
835
1231
 
836
- private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
837
- const files = this.params.resourceFiles ?? [];
838
- if (!files?.length) return undefined;
839
-
840
- const prepared = files.map((file, index) => {
841
- const devicePath = validateProtocolV2FilesystemPath(
842
- file.devicePath,
843
- `resourceFiles[${index}].devicePath`
844
- );
845
- const descriptor = file as typeof file &
846
- Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
847
- if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
848
- throw new Error(`resourceFiles[${index}] size mismatch`);
849
- }
850
- if (
851
- descriptor.fileHash &&
852
- !isProtocolV2ResourceFileValid(file.binary, {
853
- size: file.binary.byteLength,
854
- fileHash: descriptor.fileHash,
855
- })
856
- ) {
857
- throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
858
- }
859
- return {
860
- name: devicePath.split('/').pop() ?? devicePath,
861
- binary: file.binary,
862
- devicePath,
863
- };
864
- });
865
- if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
866
- throw new Error('resourceFiles contain duplicate devicePath values');
867
- }
868
- return prepared;
869
- }
870
-
871
- private async prepareProtocolV2ResourceBundles(): Promise<
872
- ProtocolV2ResourceBundleBinary[] | undefined
873
- > {
874
1232
  if (!this.params.targetsToUpdate?.includes('resource')) {
875
1233
  return undefined;
876
1234
  }
877
1235
 
878
- const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
879
- if (!resources?.length) {
880
- throw new Error('Missing Pro2 stable resource configuration');
881
- }
882
-
883
- const inventory = this.params.forcedUpdateRes
884
- ? undefined
885
- : await readProtocolV2ResourceInventory({
886
- commands: this.device.getCommands(),
887
- resources,
888
- chunkSize: this.getProtocolV2FirmwareChunkSize(),
889
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
890
- });
891
- const plan = buildProtocolV2ResourceUpdatePlan({
892
- resources,
893
- inventory,
894
- mode: 'bootloader-recovery',
895
- forced: this.params.forcedUpdateRes,
896
- });
897
- Log.log(
898
- `[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
899
- );
900
-
901
- const bundles: ProtocolV2ResourceBundleBinary[] = [];
902
- for (const resource of plan.resources) {
903
- bundles.push(await this.downloadProtocolV2Resource(resource));
1236
+ // Remote-config bundles are downloaded and compared later.
1237
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
1238
+ if (!release?.resourceBundles?.length) return undefined;
1239
+
1240
+ return release.resourceBundles.map((bundle, index) => ({
1241
+ name: bundle.name,
1242
+ binary: new ArrayBuffer(0),
1243
+ devicePath: validateProtocolV2FilesystemPath(
1244
+ bundle.devicePath,
1245
+ `resourceBundles[${index}].devicePath`
1246
+ ),
1247
+ url: bundle.url,
1248
+ version: bundle.version,
1249
+ payloadHash: bundle.payloadHash,
1250
+ headerHash: bundle.headerHash,
1251
+ })) as ProtocolV2ResourceBundleBinary[];
1252
+ }
1253
+
1254
+ private async materializeProtocolV2ResourceBundles(
1255
+ bundles: ProtocolV2ResourceBundleBinary[]
1256
+ ): Promise<ProtocolV2ResourceBundleBinary[]> {
1257
+ for (const bundle of bundles) {
1258
+ let { binary } = bundle;
1259
+ let downloadedFromRemote = false;
1260
+ if (binary.byteLength === 0) {
1261
+ if (!bundle.url) {
1262
+ throw ERRORS.TypedError(
1263
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1264
+ `Missing Protocol V2 RESC bundle binary: ${bundle.name}`
1265
+ );
1266
+ }
1267
+ if (
1268
+ this.params.artifactReader ||
1269
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
1270
+ ) {
1271
+ throw ERRORS.TypedError(
1272
+ HardwareErrorCode.RuntimeError,
1273
+ `Firmware resource bundle ${bundle.name} is not prepared`,
1274
+ {
1275
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1276
+ artifactName: bundle.name,
1277
+ }
1278
+ );
1279
+ }
1280
+ Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
1281
+ ({ binary } = await getSysResourceBinary(bundle.url));
1282
+ bundle.binary = binary;
1283
+ downloadedFromRemote = true;
1284
+ }
1285
+ if (bundle.binary.byteLength === 0) {
1286
+ throw ERRORS.TypedError(
1287
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1288
+ `Protocol V2 RESC bundle is empty: ${bundle.name}`
1289
+ );
1290
+ }
1291
+ if (downloadedFromRemote) {
1292
+ const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
1293
+ if (!header || header.type !== 'RESC') {
1294
+ throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
1295
+ }
1296
+ if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
1297
+ throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
1298
+ }
1299
+ const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
1300
+ if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
1301
+ throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
1302
+ }
1303
+ const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
1304
+ if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
1305
+ throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
1306
+ }
1307
+ }
904
1308
  }
905
1309
  return bundles;
906
1310
  }
907
1311
 
908
- private async downloadProtocolV2Resource(
909
- resource: IProtocolV2Resource
910
- ): Promise<ProtocolV2ResourceBundleBinary> {
911
- Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
912
- const { binary } = await getSysResourceBinary(resource.url);
913
- if (!isProtocolV2ResourceFileValid(binary, resource)) {
914
- throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
915
- }
916
- return {
917
- name: `${resource.type}.okpkg`,
918
- binary,
919
- devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
920
- };
921
- }
922
-
923
- private async syncProtocolV2ResourceBundles(
924
- bundles: ProtocolV2ResourceBundleBinary[],
925
- firmwareSize: number
926
- ): Promise<{ processedSize: number; totalSize: number }> {
927
- const transferStartTime = Date.now();
928
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1312
+ /**
1313
+ * Compare the existing okpkg OKPP header in remote-config mode.
1314
+ */
1315
+ private async isProtocolV2ResourceBundleUpToDate(
1316
+ bundle: Pick<
1317
+ ProtocolV2ResourceBundleBinary,
1318
+ 'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1319
+ >
1320
+ ): Promise<boolean> {
1321
+ if (this.params?.forcedUpdateRes) return false;
1322
+ if (!bundle.version && !bundle.payloadHash) return false;
929
1323
 
930
- // Write directly to the device through FileWrite.
931
- let totalSize = 0;
932
- for (const b of bundles) totalSize += b.binary.byteLength;
1324
+ try {
1325
+ const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
1326
+ if (!header) return false;
933
1327
 
934
- const transferTotalSize = totalSize + firmwareSize;
935
- let processedSize = 0;
936
- for (const bundle of bundles) {
937
- Log.log(
938
- `[FirmwareUpdateV4] syncing resource ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
939
- );
940
- processedSize = await this.protocolV2CommonUpdateProcess({
941
- payload: bundle.binary,
942
- filePath: bundle.devicePath,
943
- processedSize,
944
- totalSize: transferTotalSize,
945
- });
1328
+ if (bundle.version) {
1329
+ const cmp = compareProtocolV2Versions(header.version, bundle.version);
1330
+ if (cmp === undefined || cmp !== 0) return false;
1331
+ }
1332
+ if (bundle.payloadHash) {
1333
+ const expected = normalizeProtocolV2Hex(bundle.payloadHash);
1334
+ if (expected && header.payloadHash !== expected) return false;
1335
+ }
1336
+ if (bundle.headerHash) {
1337
+ const expected = normalizeProtocolV2Hex(bundle.headerHash);
1338
+ if (expected && header.headerHash !== expected) return false;
1339
+ }
1340
+ return true;
1341
+ } catch (error) {
1342
+ Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
1343
+ return false;
946
1344
  }
947
-
948
- const elapsedMs = Date.now() - transferStartTime;
949
- Log.log(
950
- `[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
951
- elapsedMs / 1000
952
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
953
- );
954
- return { processedSize, totalSize: transferTotalSize };
955
1345
  }
956
1346
 
957
1347
  private isProtocolV2BootloaderMode() {
958
1348
  if (typeof this.device.isBootloader === 'function') {
959
- return !!this.device.isBootloader();
1349
+ return this.device.isBootloader();
960
1350
  }
961
1351
  return (
962
1352
  this.device.features?.mode === 'bootloader' ||
@@ -965,11 +1355,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
965
1355
  }
966
1356
 
967
1357
  private isProtocolV2RomloaderMode() {
968
- const deviceType = this.device.getCurrentDeviceType();
969
- if (
970
- !this.device.isProtocolV2() ||
971
- (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
972
- ) {
1358
+ if (!this.device.isProtocolV2() || this.device.getCurrentDeviceType() !== EDeviceType.Pro2) {
973
1359
  return false;
974
1360
  }
975
1361
  if (typeof this.device.isRomloader === 'function') {
@@ -983,20 +1369,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
983
1369
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
984
1370
  if (this.isProtocolV2RomloaderMode()) {
985
1371
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
1372
+ this.protocolV2ExecutionInLoader = true;
986
1373
  return false;
987
1374
  }
988
1375
  if (this.isProtocolV2BootloaderMode()) {
989
1376
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
990
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1377
+ this.protocolV2ExecutionInLoader = true;
991
1378
  return false;
992
1379
  }
993
1380
 
994
1381
  try {
995
1382
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
996
1383
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
1384
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
997
1385
  await wait(1000);
998
1386
  await this.waitForProtocolV2BootloaderMode();
999
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1387
+ this.protocolV2ExecutionInLoader = true;
1000
1388
  return true;
1001
1389
  } catch (error) {
1002
1390
  if (error instanceof HardwareError) {
@@ -1013,17 +1401,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1013
1401
  ) {
1014
1402
  const startTime = Date.now();
1015
1403
  let lastError: unknown;
1016
- let shouldReconnect = true;
1017
1404
 
1018
1405
  while (Date.now() - startTime < timeout) {
1019
1406
  try {
1020
- if (shouldReconnect) {
1021
- await this.reconnectProtocolV2Device();
1022
- shouldReconnect = false;
1023
- }
1407
+ await this.reconnectProtocolV2Device();
1024
1408
  const deviceInfo = await requestProtocolV2DeviceInfo({
1025
1409
  commands: this.device.getCommands(),
1026
- timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
1410
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1027
1411
  });
1028
1412
  this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1029
1413
  const features = await this.device.probeProtocolV2RuntimeState(
@@ -1038,7 +1422,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1038
1422
  if (this.isProtocolV2ReconnectIdentityError(error)) {
1039
1423
  throw error;
1040
1424
  }
1041
- shouldReconnect = true;
1042
1425
  lastError = error;
1043
1426
  Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
1044
1427
  }
@@ -1091,6 +1474,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1091
1474
  return entries;
1092
1475
  }
1093
1476
 
1477
+ private buildProtocolV2ExecutionPhases({
1478
+ installSources,
1479
+ resourceSources,
1480
+ }: {
1481
+ installSources: ProtocolV2InstallSource[];
1482
+ resourceSources: ProtocolV2ResourceBundleSource[];
1483
+ }): ProtocolV2ExecutionPhase[] {
1484
+ const phases: ProtocolV2ExecutionPhase[] = [];
1485
+ if (resourceSources.length > 0) {
1486
+ phases.push({
1487
+ kind: 'resource-sync',
1488
+ installSources: [],
1489
+ resourceSources,
1490
+ });
1491
+ }
1492
+ const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
1493
+ if (bootloaderSources.length > 0) {
1494
+ phases.push(
1495
+ {
1496
+ kind: 'bootloader-install',
1497
+ installSources: bootloaderSources,
1498
+ resourceSources: [],
1499
+ },
1500
+ {
1501
+ kind: 'bootloader-verify',
1502
+ installSources: [],
1503
+ resourceSources: [],
1504
+ }
1505
+ );
1506
+ }
1507
+ const componentSources = installSources.filter(source => source.kind !== 'bootloader');
1508
+ if (componentSources.length > 0) {
1509
+ phases.push({
1510
+ kind: 'component-install',
1511
+ installSources: componentSources,
1512
+ resourceSources: [],
1513
+ });
1514
+ }
1515
+ phases.push({
1516
+ kind: 'final-verify',
1517
+ installSources: [],
1518
+ resourceSources: [],
1519
+ });
1520
+ return phases;
1521
+ }
1522
+
1523
+ private async executeProtocolV2Phases({
1524
+ installSources,
1525
+ resourceSources,
1526
+ }: {
1527
+ installSources: ProtocolV2InstallSource[];
1528
+ resourceSources: ProtocolV2ResourceBundleSource[];
1529
+ }) {
1530
+ const phases = this.buildProtocolV2ExecutionPhases({
1531
+ installSources,
1532
+ resourceSources,
1533
+ });
1534
+ for (const phase of phases) {
1535
+ if (phase.kind === 'final-verify') {
1536
+ return this.completeProtocolV2FinalVerification();
1537
+ }
1538
+ if (phase.kind === 'resource-sync') {
1539
+ await this.enterProtocolV2BootloaderMode();
1540
+ await this.executeProtocolV2TransferPhase(phase);
1541
+ } else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
1542
+ await this.enterProtocolV2BootloaderMode();
1543
+ await this.executeProtocolV2TransferPhase(phase);
1544
+ await this.exitProtocolV2BootloaderToNormal();
1545
+ } else if (phase.kind === 'bootloader-verify') {
1546
+ await this.waitForProtocolV2FinalFeatures();
1547
+ this.assertExpectedProtocolV2Versions(['boot']);
1548
+ }
1549
+ }
1550
+ throw ERRORS.TypedError(
1551
+ HardwareErrorCode.RuntimeError,
1552
+ 'Protocol V2 execution has no final verification phase'
1553
+ );
1554
+ }
1555
+
1556
+ private async executeProtocolV2SourceUpdate({
1557
+ installSources,
1558
+ resourceSources,
1559
+ }: {
1560
+ installSources: ProtocolV2InstallSource[];
1561
+ resourceSources: ProtocolV2ResourceBundleSource[];
1562
+ }) {
1563
+ return this.executeProtocolV2Phases({
1564
+ installSources,
1565
+ resourceSources,
1566
+ });
1567
+ }
1568
+
1094
1569
  private async executeProtocolV2Update({
1095
1570
  fwBinaryMap,
1096
1571
  bootloaderBinary,
@@ -1102,104 +1577,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1102
1577
  installItems?: ProtocolV2InstallItem[];
1103
1578
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1104
1579
  }) {
1105
- const orderedInstallItems =
1580
+ const memoryInstallItems =
1106
1581
  installItems ??
1107
1582
  this.buildProtocolV2InstallItems({
1108
- bootloaderBinary: bootloaderBinary ?? null,
1109
1583
  fwBinaryMap: fwBinaryMap ?? [],
1584
+ bootloaderBinary: bootloaderBinary ?? null,
1110
1585
  });
1111
-
1112
- let firmwareSize = 0;
1113
- for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1114
-
1115
- this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1116
-
1117
- let processedSize = 0;
1118
- let totalSize = firmwareSize;
1119
- if (resourceBundles?.length) {
1120
- const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1121
- resourceBundles,
1122
- firmwareSize
1586
+ try {
1587
+ const installSources = await Promise.all(
1588
+ memoryInstallItems.map(async item => ({
1589
+ fileName: item.fileName,
1590
+ source: await this.openProtocolV2MemorySource(item.binary),
1591
+ targetId: item.targetId,
1592
+ kind: item.kind,
1593
+ }))
1123
1594
  );
1124
- processedSize = resourceTransfer.processedSize;
1125
- totalSize = resourceTransfer.totalSize;
1595
+ const resourceSources = await Promise.all(
1596
+ (resourceBundles ?? []).map(async bundle => ({
1597
+ name: bundle.name,
1598
+ source: await this.openProtocolV2MemorySource(bundle.binary),
1599
+ devicePath: bundle.devicePath,
1600
+ version: bundle.version,
1601
+ payloadHash: bundle.payloadHash,
1602
+ headerHash: bundle.headerHash,
1603
+ }))
1604
+ );
1605
+ return await this.executeProtocolV2Phases({
1606
+ installSources,
1607
+ resourceSources,
1608
+ });
1609
+ } finally {
1610
+ await this.closeProtocolV2PreparedSources();
1126
1611
  }
1612
+ }
1127
1613
 
1128
- // Skip staging and installation when the update contains resource files only.
1129
- if (orderedInstallItems.length === 0) {
1130
- Log.log('[FirmwareUpdateV4] no firmware targets to install (resource files only)');
1131
- if (totalSize > 0) {
1132
- this.postProgressMessage(100, 'transferData');
1614
+ private async executeProtocolV2TransferPhase({
1615
+ installSources,
1616
+ resourceSources,
1617
+ }: ProtocolV2ExecutionPhase) {
1618
+ let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
1619
+ const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
1620
+ for (const resource of resourceSources) {
1621
+ if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
1622
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
1623
+ } else {
1624
+ resourcesToSync.push(resource);
1625
+ totalSize += resource.source.size;
1133
1626
  }
1134
- return;
1135
1627
  }
1136
- let transferredSize = processedSize;
1137
- const transferStartTime = Date.now();
1138
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1139
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1140
- const onTransferredBytes = (bytes: number) => {
1141
- transferredSize = bytes;
1142
- };
1143
- Log.log(
1144
- `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1145
- );
1146
-
1147
- const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1148
-
1149
- try {
1150
- for (const item of orderedInstallItems) {
1151
- const filePath = this.getProtocolV2InstallItemStagingPath(item);
1152
- Log.log(
1153
- `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1154
- );
1155
- processedSize = await this.protocolV2CommonUpdateProcess({
1156
- payload: item.binary,
1157
- filePath,
1158
- processedSize,
1159
- totalSize,
1160
- onTransferredBytes,
1161
- });
1162
- transferredSize = processedSize;
1163
- await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1164
1628
 
1165
- stagedInstallTargets.push({
1166
- ...item,
1167
- path: filePath,
1168
- });
1169
- }
1629
+ this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1630
+ let processedSize = 0;
1631
+ for (const resource of resourcesToSync) {
1632
+ processedSize = await this.protocolV2SourceUpdateProcess({
1633
+ source: resource.source,
1634
+ filePath: resource.devicePath,
1635
+ processedSize,
1636
+ totalSize,
1637
+ });
1638
+ await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
1639
+ }
1170
1640
 
1171
- if (totalSize > 0) {
1172
- this.postProgressMessage(100, 'transferData');
1173
- }
1641
+ const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
1642
+ for (const item of installSources) {
1643
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1644
+ processedSize = await this.protocolV2SourceUpdateProcess({
1645
+ source: item.source,
1646
+ filePath,
1647
+ processedSize,
1648
+ totalSize,
1649
+ });
1650
+ await this.verifyProtocolV2StagedFile(filePath, item.source.size);
1651
+ stagedInstallTargets.push({
1652
+ targetId: item.targetId,
1653
+ path: filePath,
1654
+ });
1655
+ }
1174
1656
 
1175
- const elapsedMs = Date.now() - transferStartTime;
1176
- Log.log(
1177
- `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1178
- elapsedMs / 1000
1179
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1180
- );
1181
- } catch (error) {
1182
- const elapsedMs = Date.now() - transferStartTime;
1183
- Log.warn(
1184
- `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1185
- elapsedMs / 1000
1186
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1187
- );
1188
- throw error;
1657
+ if (totalSize > 0) {
1658
+ this.postProgressMessage(100, 'transferData');
1659
+ }
1660
+ if (stagedInstallTargets.length === 0) {
1661
+ return;
1189
1662
  }
1190
1663
 
1191
1664
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1192
-
1193
- const allTargets = stagedInstallTargets.map(item => ({
1665
+ const targets = stagedInstallTargets.map(item => ({
1194
1666
  target_id: item.targetId,
1195
1667
  path: item.path,
1196
1668
  }));
1197
- Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1198
- await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1199
- await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1669
+ await this.protocolV2StartFirmwareUpdate({ targets });
1670
+ await this.waitForProtocolV2FirmwareUpdateComplete(targets);
1671
+ }
1672
+
1673
+ private async protocolV2SourceUpdateProcess({
1674
+ source,
1675
+ filePath,
1676
+ processedSize,
1677
+ totalSize,
1678
+ }: {
1679
+ source: FirmwareByteSource;
1680
+ filePath: string;
1681
+ processedSize: number;
1682
+ totalSize: number;
1683
+ }) {
1684
+ let lastError: unknown;
1685
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1686
+ try {
1687
+ await writeFirmwareByteSource({
1688
+ source,
1689
+ chunkSize: this.getProtocolV2FirmwareChunkSize(),
1690
+ write: async ({ data, sourceOffset, length, first }) => {
1691
+ const chunkEnd = sourceOffset + length;
1692
+ const progress = getProtocolV2DeviceTransferProgress(
1693
+ processedSize + sourceOffset,
1694
+ processedSize + chunkEnd,
1695
+ totalSize
1696
+ );
1697
+ const response = await this.fileWriteChunk(
1698
+ filePath,
1699
+ source.size,
1700
+ sourceOffset,
1701
+ data,
1702
+ first,
1703
+ progress
1704
+ );
1705
+ const rawProcessedByte = response.message.processed_byte;
1706
+ const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
1707
+ if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
1708
+ throw ERRORS.TypedError(
1709
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1710
+ `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
1711
+ );
1712
+ }
1713
+ this.postProgressMessage(progress, 'transferData');
1714
+ return length;
1715
+ },
1716
+ });
1717
+ return processedSize + source.size;
1718
+ } catch (error) {
1719
+ lastError = error;
1720
+ if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1721
+ await this.recoverProtocolV2FileTransfer();
1722
+ }
1723
+ }
1724
+ }
1725
+ throw ERRORS.TypedError(
1726
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1727
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1728
+ );
1200
1729
  }
1201
1730
 
1202
- private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1731
+ private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1203
1732
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1204
1733
  }
1205
1734
 
@@ -1218,7 +1747,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1218
1747
 
1219
1748
  private assertProtocolV2TargetStatus(
1220
1749
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1221
- expectedTargetIds: Set<number>
1750
+ expectedTargetIds: Set<number>,
1751
+ expectedPaths = new Map<number, string>()
1222
1752
  ) {
1223
1753
  Log.log(
1224
1754
  `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
@@ -1249,17 +1779,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1249
1779
  const matchingTargets = statusTargets.filter(target =>
1250
1780
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1251
1781
  );
1782
+ const seenTargetIds = new Set<number>();
1783
+ for (const target of matchingTargets) {
1784
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
1785
+ if (
1786
+ targetId === undefined ||
1787
+ seenTargetIds.has(targetId) ||
1788
+ (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
1789
+ ) {
1790
+ throw ERRORS.TypedError(
1791
+ HardwareErrorCode.RuntimeError,
1792
+ `Protocol V2 install status conflicts with target ${target.target_id}`,
1793
+ {
1794
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
1795
+ }
1796
+ );
1797
+ }
1798
+ seenTargetIds.add(targetId);
1799
+ }
1800
+ const completedTargets = matchingTargets.filter(target =>
1801
+ isProtocolV2TargetStatusFinished(target.status)
1802
+ );
1252
1803
  const completedTargetIds = new Set<number>();
1253
- matchingTargets.forEach(target => {
1804
+ completedTargets.forEach(target => {
1254
1805
  const targetId = normalizeProtocolV2TargetId(target.target_id);
1255
- if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
1806
+ if (targetId !== undefined) {
1256
1807
  completedTargetIds.add(targetId);
1257
1808
  }
1258
1809
  });
1259
- const allExpectedTargetsCompleted =
1260
- expectedTargetIds.size > 0 &&
1261
- Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
1262
- if (allExpectedTargetsCompleted) {
1810
+ if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
1811
+ for (const target of completedTargets) {
1812
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
1813
+ const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
1814
+ if (targetId === undefined) {
1815
+ throw ERRORS.TypedError(
1816
+ HardwareErrorCode.RuntimeError,
1817
+ 'Protocol V2 completed target identity is invalid'
1818
+ );
1819
+ }
1820
+ const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
1821
+ if (expectedVersion !== undefined && payloadVersion !== undefined) {
1822
+ if (payloadVersion !== expectedVersion) {
1823
+ throw ERRORS.TypedError(
1824
+ HardwareErrorCode.FirmwareVerificationFailed,
1825
+ `Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
1826
+ );
1827
+ }
1828
+ }
1829
+ if (payloadVersion !== undefined) {
1830
+ this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
1831
+ }
1832
+ }
1263
1833
  this.postProgressMessage(100, 'installingFirmware');
1264
1834
  return true;
1265
1835
  }
@@ -1280,43 +1850,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1280
1850
  return false;
1281
1851
  }
1282
1852
 
1283
- private getProtocolV2MissingTargetIds(
1284
- statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1285
- expectedTargetIds: Set<number>
1286
- ) {
1287
- const reportedTargetIds = new Set<number>();
1288
- statusTargets.forEach(target => {
1289
- const targetId = normalizeProtocolV2TargetId(target.target_id);
1290
- if (targetId !== undefined) {
1291
- reportedTargetIds.add(targetId);
1292
- }
1293
- });
1294
- return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
1295
- }
1296
-
1297
1853
  private async waitForProtocolV2FirmwareUpdateComplete(
1298
1854
  targets: Array<{ target_id: number; path: string }>
1299
1855
  ) {
1856
+ this.protocolV2FinalStatusVerified = false;
1300
1857
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1858
+ const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1301
1859
  const startTime = Date.now();
1302
1860
  let lastError: unknown;
1303
- let shouldReconnect = true;
1304
- let deviceInfo: ProtocolV2DeviceInfo | undefined;
1305
- let missingTargetStatusSince: number | undefined;
1306
- let missingTargetStatusKey: string | undefined;
1307
- const resetMissingTargetStatusGrace = () => {
1308
- missingTargetStatusSince = undefined;
1309
- missingTargetStatusKey = undefined;
1310
- };
1311
1861
 
1312
1862
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
1313
1863
  try {
1314
- if (shouldReconnect) {
1315
- await this.reconnectProtocolV2Device();
1316
- deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1317
- shouldReconnect = false;
1318
- }
1319
- const currentDeviceInfo = deviceInfo;
1864
+ await this.reconnectProtocolV2Device();
1865
+ const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1320
1866
  try {
1321
1867
  const statusResponse = await this.device.getCommands().typedCall(
1322
1868
  'DeviceFirmwareUpdateStatusGet',
@@ -1328,89 +1874,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1328
1874
  path: true,
1329
1875
  },
1330
1876
  },
1331
- { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
1877
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1332
1878
  );
1333
- const statusTargets = (statusResponse.message.records ??
1334
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
1335
- if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
1336
- return;
1337
- }
1338
-
1339
1879
  if (
1340
- statusTargets.length === 0 &&
1341
- currentDeviceInfo &&
1342
- (await this.probeProtocolV2NormalMode(currentDeviceInfo))
1880
+ this.assertProtocolV2TargetStatus(
1881
+ (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1882
+ expectedTargetIds,
1883
+ expectedPaths
1884
+ )
1343
1885
  ) {
1344
- Log.log(
1345
- '[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
1346
- );
1347
- this.postProgressMessage(100, 'installingFirmware');
1886
+ this.protocolV2FinalStatusVerified = true;
1348
1887
  return;
1349
1888
  }
1350
-
1351
- const missingTargetIds = this.getProtocolV2MissingTargetIds(
1352
- statusTargets,
1353
- expectedTargetIds
1354
- );
1355
- if (missingTargetIds.length > 0) {
1356
- const now = Date.now();
1357
- const missingKey = missingTargetIds.join(',');
1358
- if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
1359
- missingTargetStatusSince = now;
1360
- missingTargetStatusKey = missingKey;
1361
- } else if (
1362
- now - missingTargetStatusSince >=
1363
- PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
1364
- ) {
1365
- const reportedTargetIds = statusTargets
1366
- .map(target => normalizeProtocolV2TargetId(target.target_id))
1367
- .filter((targetId): targetId is number => targetId !== undefined);
1368
- throw ERRORS.TypedError(
1369
- HardwareErrorCode.FirmwareError,
1370
- `Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
1371
- ','
1372
- )}`
1373
- );
1374
- }
1375
- lastError = new Error(
1376
- `Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
1377
- );
1378
- } else {
1379
- resetMissingTargetStatusGrace();
1380
- lastError = new Error('Protocol V2 firmware targets are still installing');
1381
- }
1889
+ lastError = new Error('Protocol V2 firmware targets are still installing');
1382
1890
  } catch (error) {
1383
- if (
1384
- error instanceof HardwareError &&
1385
- error.errorCode === HardwareErrorCode.FirmwareError
1386
- ) {
1891
+ if (isProtocolV2TerminalInstallStatusError(error)) {
1387
1892
  throw error;
1388
1893
  }
1389
- // Only consecutive full status dumps can prove that a record is absent.
1390
- // A reboot or transport interruption starts a fresh grace window.
1391
- resetMissingTargetStatusGrace();
1392
1894
  // App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
1393
1895
  // missing endpoint as completion only after the runtime probe confirms App mode.
1394
1896
  if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
1395
- if (!currentDeviceInfo) {
1396
- throw ERRORS.TypedError(
1397
- HardwareErrorCode.RuntimeError,
1398
- 'Protocol V2 device identity is unavailable during install polling'
1399
- );
1400
- }
1401
- if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
1897
+ if (await this.probeProtocolV2NormalMode(deviceInfo)) {
1402
1898
  Log.log(
1403
1899
  '[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
1404
1900
  );
1405
- this.postProgressMessage(100, 'installingFirmware');
1406
1901
  return;
1407
1902
  }
1408
1903
  lastError = new Error(
1409
1904
  'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
1410
1905
  );
1411
1906
  } else {
1412
- shouldReconnect = true;
1413
- deviceInfo = undefined;
1414
1907
  lastError = error;
1415
1908
  Log.log(
1416
1909
  '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
@@ -1423,12 +1916,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1423
1916
  if (this.isProtocolV2ReconnectIdentityError(error)) {
1424
1917
  throw error;
1425
1918
  }
1426
- if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1919
+ if (isProtocolV2TerminalInstallStatusError(error)) {
1920
+ throw error;
1921
+ }
1922
+ if (
1923
+ error instanceof HardwareError &&
1924
+ error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
1925
+ ) {
1427
1926
  throw error;
1428
1927
  }
1429
- shouldReconnect = true;
1430
- deviceInfo = undefined;
1431
- resetMissingTargetStatusGrace();
1432
1928
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1433
1929
  }
1434
1930
  await wait(1000);
@@ -1436,24 +1932,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1436
1932
 
1437
1933
  throw ERRORS.TypedError(
1438
1934
  HardwareErrorCode.RuntimeError,
1439
- `Protocol V2 firmware update status not complete within ${
1440
- PROTOCOL_V2_INSTALL_TIMEOUT / 1000
1441
- }s: ${this.normalizeErrorMessage(lastError)}`
1935
+ `Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
1442
1936
  );
1443
1937
  }
1444
1938
 
1445
1939
  private async exitProtocolV2BootloaderToNormal() {
1446
1940
  await this.reconnectProtocolV2Device();
1447
- const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1448
- try {
1449
- if (await this.probeProtocolV2NormalMode(deviceInfo)) {
1450
- Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
1451
- return;
1452
- }
1453
- } catch (error) {
1454
- Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
1455
- }
1941
+ await this.verifyProtocolV2ReconnectIdentity();
1942
+ // The connection may still be in bootloader. Request a Normal reboot directly;
1943
+ // repeating it after an automatic App reboot is idempotent.
1456
1944
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1945
+ this.protocolV2ExecutionInLoader = false;
1457
1946
  }
1458
1947
 
1459
1948
  private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
@@ -1469,13 +1958,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1469
1958
  PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1470
1959
  );
1471
1960
 
1472
- return this.getProtocolV2VersionResult(features);
1473
- }
1474
-
1475
- private getProtocolV2VersionResult(features: Features) {
1476
1961
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1477
1962
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1478
1963
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
1964
+ this.protocolV2LatestFinalFeatures = features;
1479
1965
  if (firmwareVersion === '0.0.0') {
1480
1966
  Log.warn(
1481
1967
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1489,20 +1975,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1489
1975
  };
1490
1976
  }
1491
1977
 
1978
+ private async completeProtocolV2FinalVerification() {
1979
+ if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
1980
+ await this.exitProtocolV2BootloaderToNormal();
1981
+ }
1982
+ const versions = await this.waitForProtocolV2FinalFeatures();
1983
+ this.assertExpectedProtocolV2Versions();
1984
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
1985
+ DevicePool.resetState();
1986
+ return versions;
1987
+ }
1988
+
1492
1989
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1493
1990
  const startTime = Date.now();
1494
1991
  let lastError: unknown;
1495
- let shouldReconnect = true;
1496
1992
 
1497
1993
  while (Date.now() - startTime < timeout) {
1498
1994
  try {
1499
- if (shouldReconnect) {
1500
- await this.reconnectProtocolV2Device();
1501
- shouldReconnect = false;
1502
- }
1995
+ await this.reconnectProtocolV2Device();
1503
1996
  const deviceInfo = await requestProtocolV2DeviceInfo({
1504
1997
  commands: this.device.getCommands(),
1505
- timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
1998
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1506
1999
  // Completion needs target versions only; keep scope aligned with the request.
1507
2000
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
1508
2001
  });
@@ -1511,22 +2004,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1511
2004
  deviceInfo,
1512
2005
  PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
1513
2006
  );
1514
- if (features.mode === 'normal' && !features.bootloaderMode) {
1515
- return features;
2007
+ if (features.mode !== 'normal' || features.bootloaderMode) {
2008
+ throw ERRORS.TypedError(
2009
+ HardwareErrorCode.DeviceNotFound,
2010
+ 'Protocol V2 device is still in bootloader mode'
2011
+ );
1516
2012
  }
1517
- lastError = ERRORS.TypedError(
1518
- HardwareErrorCode.DeviceNotFound,
1519
- 'Protocol V2 device is still in bootloader mode'
1520
- );
2013
+ return features;
1521
2014
  } catch (error) {
1522
2015
  if (this.isProtocolV2ReconnectIdentityError(error)) {
1523
2016
  throw error;
1524
2017
  }
1525
- shouldReconnect = true;
1526
2018
  lastError = error;
1527
2019
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
2020
+ await wait(1000);
1528
2021
  }
1529
- await wait(1000);
1530
2022
  }
1531
2023
 
1532
2024
  throw ERRORS.TypedError(
@@ -1550,13 +2042,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1550
2042
  DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
1551
2043
  devicesDescriptor.length === 1
1552
2044
  ) {
1553
- const descriptor = devicesDescriptor[0];
1554
- if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
1555
- throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1556
- }
1557
2045
  this.device.updateDescriptor(
1558
2046
  {
1559
- ...descriptor,
2047
+ ...devicesDescriptor[0],
1560
2048
  protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
1561
2049
  },
1562
2050
  true
@@ -1567,29 +2055,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1567
2055
  return;
1568
2056
  }
1569
2057
 
1570
- // Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
1571
- // Enumeration order is not stable and must never decide the reconnect target.
2058
+ // App and bootloader serials may differ temporarily. During V4 reconnect, accept
2059
+ // only a uniquely enumerated device instead of repeatedly using the old App path.
1572
2060
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
1573
2061
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1574
2062
  });
1575
- const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
1576
- const expectedPath = this.protocolV2ExpectedPath?.trim();
1577
- const reconnectDevice = deviceList.find(candidate => {
1578
- const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
1579
- if (expectedSerialNumber && candidateSerialNumber) {
1580
- return candidateSerialNumber === expectedSerialNumber;
1581
- }
1582
- return !!expectedPath && candidate.getConnectId() === expectedPath;
1583
- });
1584
- if (!reconnectDevice) {
2063
+ if (deviceList.length !== 1) {
1585
2064
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1586
2065
  }
1587
2066
 
1588
2067
  Log.debug(
1589
- 'Protocol V2 firmware reconnect using matched device:',
1590
- reconnectDevice.getConnectId()
2068
+ 'Protocol V2 firmware reconnect using single enumerated device:',
2069
+ deviceList[0].getConnectId()
1591
2070
  );
1592
- this.device.updateFromCache(reconnectDevice);
2071
+ this.device.updateFromCache(deviceList[0]);
1593
2072
  await this.ensureProtocolV2DeviceAcquired();
1594
2073
  this.device.commands.disposed = false;
1595
2074
  this.device.getCommands().mainId = this.device.mainId ?? '';
@@ -1613,99 +2092,35 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1613
2092
  return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
1614
2093
  }
1615
2094
 
1616
- private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1617
- let lastError: unknown;
1618
- for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1619
- try {
1620
- return await this.protocolV2WriteWholeFile(params);
1621
- } catch (error) {
1622
- lastError = error;
1623
- Log.error(
1624
- `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1625
- error
1626
- );
1627
- if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1628
- break;
1629
- }
1630
- await this.recoverProtocolV2FileTransfer();
1631
- }
1632
- }
1633
-
1634
- throw ERRORS.TypedError(
1635
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1636
- `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1637
- );
1638
- }
1639
-
1640
- private async protocolV2WriteWholeFile({
1641
- payload,
1642
- filePath,
1643
- processedSize,
1644
- totalSize,
1645
- onTransferredBytes,
1646
- }: ProtocolV2FileTransferParams) {
1647
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1648
- const getUploadProgress = (fileOffset: number) => {
1649
- if (totalSize !== undefined && processedSize !== undefined) {
1650
- return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1651
- }
1652
- return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1653
- };
1654
-
1655
- try {
1656
- await writeProtocolV2File({
1657
- commands: this.device.getCommands(),
2095
+ private async fileWriteChunk(
2096
+ filePath: string,
2097
+ totalFileSize: number,
2098
+ offset: number,
2099
+ chunk: ArrayBuffer | Buffer,
2100
+ overwrite: boolean,
2101
+ progress: number | null
2102
+ ): Promise<TypedResponseMessage<'FilesystemFile'>> {
2103
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
2104
+ const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
2105
+ file: {
1658
2106
  path: filePath,
1659
- data: payload,
1660
- totalSize: payload.byteLength,
1661
- chunkSize,
1662
- overwrite: true,
1663
- append: false,
1664
- writeWithResponse: true,
1665
- maxChunkRetries: 0,
1666
- getUiPercentage: ({ offset, chunkLength }) =>
1667
- getProtocolV2DeviceTransferProgress(
1668
- (processedSize ?? 0) + offset,
1669
- (processedSize ?? 0) + offset + chunkLength,
1670
- totalSize ?? payload.byteLength
1671
- ),
1672
- onProgress: progress => {
1673
- const transferredBytes = (processedSize ?? 0) + progress.transferredBytes;
1674
- onTransferredBytes?.(transferredBytes);
1675
- this.postProgressMessage(getUploadProgress(progress.transferredBytes), 'transferData', {
1676
- transferredBytes,
1677
- totalBytes: totalSize ?? payload.byteLength,
1678
- rateBytesPerSecond: progress.rateBytesPerSecond,
1679
- elapsedMs: progress.elapsedMs,
1680
- });
1681
- },
1682
- });
1683
- } catch (error) {
1684
- if (
1685
- error instanceof HardwareError &&
1686
- error.errorCode === HardwareErrorCode.RuntimeError &&
1687
- error.message.includes('FilesystemFileWrite')
1688
- ) {
1689
- throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, error.message);
2107
+ offset,
2108
+ total_size: totalFileSize,
2109
+ data: chunk,
2110
+ },
2111
+ overwrite,
2112
+ append: false,
2113
+ ui_percentage: progress ?? undefined,
2114
+ });
2115
+ if (writeRes.type !== 'FilesystemFile') {
2116
+ if ((writeRes as any).type === 'CallMethodError') {
2117
+ if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
2118
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
2119
+ }
1690
2120
  }
1691
- throw error;
1692
- }
1693
-
1694
- return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
1695
- }
1696
-
1697
- private getProtocolV2FirmwareTransferTransport() {
1698
- const env = DataManager.getSettings('env');
1699
- if (env && DataManager.isBleConnect(env)) {
1700
- return 'BLE';
1701
- }
1702
- if (
1703
- env &&
1704
- (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1705
- ) {
1706
- return 'WebUSB';
2121
+ throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
1707
2122
  }
1708
- return env ?? 'unknown';
2123
+ return writeRes;
1709
2124
  }
1710
2125
 
1711
2126
  private async recoverProtocolV2FileTransfer() {