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

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 (316) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
  2. package/__tests__/DeviceCommands.test.ts +59 -13
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +226 -3
  5. package/__tests__/connectSettings.test.ts +5 -47
  6. package/__tests__/core-initialization.test.ts +23 -0
  7. package/__tests__/device-connector-protocol.test.ts +81 -0
  8. package/__tests__/device-lifecycle-events.test.ts +105 -3
  9. package/__tests__/device-pool-state.test.ts +22 -0
  10. package/__tests__/device-settings.test.ts +39 -18
  11. package/__tests__/device-state-contract.test.ts +1 -0
  12. package/__tests__/device-state-mapper.test.ts +13 -1
  13. package/__tests__/device-utils.test.ts +48 -1
  14. package/__tests__/deviceSettings.test.ts +11 -1
  15. package/__tests__/deviceUploadNft.test.ts +316 -0
  16. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  17. package/__tests__/get-device-state.test.ts +132 -12
  18. package/__tests__/homescreen.test.ts +41 -1
  19. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  20. package/__tests__/kaspaSignTransaction.test.ts +3 -26
  21. package/__tests__/logBlockEvent.test.ts +1 -1
  22. package/__tests__/method-protocol-support.test.ts +19 -0
  23. package/__tests__/open-wallet-session.test.ts +568 -37
  24. package/__tests__/pro2Nft.test.ts +108 -0
  25. package/__tests__/protocol-binding.test.ts +89 -0
  26. package/__tests__/protocol-v2-firmware-targets.test.ts +33 -1
  27. package/__tests__/protocol-v2-resources.test.ts +343 -0
  28. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  29. package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
  30. package/__tests__/protocol-v2.test.ts +1586 -1213
  31. package/__tests__/protocolV2FileWrite.test.ts +193 -0
  32. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  33. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  34. package/__tests__/search-devices.test.ts +12 -3
  35. package/__tests__/tron-sign-message-init.test.ts +2 -2
  36. package/__tests__/uiProgressThrottle.test.ts +74 -0
  37. package/__tests__/uiPromiseRegistry.test.ts +115 -0
  38. package/dist/api/BaseMethod.d.ts +3 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts +3 -1
  41. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  42. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  43. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  44. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  45. package/dist/api/FirmwareUpdateV2.d.ts +0 -10
  46. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  47. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  48. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  49. package/dist/api/FirmwareUpdateV4.d.ts +15 -27
  50. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  51. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  52. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  53. package/dist/api/SearchDevices.d.ts.map +1 -1
  54. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  55. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  56. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  57. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  58. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  59. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/getBinary.d.ts +0 -7
  63. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  64. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  65. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  66. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  67. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  68. package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
  69. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  70. package/dist/api/index.d.ts +2 -0
  71. package/dist/api/index.d.ts.map +1 -1
  72. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  73. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  74. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  75. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  76. package/dist/api/polkadot/networks.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  78. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  79. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  81. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  82. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  83. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  84. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  85. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  86. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  87. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  88. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  89. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  90. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  91. package/dist/api/ton/TonSignProof.d.ts +1 -1
  92. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  93. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  94. package/dist/core/deviceEventRegistration.d.ts +2 -0
  95. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  96. package/dist/core/index.d.ts.map +1 -1
  97. package/dist/core/uiPromiseRegistry.d.ts +6 -0
  98. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  99. package/dist/data-manager/DataManager.d.ts +9 -4
  100. package/dist/data-manager/DataManager.d.ts.map +1 -1
  101. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  102. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  103. package/dist/device/Device.d.ts +29 -6
  104. package/dist/device/Device.d.ts.map +1 -1
  105. package/dist/device/DeviceCommands.d.ts +4 -4
  106. package/dist/device/DeviceCommands.d.ts.map +1 -1
  107. package/dist/device/DeviceConnector.d.ts +2 -1
  108. package/dist/device/DeviceConnector.d.ts.map +1 -1
  109. package/dist/device/DevicePool.d.ts.map +1 -1
  110. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  111. package/dist/events/device.d.ts +4 -0
  112. package/dist/events/device.d.ts.map +1 -1
  113. package/dist/events/logBlockEvent.d.ts.map +1 -1
  114. package/dist/events/ui-promise.d.ts +4 -2
  115. package/dist/events/ui-promise.d.ts.map +1 -1
  116. package/dist/events/ui-request.d.ts +31 -2
  117. package/dist/events/ui-request.d.ts.map +1 -1
  118. package/dist/events/ui-response.d.ts +10 -2
  119. package/dist/events/ui-response.d.ts.map +1 -1
  120. package/dist/index.d.ts +243 -247
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +3135 -3785
  123. package/dist/inject.d.ts +6 -1
  124. package/dist/inject.d.ts.map +1 -1
  125. package/dist/lowLevelInject.d.ts.map +1 -1
  126. package/dist/protocols/protocol-v2/features.d.ts +0 -4
  127. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  128. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  129. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  130. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  131. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  132. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  133. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  134. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  135. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  136. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  137. package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
  138. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  139. package/dist/topLevelInject.d.ts.map +1 -1
  140. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
  141. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  142. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  143. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  144. package/dist/types/api/deviceUnlock.d.ts +5 -1
  145. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  146. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  147. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  148. package/dist/types/api/export.d.ts +2 -4
  149. package/dist/types/api/export.d.ts.map +1 -1
  150. package/dist/types/api/firmwareUpdate.d.ts +4 -70
  151. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  152. package/dist/types/api/index.d.ts +7 -10
  153. package/dist/types/api/index.d.ts.map +1 -1
  154. package/dist/types/api/protocolV2.d.ts +3 -0
  155. package/dist/types/api/protocolV2.d.ts.map +1 -1
  156. package/dist/types/device.d.ts +2 -2
  157. package/dist/types/device.d.ts.map +1 -1
  158. package/dist/types/params.d.ts +1 -0
  159. package/dist/types/params.d.ts.map +1 -1
  160. package/dist/types/settings.d.ts +38 -21
  161. package/dist/types/settings.d.ts.map +1 -1
  162. package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
  163. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  164. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  165. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  166. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  167. package/dist/utils/deviceSettings.d.ts.map +1 -1
  168. package/dist/utils/homescreen.d.ts +4 -0
  169. package/dist/utils/homescreen.d.ts.map +1 -1
  170. package/dist/utils/index.d.ts +1 -1
  171. package/dist/utils/index.d.ts.map +1 -1
  172. package/dist/utils/patch.d.ts +1 -1
  173. package/dist/utils/patch.d.ts.map +1 -1
  174. package/dist/utils/pro2Nft.d.ts +31 -0
  175. package/dist/utils/pro2Nft.d.ts.map +1 -0
  176. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  177. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  178. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  179. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  180. package/package.json +4 -4
  181. package/src/api/BaseMethod.ts +8 -10
  182. package/src/api/CheckAllFirmwareRelease.ts +78 -64
  183. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  184. package/src/api/FirmwareUpdate.ts +7 -15
  185. package/src/api/FirmwareUpdateV2.ts +126 -298
  186. package/src/api/FirmwareUpdateV3.ts +63 -265
  187. package/src/api/FirmwareUpdateV4.ts +645 -1018
  188. package/src/api/GetPassphraseState.ts +7 -1
  189. package/src/api/OpenWalletSession.ts +71 -17
  190. package/src/api/SearchDevices.ts +3 -1
  191. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  192. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
  193. package/src/api/device/DeviceSettings.ts +5 -4
  194. package/src/api/device/DeviceUnlock.ts +8 -3
  195. package/src/api/device/DeviceUpdateBootloader.ts +6 -125
  196. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
  197. package/src/api/firmware/updateBootloader.ts +4 -19
  198. package/src/api/firmware/uploadFirmware.ts +39 -144
  199. package/src/api/helpers/protocolV2FileWrite.ts +252 -63
  200. package/src/api/index.ts +2 -0
  201. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  202. package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
  203. package/src/api/polkadot/networks.ts +3 -3
  204. package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
  205. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  206. package/src/api/protocol-v2/helpers.ts +1 -1
  207. package/src/api/solana/SolSignMessage.ts +1 -1
  208. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  209. package/src/api/solana/SolSignTransaction.ts +2 -2
  210. package/src/api/sui/SuiGetAddress.ts +1 -1
  211. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  212. package/src/api/sui/SuiSignMessage.ts +1 -1
  213. package/src/api/sui/SuiSignTransaction.ts +1 -1
  214. package/src/api/ton/TonGetAddress.ts +1 -1
  215. package/src/api/ton/TonSignMessage.ts +1 -1
  216. package/src/api/ton/TonSignProof.ts +1 -1
  217. package/src/api/tron/TronSignMessage.ts +2 -2
  218. package/src/api/tron/TronSignTransaction.ts +1 -1
  219. package/src/core/deviceEventRegistration.ts +4 -0
  220. package/src/core/index.ts +213 -111
  221. package/src/core/uiPromiseRegistry.ts +63 -0
  222. package/src/data/messages/messages-protocol-v2.json +25 -0
  223. package/src/data-manager/DataManager.ts +113 -41
  224. package/src/data-manager/TransportManager.ts +6 -0
  225. package/src/data-manager/connectSettings.ts +0 -11
  226. package/src/device/Device.ts +274 -49
  227. package/src/device/DeviceCommands.ts +31 -4
  228. package/src/device/DeviceConnector.ts +33 -13
  229. package/src/device/DevicePool.ts +21 -7
  230. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  231. package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
  232. package/src/events/device.ts +4 -0
  233. package/src/events/logBlockEvent.ts +1 -0
  234. package/src/events/ui-promise.ts +4 -2
  235. package/src/events/ui-request.ts +36 -2
  236. package/src/events/ui-response.ts +12 -2
  237. package/src/index.ts +0 -5
  238. package/src/inject.ts +60 -24
  239. package/src/lowLevelInject.ts +7 -8
  240. package/src/protocols/protocol-v2/features.ts +10 -7
  241. package/src/protocols/protocol-v2/firmware.ts +2 -0
  242. package/src/protocols/protocol-v2/resources.ts +394 -0
  243. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  244. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  245. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
  246. package/src/protocols/protocol-v2/walletSession.ts +214 -36
  247. package/src/topLevelInject.ts +7 -8
  248. package/src/types/api/checkAllFirmwareRelease.ts +3 -5
  249. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  250. package/src/types/api/deviceUnlock.ts +12 -1
  251. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  252. package/src/types/api/export.ts +1 -18
  253. package/src/types/api/firmwareUpdate.ts +6 -78
  254. package/src/types/api/index.ts +13 -14
  255. package/src/types/api/protocolV2.ts +13 -0
  256. package/src/types/device.ts +15 -3
  257. package/src/types/params.ts +7 -1
  258. package/src/types/settings.ts +61 -22
  259. package/src/utils/deviceFeaturesCompat.ts +5 -6
  260. package/src/utils/deviceFeaturesUtils.ts +14 -3
  261. package/src/utils/deviceInfoUtils.ts +2 -0
  262. package/src/utils/deviceSettings.ts +3 -0
  263. package/src/utils/homescreen.ts +32 -6
  264. package/src/utils/index.ts +6 -1
  265. package/src/utils/pro2Nft.ts +142 -0
  266. package/src/utils/pro2Wallpaper.ts +35 -8
  267. package/src/utils/uiProgressThrottle.ts +63 -0
  268. package/__tests__/device-initialize-timeout.test.ts +0 -56
  269. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  270. package/__tests__/firmware-update/device-update-bootloader.test.ts +0 -87
  271. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  272. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  273. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  274. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  275. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  276. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
  277. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  278. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -231
  279. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  280. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  281. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  282. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  283. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  284. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  285. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  286. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  287. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  288. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  289. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  290. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  291. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
  292. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  293. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  294. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  295. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  296. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  297. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  298. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  299. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  300. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  301. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  302. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  303. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  304. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  305. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  306. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  307. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  308. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  309. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
  310. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
  311. package/src/api/firmware/progressThrottle.ts +0 -44
  312. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  313. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  314. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  315. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  316. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -2,24 +2,24 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
2
2
  import {
3
3
  DeviceRebootType,
4
4
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
5
- PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
6
5
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
7
6
  } from '@onekeyfe/hd-transport';
8
7
  import { sha256 } from '@noble/hashes/sha256';
9
8
 
10
9
  import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
11
10
  import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
11
+ import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
12
12
  import { validateParams } from './helpers/paramsValidator';
13
13
  import {
14
14
  LoggerNames,
15
15
  getDeviceBLEFirmwareVersion,
16
16
  getDeviceBootloaderVersion,
17
17
  getDeviceFirmwareVersion,
18
+ getDeviceType,
18
19
  getFirmwareType,
19
20
  getLogger,
20
21
  } from '../utils';
21
22
  import { getSysResourceBinary } from './firmware/getBinary';
22
- import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
23
23
  import { DataManager } from '../data-manager';
24
24
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
25
25
  import { DevicePool } from '../device/DevicePool';
@@ -28,41 +28,40 @@ import {
28
28
  ProtocolV2FirmwareTargetType,
29
29
  } from '../protocols/protocol-v2';
30
30
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
31
+ import {
32
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
33
+ buildProtocolV2ResourceUpdatePlan,
34
+ isProtocolV2ResourceFileValid,
35
+ readProtocolV2ResourceInventory,
36
+ } from '../protocols/protocol-v2/resources';
31
37
  import {
32
38
  getProtocolV2UnknownErrorText,
33
39
  isProtocolV2DeviceDisconnectedError,
34
40
  } from './protocol-v2/helpers';
35
- import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
36
- import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
37
- import {
38
- assertFirmwareUpdatePreparedPlanBinding,
39
- assertFirmwareUpdatePreparedPlanDeviceIdentity,
40
- } from './firmware/FirmwareUpdatePreparedPlan';
41
41
 
42
- import type {
43
- FirmwareArtifactReference,
44
- FirmwareUpdateV4Params,
45
- FirmwareUpdateV4Target,
46
- } from '../types/api/firmwareUpdate';
42
+ import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
47
43
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
48
44
  import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
45
+ import type { PROTO } from '../constants';
49
46
  import type { TypedResponseMessage } from '../device/DeviceCommands';
50
47
  import type {
51
48
  Features,
52
49
  IFirmwareReleaseInfo,
53
50
  IProtocolV2FirmwareComponent,
51
+ IProtocolV2Resource,
52
+ IProtocolV2ResourceFile,
54
53
  IVersionArray,
55
54
  } from '../types';
56
- import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
57
55
 
58
56
  const Log = getLogger(LoggerNames.Method);
59
57
 
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;
58
+ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
59
+ const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
63
60
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
61
+ const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
64
62
  const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
65
- const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
63
+ const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
64
+ const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
66
65
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
67
66
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
68
67
  const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
@@ -73,14 +72,37 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
73
72
  const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
74
73
  const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
75
74
  const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
76
- const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
75
+ const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
77
76
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
78
- const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
79
77
  const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
80
78
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
81
79
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
82
80
  const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
83
81
 
82
+ const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
83
+
84
+ export function assertProtocolV2FirmwareTargetsSupported(
85
+ deviceType: EDeviceType | string | undefined,
86
+ params: FirmwareUpdateV4Params
87
+ ) {
88
+ if (deviceType !== EDeviceType.Neo) return;
89
+
90
+ const unsupportedTargets = new Set(
91
+ (params.targetsToUpdate ?? []).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
92
+ );
93
+ if (params.se03Binary) unsupportedTargets.add('se03');
94
+ if (params.se04Binary) unsupportedTargets.add('se04');
95
+
96
+ if (unsupportedTargets.size) {
97
+ throw ERRORS.TypedError(
98
+ HardwareErrorCode.RuntimeError,
99
+ `Neo only supports SE01 and SE02; unsupported firmware targets: ${Array.from(
100
+ unsupportedTargets
101
+ ).join(', ')}`
102
+ );
103
+ }
104
+ }
105
+
84
106
  const getProtocolV2DeviceTransferProgress = (
85
107
  bytesBeforeChunk: number,
86
108
  bytesAfterChunk: number,
@@ -98,6 +120,11 @@ const getProtocolV2DeviceTransferProgress = (
98
120
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
99
121
  };
100
122
 
123
+ const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
124
+ const safeElapsedMs = Math.max(elapsedMs, 1);
125
+ return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
126
+ };
127
+
101
128
  type ProtocolV2FirmwareUpdateStatusTarget = {
102
129
  target_id: number | string;
103
130
  status?: number | string;
@@ -111,16 +138,22 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
111
138
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
112
139
  kind: ProtocolV2RemoteComponentTarget['kind'];
113
140
  };
114
- /** RESC bundle okpkg written independently to devicePath through FileWrite. */
141
+ type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
142
+ path: string;
143
+ };
144
+
145
+ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
146
+ filePath: string;
147
+ processedSize?: number;
148
+ totalSize?: number;
149
+ onTransferredBytes?: (transferredBytes: number) => void;
150
+ };
151
+
152
+ /** Protocol V2 resource file written independently to devicePath through FileWrite. */
115
153
  type ProtocolV2ResourceBundleBinary = {
116
154
  name: string;
117
155
  binary: ArrayBuffer;
118
156
  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;
124
157
  };
125
158
 
126
159
  type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
@@ -133,35 +166,6 @@ type ProtocolV2RemoteComponentTarget = {
133
166
  kind: 'bootloader' | 'firmware';
134
167
  };
135
168
 
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
-
165
169
  type ProtocolV2OkppHeader = {
166
170
  type: string;
167
171
  version: IVersionArray;
@@ -264,12 +268,6 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
264
268
  );
265
269
  };
266
270
 
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
-
273
271
  const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
274
272
  normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
275
273
 
@@ -307,18 +305,6 @@ const normalizeProtocolV2TargetStatus = (
307
305
 
308
306
  const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
309
307
 
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
-
322
308
  const bytesToHex = (bytes: Uint8Array) =>
323
309
  Array.from(bytes)
324
310
  .map(byte => byte.toString(16).padStart(2, '0'))
@@ -409,20 +395,30 @@ export const isProtocolV2FirmwareFingerprintValid = (
409
395
 
410
396
  export const assertProtocolV2ReconnectIdentity = (
411
397
  expectedSerialNumber?: string,
412
- actualSerialNumber?: string
398
+ actualSerialNumber?: string,
399
+ expectedPath?: string,
400
+ actualPath?: string
413
401
  ) => {
414
- if (!expectedSerialNumber || !actualSerialNumber) {
415
- throw ERRORS.TypedError(
416
- HardwareErrorCode.DeviceNotFound,
417
- 'Protocol V2 reconnect physical identity is unavailable'
418
- );
402
+ if (expectedSerialNumber && actualSerialNumber) {
403
+ if (actualSerialNumber !== expectedSerialNumber) {
404
+ throw ERRORS.TypedError(
405
+ HardwareErrorCode.DeviceNotFound,
406
+ `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
407
+ );
408
+ }
409
+ return;
419
410
  }
420
- if (actualSerialNumber !== expectedSerialNumber) {
421
- throw ERRORS.TypedError(
422
- HardwareErrorCode.DeviceNotFound,
423
- `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
424
- );
411
+
412
+ if (expectedPath && actualPath && expectedPath === actualPath) {
413
+ return;
425
414
  }
415
+
416
+ throw ERRORS.TypedError(
417
+ HardwareErrorCode.DeviceNotFound,
418
+ `Protocol V2 reconnect physical identity unavailable: expected path ${
419
+ expectedPath ?? 'unknown'
420
+ }, received ${actualPath ?? 'unknown'}`
421
+ );
426
422
  };
427
423
 
428
424
  /**
@@ -436,20 +432,12 @@ export const assertProtocolV2ReconnectIdentity = (
436
432
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
437
433
  private protocolV2ExpectedSerialNumber?: string;
438
434
 
435
+ private protocolV2ExpectedPath?: string;
436
+
439
437
  getSupportedProtocols() {
440
438
  return ['V2'] as const;
441
439
  }
442
440
 
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
-
453
441
  init() {
454
442
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
455
443
  this.requireDeviceMode = [];
@@ -487,62 +475,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
487
475
  { name: 'firmwareType', type: 'string' },
488
476
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
489
477
  { name: 'platform', type: 'string' },
490
- { name: 'expectedDeviceId', type: 'string' },
491
- { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
478
+ { name: 'resourceFiles', type: 'array', allowEmpty: true },
492
479
  ]);
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
- }
543
480
 
544
481
  this.params = {
545
- preparedPlan: payload.preparedPlan,
546
482
  chunkSize: payload.chunkSize,
547
483
  forcedUpdateRes: payload.forcedUpdateRes,
548
484
  bootloaderBinary: payload.bootloaderBinary,
@@ -554,28 +490,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
554
490
  se02Binary: payload.se02Binary,
555
491
  se03Binary: payload.se03Binary,
556
492
  se04Binary: payload.se04Binary,
557
- resourceBundleFiles: payload.resourceBundleFiles,
493
+ resourceFiles: payload.resourceFiles,
558
494
  firmwareType: payload.firmwareType,
559
495
  targetsToUpdate: payload.targetsToUpdate,
560
- expectedTargetVersions: payload.expectedTargetVersions,
561
496
  platform: payload.platform,
562
- expectedDeviceId: payload.expectedDeviceId,
563
- artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
564
- componentArtifacts: payload.componentArtifacts,
565
- resourceBundleArtifacts: payload.resourceBundleArtifacts,
566
497
  };
567
498
  }
568
499
 
569
- private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
500
+ private getProtocolV2FirmwareChunkSize() {
570
501
  const payloadChunkSize = Number(this.params?.chunkSize);
571
502
  const env = DataManager.getSettings('env');
572
503
  const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
573
504
  let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
574
505
  if (isBle) {
575
- maxChunkSize =
576
- direction === 'read'
577
- ? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
578
- : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
506
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
579
507
  }
580
508
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
581
509
  return maxChunkSize;
@@ -594,16 +522,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
594
522
  private async runProtocolV2() {
595
523
  await this.captureProtocolV2PhysicalIdentity();
596
524
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
525
+ const currentDeviceType = this.device.getCurrentDeviceType();
526
+ const capabilityDeviceType =
527
+ currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
528
+ ? currentDeviceType
529
+ : getDeviceType(deviceFeatures);
530
+ assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
597
531
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
598
532
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
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
- }
533
+ const needsRemoteResources =
534
+ !this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
607
535
 
608
536
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
609
537
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -611,21 +539,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
611
539
  let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
612
540
  try {
613
541
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
542
+ resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
614
543
  fwBinaryMap = this.collectExplicitTargetBinaries();
615
544
  bootloaderBinary = this.prepareBootloaderBinary();
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
- }
545
+ const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
546
+ const needsRemoteBootResources =
547
+ !!this.params.targetsToUpdate?.includes('resource') ||
548
+ !!this.params.targetsToUpdate?.includes('boot_resources');
549
+ if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
550
+ // Remote updates must use a freshly fetched config before any reboot or file write.
551
+ await DataManager.forceReloadData({
552
+ requireResources: needsRemoteResources || needsRemoteBootResources,
553
+ });
554
+ }
555
+ if (needsRemoteFirmware) {
629
556
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
630
557
  firmwareType,
631
558
  deviceFeatures
@@ -634,308 +561,70 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
634
561
  fwBinaryMap = remoteBinaries.fwBinaryMap;
635
562
  installItems = remoteBinaries.installItems;
636
563
  }
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);
564
+ const bootResourceFiles = await this.prepareProtocolV2BootResources();
565
+ if (bootResourceFiles?.length) {
566
+ resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
567
+ }
568
+ if (!needsRemoteResources) {
569
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
570
+ }
645
571
  } catch (err) {
646
- throw normalizeFirmwarePreparationError(err);
572
+ if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
573
+ throw err;
574
+ }
575
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
647
576
  }
648
577
 
649
- if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
578
+ if (
579
+ !bootloaderBinary &&
580
+ fwBinaryMap.length === 0 &&
581
+ !installItems?.length &&
582
+ !resourceBundles?.length &&
583
+ !needsRemoteResources
584
+ ) {
650
585
  throw ERRORS.TypedError(
651
586
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
652
587
  'No firmware to update'
653
588
  );
654
589
  }
655
590
 
656
- return this.executeProtocolV2Update({
657
- fwBinaryMap,
658
- bootloaderBinary,
659
- ...(installItems ? { installItems } : undefined),
660
- ...(resourceBundles?.length ? { resourceBundles } : undefined),
661
- });
662
- }
663
-
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
- }
591
+ const enteredBootloader = await this.enterProtocolV2BootloaderMode();
681
592
 
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
- }
693
-
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,
593
+ if (needsRemoteResources) {
594
+ try {
595
+ const stableResources = await this.prepareProtocolV2ResourceBundles();
596
+ resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
597
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
598
+ } catch (err) {
599
+ if (enteredBootloader) {
600
+ try {
601
+ await this.exitProtocolV2BootloaderToNormal();
602
+ } catch (restoreError) {
603
+ Log.warn(
604
+ '[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
605
+ restoreError
606
+ );
777
607
  }
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
- );
608
+ }
609
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
813
610
  }
814
- this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
815
-
816
- return await this.executeProtocolV2SourceUpdate({
817
- installSources,
818
- resourceSources,
819
- });
820
- } finally {
821
- await this.closeProtocolV2PreparedSources();
822
611
  }
823
- }
824
612
 
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
- }
613
+ await this.executeProtocolV2Update({
614
+ fwBinaryMap,
615
+ bootloaderBinary,
616
+ ...(installItems ? { installItems } : undefined),
617
+ ...(resourceBundles?.length ? { resourceBundles } : undefined),
618
+ });
850
619
 
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
- }
620
+ this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
621
+ await this.exitProtocolV2BootloaderToNormal();
864
622
 
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
- }
623
+ const versions = await this.waitForProtocolV2FinalFeatures();
624
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
625
+ DevicePool.resetState();
918
626
 
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);
627
+ return versions;
939
628
  }
940
629
 
941
630
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
@@ -957,31 +646,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
957
646
  private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
958
647
  assertProtocolV2ReconnectIdentity(
959
648
  this.protocolV2ExpectedSerialNumber,
960
- this.getProtocolV2SerialNumber(deviceInfo)
649
+ this.getProtocolV2SerialNumber(deviceInfo),
650
+ this.protocolV2ExpectedPath,
651
+ this.device.originalDescriptor.path
961
652
  );
962
653
  }
963
654
 
655
+ private getProtocolV2DeviceInfoTimeout() {
656
+ return this.isBleReconnect()
657
+ ? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
658
+ : PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
659
+ }
660
+
964
661
  private async requestProtocolV2PhysicalIdentity() {
965
662
  return requestProtocolV2DeviceInfo({
966
663
  commands: this.device.getCommands(),
967
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
664
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
968
665
  });
969
666
  }
970
667
 
971
668
  private async captureProtocolV2PhysicalIdentity() {
972
669
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
973
670
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
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
- }
671
+ const path = this.device.originalDescriptor.path?.trim() || undefined;
672
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
984
673
  this.protocolV2ExpectedSerialNumber = serialNumber;
674
+ this.protocolV2ExpectedPath = path;
985
675
  }
986
676
 
987
677
  private async verifyProtocolV2ReconnectIdentity() {
@@ -996,7 +686,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
996
686
 
997
687
  private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
998
688
  return (
999
- !!this.params.resourceBundleFiles?.length ||
689
+ !!this.params.resourceFiles?.length ||
1000
690
  !!this.params.bootloaderBinary ||
1001
691
  fwBinaryMap.length > 0
1002
692
  );
@@ -1059,57 +749,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1059
749
  return target;
1060
750
  }
1061
751
 
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
-
1113
752
  private async downloadRemoteProtocolV2Component(
1114
753
  key: string,
1115
754
  component: IProtocolV2FirmwareComponent
@@ -1198,155 +837,164 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1198
837
  };
1199
838
  }
1200
839
 
1201
- // ============================================================
1202
- // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
1203
- // ============================================================
840
+ private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
841
+ const deviceType = this.device.getCurrentDeviceType();
842
+ if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
843
+ throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
844
+ }
1204
845
 
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
- };
846
+ private async prepareProtocolV2BootResources(): Promise<
847
+ ProtocolV2ResourceBundleBinary[] | undefined
848
+ > {
849
+ if (
850
+ !this.params.targetsToUpdate?.includes('resource') &&
851
+ !this.params.targetsToUpdate?.includes('boot_resources')
852
+ ) {
853
+ return undefined;
854
+ }
855
+ const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
856
+ if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
857
+
858
+ const files: ProtocolV2ResourceBundleBinary[] = [];
859
+ for (const file of resource.files) {
860
+ Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
861
+ const { binary } = await getSysResourceBinary(file.url);
862
+ if (!isProtocolV2ResourceFileValid(binary, file)) {
863
+ throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
864
+ }
865
+ files.push({
866
+ name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
867
+ binary,
868
+ devicePath: file.devicePath,
1229
869
  });
1230
870
  }
871
+ return files;
872
+ }
1231
873
 
874
+ private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
875
+ const files = this.params.resourceFiles ?? [];
876
+ if (!files?.length) return undefined;
877
+
878
+ const prepared = files.map((file, index) => {
879
+ const devicePath = validateProtocolV2FilesystemPath(
880
+ file.devicePath,
881
+ `resourceFiles[${index}].devicePath`
882
+ );
883
+ const descriptor = file as typeof file &
884
+ Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
885
+ if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
886
+ throw new Error(`resourceFiles[${index}] size mismatch`);
887
+ }
888
+ if (
889
+ descriptor.fileHash &&
890
+ !isProtocolV2ResourceFileValid(file.binary, {
891
+ size: file.binary.byteLength,
892
+ fileHash: descriptor.fileHash,
893
+ })
894
+ ) {
895
+ throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
896
+ }
897
+ return {
898
+ name: devicePath.split('/').pop() ?? devicePath,
899
+ binary: file.binary,
900
+ devicePath,
901
+ };
902
+ });
903
+ if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
904
+ throw new Error('resourceFiles contain duplicate devicePath values');
905
+ }
906
+ return prepared;
907
+ }
908
+
909
+ private async prepareProtocolV2ResourceBundles(): Promise<
910
+ ProtocolV2ResourceBundleBinary[] | undefined
911
+ > {
1232
912
  if (!this.params.targetsToUpdate?.includes('resource')) {
1233
913
  return undefined;
1234
914
  }
1235
915
 
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
- }
916
+ const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
917
+ if (!resources?.length) {
918
+ throw new Error('Missing Pro2 stable resource configuration');
919
+ }
920
+
921
+ const inventory = this.params.forcedUpdateRes
922
+ ? undefined
923
+ : await readProtocolV2ResourceInventory({
924
+ commands: this.device.getCommands(),
925
+ resources,
926
+ chunkSize: this.getProtocolV2FirmwareChunkSize(),
927
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
928
+ });
929
+ const plan = buildProtocolV2ResourceUpdatePlan({
930
+ resources,
931
+ inventory,
932
+ mode: 'bootloader-recovery',
933
+ forced: this.params.forcedUpdateRes,
934
+ });
935
+ Log.log(
936
+ `[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
937
+ );
938
+
939
+ const bundles: ProtocolV2ResourceBundleBinary[] = [];
940
+ for (const resource of plan.resources) {
941
+ bundles.push(await this.downloadProtocolV2Resource(resource));
1308
942
  }
1309
943
  return bundles;
1310
944
  }
1311
945
 
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;
946
+ private async downloadProtocolV2Resource(
947
+ resource: IProtocolV2Resource
948
+ ): Promise<ProtocolV2ResourceBundleBinary> {
949
+ Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
950
+ const { binary } = await getSysResourceBinary(resource.url);
951
+ if (!isProtocolV2ResourceFileValid(binary, resource)) {
952
+ throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
953
+ }
954
+ return {
955
+ name: `${resource.type}.okpkg`,
956
+ binary,
957
+ devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
958
+ };
959
+ }
1323
960
 
1324
- try {
1325
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
1326
- if (!header) return false;
961
+ private async syncProtocolV2ResourceBundles(
962
+ bundles: ProtocolV2ResourceBundleBinary[],
963
+ firmwareSize: number
964
+ ): Promise<{ processedSize: number; totalSize: number }> {
965
+ const transferStartTime = Date.now();
966
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1327
967
 
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;
968
+ // Write directly to the device through FileWrite.
969
+ let totalSize = 0;
970
+ for (const b of bundles) totalSize += b.binary.byteLength;
971
+
972
+ const transferTotalSize = totalSize + firmwareSize;
973
+ let processedSize = 0;
974
+ for (const bundle of bundles) {
975
+ Log.log(
976
+ `[FirmwareUpdateV4] syncing resource ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
977
+ );
978
+ processedSize = await this.protocolV2CommonUpdateProcess({
979
+ payload: bundle.binary,
980
+ filePath: bundle.devicePath,
981
+ processedSize,
982
+ totalSize: transferTotalSize,
983
+ });
1344
984
  }
985
+
986
+ const elapsedMs = Date.now() - transferStartTime;
987
+ Log.log(
988
+ `[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
989
+ elapsedMs / 1000
990
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
991
+ );
992
+ return { processedSize, totalSize: transferTotalSize };
1345
993
  }
1346
994
 
1347
995
  private isProtocolV2BootloaderMode() {
1348
996
  if (typeof this.device.isBootloader === 'function') {
1349
- return this.device.isBootloader();
997
+ return !!this.device.isBootloader();
1350
998
  }
1351
999
  return (
1352
1000
  this.device.features?.mode === 'bootloader' ||
@@ -1355,7 +1003,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1355
1003
  }
1356
1004
 
1357
1005
  private isProtocolV2RomloaderMode() {
1358
- if (!this.device.isProtocolV2() || this.device.getCurrentDeviceType() !== EDeviceType.Pro2) {
1006
+ const deviceType = this.device.getCurrentDeviceType();
1007
+ if (
1008
+ !this.device.isProtocolV2() ||
1009
+ (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
1010
+ ) {
1359
1011
  return false;
1360
1012
  }
1361
1013
  if (typeof this.device.isRomloader === 'function') {
@@ -1369,22 +1021,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1369
1021
  // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
1370
1022
  if (this.isProtocolV2RomloaderMode()) {
1371
1023
  Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
1372
- this.protocolV2ExecutionInLoader = true;
1373
1024
  return false;
1374
1025
  }
1375
1026
  if (this.isProtocolV2BootloaderMode()) {
1376
1027
  Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
1377
- this.protocolV2ExecutionInLoader = true;
1028
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1378
1029
  return false;
1379
1030
  }
1380
1031
 
1381
1032
  try {
1382
1033
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
1383
1034
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
1384
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1385
1035
  await wait(1000);
1386
1036
  await this.waitForProtocolV2BootloaderMode();
1387
- this.protocolV2ExecutionInLoader = true;
1037
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
1388
1038
  return true;
1389
1039
  } catch (error) {
1390
1040
  if (error instanceof HardwareError) {
@@ -1401,13 +1051,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1401
1051
  ) {
1402
1052
  const startTime = Date.now();
1403
1053
  let lastError: unknown;
1054
+ let shouldReconnect = true;
1404
1055
 
1405
1056
  while (Date.now() - startTime < timeout) {
1406
1057
  try {
1407
- await this.reconnectProtocolV2Device();
1058
+ if (shouldReconnect) {
1059
+ await this.reconnectProtocolV2Device();
1060
+ shouldReconnect = false;
1061
+ }
1408
1062
  const deviceInfo = await requestProtocolV2DeviceInfo({
1409
1063
  commands: this.device.getCommands(),
1410
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1064
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
1411
1065
  });
1412
1066
  this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1413
1067
  const features = await this.device.probeProtocolV2RuntimeState(
@@ -1422,6 +1076,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1422
1076
  if (this.isProtocolV2ReconnectIdentityError(error)) {
1423
1077
  throw error;
1424
1078
  }
1079
+ shouldReconnect = true;
1425
1080
  lastError = error;
1426
1081
  Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
1427
1082
  }
@@ -1474,98 +1129,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1474
1129
  return entries;
1475
1130
  }
1476
1131
 
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
-
1569
1132
  private async executeProtocolV2Update({
1570
1133
  fwBinaryMap,
1571
1134
  bootloaderBinary,
@@ -1577,158 +1140,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1577
1140
  installItems?: ProtocolV2InstallItem[];
1578
1141
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1579
1142
  }) {
1580
- const memoryInstallItems =
1143
+ const orderedInstallItems =
1581
1144
  installItems ??
1582
1145
  this.buildProtocolV2InstallItems({
1583
- fwBinaryMap: fwBinaryMap ?? [],
1584
1146
  bootloaderBinary: bootloaderBinary ?? null,
1147
+ fwBinaryMap: fwBinaryMap ?? [],
1585
1148
  });
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
- }))
1594
- );
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();
1611
- }
1612
- }
1613
1149
 
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;
1626
- }
1627
- }
1150
+ let firmwareSize = 0;
1151
+ for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1628
1152
 
1629
1153
  this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1154
+
1630
1155
  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);
1156
+ let totalSize = firmwareSize;
1157
+ if (resourceBundles?.length) {
1158
+ const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1159
+ resourceBundles,
1160
+ firmwareSize
1161
+ );
1162
+ processedSize = resourceTransfer.processedSize;
1163
+ totalSize = resourceTransfer.totalSize;
1639
1164
  }
1640
1165
 
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
- });
1166
+ // Skip staging and installation when the update contains resource files only.
1167
+ if (orderedInstallItems.length === 0) {
1168
+ Log.log('[FirmwareUpdateV4] no firmware targets to install (resource files only)');
1169
+ if (totalSize > 0) {
1170
+ this.postProgressMessage(100, 'transferData');
1171
+ }
1172
+ return;
1655
1173
  }
1174
+ let transferredSize = processedSize;
1175
+ const transferStartTime = Date.now();
1176
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1177
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1178
+ const onTransferredBytes = (bytes: number) => {
1179
+ transferredSize = bytes;
1180
+ };
1181
+ Log.log(
1182
+ `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1183
+ );
1656
1184
 
1657
- if (totalSize > 0) {
1658
- this.postProgressMessage(100, 'transferData');
1659
- }
1660
- if (stagedInstallTargets.length === 0) {
1661
- return;
1185
+ const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1186
+
1187
+ try {
1188
+ for (const item of orderedInstallItems) {
1189
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1190
+ Log.log(
1191
+ `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1192
+ );
1193
+ processedSize = await this.protocolV2CommonUpdateProcess({
1194
+ payload: item.binary,
1195
+ filePath,
1196
+ processedSize,
1197
+ totalSize,
1198
+ onTransferredBytes,
1199
+ });
1200
+ transferredSize = processedSize;
1201
+ await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1202
+
1203
+ stagedInstallTargets.push({
1204
+ ...item,
1205
+ path: filePath,
1206
+ });
1207
+ }
1208
+
1209
+ if (totalSize > 0) {
1210
+ this.postProgressMessage(100, 'transferData');
1211
+ }
1212
+
1213
+ const elapsedMs = Date.now() - transferStartTime;
1214
+ Log.log(
1215
+ `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1216
+ elapsedMs / 1000
1217
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1218
+ );
1219
+ } catch (error) {
1220
+ const elapsedMs = Date.now() - transferStartTime;
1221
+ Log.warn(
1222
+ `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1223
+ elapsedMs / 1000
1224
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1225
+ );
1226
+ throw error;
1662
1227
  }
1663
1228
 
1664
1229
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1665
- const targets = stagedInstallTargets.map(item => ({
1230
+
1231
+ const allTargets = stagedInstallTargets.map(item => ({
1666
1232
  target_id: item.targetId,
1667
1233
  path: item.path,
1668
1234
  }));
1669
- await this.protocolV2StartFirmwareUpdate({ targets });
1670
- await this.waitForProtocolV2FirmwareUpdateComplete(targets);
1235
+ Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1236
+ await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1237
+ await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1671
1238
  }
1672
1239
 
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
- );
1729
- }
1730
-
1731
- private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1240
+ private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1732
1241
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1733
1242
  }
1734
1243
 
@@ -1747,8 +1256,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1747
1256
 
1748
1257
  private assertProtocolV2TargetStatus(
1749
1258
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1750
- expectedTargetIds: Set<number>,
1751
- expectedPaths = new Map<number, string>()
1259
+ expectedTargetIds: Set<number>
1752
1260
  ) {
1753
1261
  Log.log(
1754
1262
  `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
@@ -1779,57 +1287,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1779
1287
  const matchingTargets = statusTargets.filter(target =>
1780
1288
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1781
1289
  );
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
- );
1803
1290
  const completedTargetIds = new Set<number>();
1804
- completedTargets.forEach(target => {
1291
+ matchingTargets.forEach(target => {
1805
1292
  const targetId = normalizeProtocolV2TargetId(target.target_id);
1806
- if (targetId !== undefined) {
1293
+ if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
1807
1294
  completedTargetIds.add(targetId);
1808
1295
  }
1809
1296
  });
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
- }
1297
+ const allExpectedTargetsCompleted =
1298
+ expectedTargetIds.size > 0 &&
1299
+ Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
1300
+ if (allExpectedTargetsCompleted) {
1833
1301
  this.postProgressMessage(100, 'installingFirmware');
1834
1302
  return true;
1835
1303
  }
@@ -1850,19 +1318,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1850
1318
  return false;
1851
1319
  }
1852
1320
 
1321
+ private getProtocolV2MissingTargetIds(
1322
+ statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1323
+ expectedTargetIds: Set<number>
1324
+ ) {
1325
+ const reportedTargetIds = new Set<number>();
1326
+ statusTargets.forEach(target => {
1327
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
1328
+ if (targetId !== undefined) {
1329
+ reportedTargetIds.add(targetId);
1330
+ }
1331
+ });
1332
+ return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
1333
+ }
1334
+
1853
1335
  private async waitForProtocolV2FirmwareUpdateComplete(
1854
1336
  targets: Array<{ target_id: number; path: string }>
1855
1337
  ) {
1856
- this.protocolV2FinalStatusVerified = false;
1857
1338
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1858
- const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1859
1339
  const startTime = Date.now();
1860
1340
  let lastError: unknown;
1341
+ let shouldReconnect = true;
1342
+ let deviceInfo: ProtocolV2DeviceInfo | undefined;
1343
+ let missingTargetStatusSince: number | undefined;
1344
+ let missingTargetStatusKey: string | undefined;
1345
+ const resetMissingTargetStatusGrace = () => {
1346
+ missingTargetStatusSince = undefined;
1347
+ missingTargetStatusKey = undefined;
1348
+ };
1861
1349
 
1862
1350
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
1863
1351
  try {
1864
- await this.reconnectProtocolV2Device();
1865
- const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1352
+ if (shouldReconnect) {
1353
+ await this.reconnectProtocolV2Device();
1354
+ deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1355
+ shouldReconnect = false;
1356
+ }
1357
+ const currentDeviceInfo = deviceInfo;
1866
1358
  try {
1867
1359
  const statusResponse = await this.device.getCommands().typedCall(
1868
1360
  'DeviceFirmwareUpdateStatusGet',
@@ -1874,36 +1366,89 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1874
1366
  path: true,
1875
1367
  },
1876
1368
  },
1877
- { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1369
+ { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
1878
1370
  );
1371
+ const statusTargets = (statusResponse.message.records ??
1372
+ []) as ProtocolV2FirmwareUpdateStatusTarget[];
1373
+ if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
1374
+ return;
1375
+ }
1376
+
1879
1377
  if (
1880
- this.assertProtocolV2TargetStatus(
1881
- (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1882
- expectedTargetIds,
1883
- expectedPaths
1884
- )
1378
+ statusTargets.length === 0 &&
1379
+ currentDeviceInfo &&
1380
+ (await this.probeProtocolV2NormalMode(currentDeviceInfo))
1885
1381
  ) {
1886
- this.protocolV2FinalStatusVerified = true;
1382
+ Log.log(
1383
+ '[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
1384
+ );
1385
+ this.postProgressMessage(100, 'installingFirmware');
1887
1386
  return;
1888
1387
  }
1889
- lastError = new Error('Protocol V2 firmware targets are still installing');
1388
+
1389
+ const missingTargetIds = this.getProtocolV2MissingTargetIds(
1390
+ statusTargets,
1391
+ expectedTargetIds
1392
+ );
1393
+ if (missingTargetIds.length > 0) {
1394
+ const now = Date.now();
1395
+ const missingKey = missingTargetIds.join(',');
1396
+ if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
1397
+ missingTargetStatusSince = now;
1398
+ missingTargetStatusKey = missingKey;
1399
+ } else if (
1400
+ now - missingTargetStatusSince >=
1401
+ PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
1402
+ ) {
1403
+ const reportedTargetIds = statusTargets
1404
+ .map(target => normalizeProtocolV2TargetId(target.target_id))
1405
+ .filter((targetId): targetId is number => targetId !== undefined);
1406
+ throw ERRORS.TypedError(
1407
+ HardwareErrorCode.FirmwareError,
1408
+ `Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
1409
+ ','
1410
+ )}`
1411
+ );
1412
+ }
1413
+ lastError = new Error(
1414
+ `Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
1415
+ );
1416
+ } else {
1417
+ resetMissingTargetStatusGrace();
1418
+ lastError = new Error('Protocol V2 firmware targets are still installing');
1419
+ }
1890
1420
  } catch (error) {
1891
- if (isProtocolV2TerminalInstallStatusError(error)) {
1421
+ if (
1422
+ error instanceof HardwareError &&
1423
+ error.errorCode === HardwareErrorCode.FirmwareError
1424
+ ) {
1892
1425
  throw error;
1893
1426
  }
1427
+ // Only consecutive full status dumps can prove that a record is absent.
1428
+ // A reboot or transport interruption starts a fresh grace window.
1429
+ resetMissingTargetStatusGrace();
1894
1430
  // App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
1895
1431
  // missing endpoint as completion only after the runtime probe confirms App mode.
1896
1432
  if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
1897
- if (await this.probeProtocolV2NormalMode(deviceInfo)) {
1433
+ if (!currentDeviceInfo) {
1434
+ throw ERRORS.TypedError(
1435
+ HardwareErrorCode.RuntimeError,
1436
+ 'Protocol V2 device identity is unavailable during install polling'
1437
+ );
1438
+ }
1439
+ if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
1898
1440
  Log.log(
1899
1441
  '[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
1900
1442
  );
1443
+ this.postProgressMessage(100, 'installingFirmware');
1901
1444
  return;
1902
1445
  }
1903
1446
  lastError = new Error(
1904
1447
  'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
1905
1448
  );
1906
1449
  } else {
1450
+ shouldReconnect = true;
1451
+ deviceInfo = undefined;
1907
1452
  lastError = error;
1908
1453
  Log.log(
1909
1454
  '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
@@ -1916,15 +1461,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1916
1461
  if (this.isProtocolV2ReconnectIdentityError(error)) {
1917
1462
  throw error;
1918
1463
  }
1919
- if (isProtocolV2TerminalInstallStatusError(error)) {
1920
- throw error;
1921
- }
1922
- if (
1923
- error instanceof HardwareError &&
1924
- error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
1925
- ) {
1464
+ if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1926
1465
  throw error;
1927
1466
  }
1467
+ shouldReconnect = true;
1468
+ deviceInfo = undefined;
1469
+ resetMissingTargetStatusGrace();
1928
1470
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1929
1471
  }
1930
1472
  await wait(1000);
@@ -1932,17 +1474,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1932
1474
 
1933
1475
  throw ERRORS.TypedError(
1934
1476
  HardwareErrorCode.RuntimeError,
1935
- `Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
1477
+ `Protocol V2 firmware update status not complete within ${
1478
+ PROTOCOL_V2_INSTALL_TIMEOUT / 1000
1479
+ }s: ${this.normalizeErrorMessage(lastError)}`
1936
1480
  );
1937
1481
  }
1938
1482
 
1939
1483
  private async exitProtocolV2BootloaderToNormal() {
1940
1484
  await this.reconnectProtocolV2Device();
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.
1485
+ const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1486
+ try {
1487
+ if (await this.probeProtocolV2NormalMode(deviceInfo)) {
1488
+ Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
1489
+ return;
1490
+ }
1491
+ } catch (error) {
1492
+ Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
1493
+ }
1944
1494
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1945
- this.protocolV2ExecutionInLoader = false;
1946
1495
  }
1947
1496
 
1948
1497
  private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
@@ -1958,10 +1507,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1958
1507
  PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1959
1508
  );
1960
1509
 
1510
+ return this.getProtocolV2VersionResult(features);
1511
+ }
1512
+
1513
+ private getProtocolV2VersionResult(features: Features) {
1961
1514
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1962
1515
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1963
1516
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
1964
- this.protocolV2LatestFinalFeatures = features;
1965
1517
  if (firmwareVersion === '0.0.0') {
1966
1518
  Log.warn(
1967
1519
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1975,27 +1527,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1975
1527
  };
1976
1528
  }
1977
1529
 
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
-
1989
1530
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1990
1531
  const startTime = Date.now();
1991
1532
  let lastError: unknown;
1533
+ let shouldReconnect = true;
1992
1534
 
1993
1535
  while (Date.now() - startTime < timeout) {
1994
1536
  try {
1995
- await this.reconnectProtocolV2Device();
1537
+ if (shouldReconnect) {
1538
+ await this.reconnectProtocolV2Device();
1539
+ shouldReconnect = false;
1540
+ }
1996
1541
  const deviceInfo = await requestProtocolV2DeviceInfo({
1997
1542
  commands: this.device.getCommands(),
1998
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1543
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
1999
1544
  // Completion needs target versions only; keep scope aligned with the request.
2000
1545
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
2001
1546
  });
@@ -2004,21 +1549,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2004
1549
  deviceInfo,
2005
1550
  PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2006
1551
  );
2007
- if (features.mode !== 'normal' || features.bootloaderMode) {
2008
- throw ERRORS.TypedError(
2009
- HardwareErrorCode.DeviceNotFound,
2010
- 'Protocol V2 device is still in bootloader mode'
2011
- );
1552
+ if (features.mode === 'normal' && !features.bootloaderMode) {
1553
+ return features;
2012
1554
  }
2013
- return features;
1555
+ lastError = ERRORS.TypedError(
1556
+ HardwareErrorCode.DeviceNotFound,
1557
+ 'Protocol V2 device is still in bootloader mode'
1558
+ );
2014
1559
  } catch (error) {
2015
1560
  if (this.isProtocolV2ReconnectIdentityError(error)) {
2016
1561
  throw error;
2017
1562
  }
1563
+ shouldReconnect = true;
2018
1564
  lastError = error;
2019
1565
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
2020
- await wait(1000);
2021
1566
  }
1567
+ await wait(1000);
2022
1568
  }
2023
1569
 
2024
1570
  throw ERRORS.TypedError(
@@ -2042,9 +1588,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2042
1588
  DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
2043
1589
  devicesDescriptor.length === 1
2044
1590
  ) {
1591
+ const descriptor = devicesDescriptor[0];
1592
+ if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
1593
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1594
+ }
2045
1595
  this.device.updateDescriptor(
2046
1596
  {
2047
- ...devicesDescriptor[0],
1597
+ ...descriptor,
2048
1598
  protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
2049
1599
  },
2050
1600
  true
@@ -2055,20 +1605,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2055
1605
  return;
2056
1606
  }
2057
1607
 
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.
1608
+ // Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
1609
+ // Enumeration order is not stable and must never decide the reconnect target.
2060
1610
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
2061
1611
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
2062
1612
  });
2063
- if (deviceList.length !== 1) {
1613
+ const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
1614
+ const expectedPath = this.protocolV2ExpectedPath?.trim();
1615
+ const reconnectDevice = deviceList.find(candidate => {
1616
+ const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
1617
+ if (expectedSerialNumber && candidateSerialNumber) {
1618
+ return candidateSerialNumber === expectedSerialNumber;
1619
+ }
1620
+ return !!expectedPath && candidate.getConnectId() === expectedPath;
1621
+ });
1622
+ if (!reconnectDevice) {
2064
1623
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
2065
1624
  }
2066
1625
 
2067
1626
  Log.debug(
2068
- 'Protocol V2 firmware reconnect using single enumerated device:',
2069
- deviceList[0].getConnectId()
1627
+ 'Protocol V2 firmware reconnect using matched device:',
1628
+ reconnectDevice.getConnectId()
2070
1629
  );
2071
- this.device.updateFromCache(deviceList[0]);
1630
+ this.device.updateFromCache(reconnectDevice);
2072
1631
  await this.ensureProtocolV2DeviceAcquired();
2073
1632
  this.device.commands.disposed = false;
2074
1633
  this.device.getCommands().mainId = this.device.mainId ?? '';
@@ -2089,38 +1648,106 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2089
1648
  }
2090
1649
 
2091
1650
  private isProtocolV2ReconnectIdentityError(error: unknown) {
2092
- return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
1651
+ // 序列号在 Loader 刚启动时可能暂时不可用,BLE descriptor 也没有稳定的
1652
+ // path。此时应继续轮询,只有明确读到不同序列号时才立即终止更新。
1653
+ return this.normalizeErrorMessage(error).includes(
1654
+ 'Protocol V2 reconnect physical identity mismatch'
1655
+ );
2093
1656
  }
2094
1657
 
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: {
2106
- path: filePath,
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);
1658
+ private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1659
+ let lastError: unknown;
1660
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1661
+ try {
1662
+ return await this.protocolV2WriteWholeFile(params);
1663
+ } catch (error) {
1664
+ lastError = error;
1665
+ Log.error(
1666
+ `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1667
+ error
1668
+ );
1669
+ if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1670
+ break;
2119
1671
  }
1672
+ await this.recoverProtocolV2FileTransfer();
2120
1673
  }
2121
- throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
2122
1674
  }
2123
- return writeRes;
1675
+
1676
+ throw ERRORS.TypedError(
1677
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1678
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1679
+ );
1680
+ }
1681
+
1682
+ private async protocolV2WriteWholeFile({
1683
+ payload,
1684
+ filePath,
1685
+ processedSize,
1686
+ totalSize,
1687
+ onTransferredBytes,
1688
+ }: ProtocolV2FileTransferParams) {
1689
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1690
+ const getUploadProgress = (fileOffset: number) => {
1691
+ if (totalSize !== undefined && processedSize !== undefined) {
1692
+ return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1693
+ }
1694
+ return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1695
+ };
1696
+
1697
+ try {
1698
+ await writeProtocolV2File({
1699
+ commands: this.device.getCommands(),
1700
+ path: filePath,
1701
+ data: payload,
1702
+ totalSize: payload.byteLength,
1703
+ chunkSize,
1704
+ overwrite: true,
1705
+ append: false,
1706
+ writeWithResponse: false,
1707
+ maxChunkRetries: 0,
1708
+ getUiPercentage: ({ offset, chunkLength }) =>
1709
+ getProtocolV2DeviceTransferProgress(
1710
+ (processedSize ?? 0) + offset,
1711
+ (processedSize ?? 0) + offset + chunkLength,
1712
+ totalSize ?? payload.byteLength
1713
+ ),
1714
+ onProgress: progress => {
1715
+ const transferredBytes = (processedSize ?? 0) + progress.transferredBytes;
1716
+ onTransferredBytes?.(transferredBytes);
1717
+ this.postProgressMessage(getUploadProgress(progress.transferredBytes), 'transferData', {
1718
+ transferredBytes,
1719
+ totalBytes: totalSize ?? payload.byteLength,
1720
+ rateBytesPerSecond: progress.rateBytesPerSecond,
1721
+ elapsedMs: progress.elapsedMs,
1722
+ });
1723
+ },
1724
+ });
1725
+ } catch (error) {
1726
+ if (
1727
+ error instanceof HardwareError &&
1728
+ error.errorCode === HardwareErrorCode.RuntimeError &&
1729
+ error.message.includes('FilesystemFileWrite')
1730
+ ) {
1731
+ throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, error.message);
1732
+ }
1733
+ throw error;
1734
+ }
1735
+
1736
+ return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
1737
+ }
1738
+
1739
+ private getProtocolV2FirmwareTransferTransport() {
1740
+ const env = DataManager.getSettings('env');
1741
+ if (env && DataManager.isBleConnect(env)) {
1742
+ return 'BLE';
1743
+ }
1744
+ if (
1745
+ env &&
1746
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1747
+ ) {
1748
+ return 'WebUSB';
1749
+ }
1750
+ return env ?? 'unknown';
2124
1751
  }
2125
1752
 
2126
1753
  private async recoverProtocolV2FileTransfer() {