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

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