@onekeyfe/hd-core 1.2.0-alpha.10 → 1.2.0-alpha.101

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 (819) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +390 -0
  2. package/__tests__/DeviceCommands.test.ts +563 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +49 -0
  4. package/__tests__/chain-params-onekey-compatibility.test.ts +229 -0
  5. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +830 -0
  6. package/__tests__/check-firmware-release-protocol-v2.test.ts +270 -0
  7. package/__tests__/connectSettings.test.ts +45 -5
  8. package/__tests__/core-dispose.test.ts +34 -0
  9. package/__tests__/core-error-output.test.ts +39 -0
  10. package/__tests__/core-initialization.test.ts +23 -0
  11. package/__tests__/device-connector-protocol.test.ts +81 -0
  12. package/__tests__/device-features-state.test.ts +78 -0
  13. package/__tests__/device-identity.test.ts +52 -0
  14. package/__tests__/device-initialize-timeout.test.ts +56 -0
  15. package/__tests__/device-lifecycle-events.test.ts +351 -0
  16. package/__tests__/device-pool-state.test.ts +166 -0
  17. package/__tests__/device-settings.test.ts +437 -0
  18. package/__tests__/device-state-contract.test.ts +28 -0
  19. package/__tests__/device-state-events.test.ts +286 -0
  20. package/__tests__/device-state-mapper.test.ts +288 -0
  21. package/__tests__/device-state-projector.test.ts +114 -0
  22. package/__tests__/device-state-store.test.ts +109 -0
  23. package/__tests__/device-utils.test.ts +154 -0
  24. package/__tests__/device-wallet-session-store.test.ts +348 -1
  25. package/__tests__/deviceFeaturesUtils.test.ts +39 -0
  26. package/__tests__/devicePoolBootloaderPath.test.ts +64 -0
  27. package/__tests__/deviceSettings.test.ts +113 -0
  28. package/__tests__/deviceUploadNft.test.ts +316 -0
  29. package/__tests__/evmLedgerLegacySafety.test.ts +12 -6
  30. package/__tests__/evmSignTransaction.test.ts +69 -0
  31. package/__tests__/evmSignTypedData.test.ts +80 -0
  32. package/__tests__/filesystemValidation.test.ts +53 -0
  33. package/__tests__/firmware-memory-host.test.ts +126 -0
  34. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  35. package/__tests__/firmware-update/device-update-bootloader.test.ts +109 -0
  36. package/__tests__/firmware-update/firmware-artifact-source.test.ts +171 -0
  37. package/__tests__/firmware-update/firmware-host-binding.test.ts +79 -0
  38. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  39. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +583 -0
  40. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
  41. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +375 -0
  42. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  43. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +474 -0
  44. package/__tests__/firmware-update/firmware-update-plan.test.ts +819 -0
  45. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +283 -0
  46. package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
  47. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +457 -0
  48. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +121 -0
  49. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  50. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  51. package/__tests__/get-device-state.test.ts +578 -0
  52. package/__tests__/homescreen.test.ts +81 -0
  53. package/__tests__/kaspa-public-types.type-test.ts +16 -0
  54. package/__tests__/kaspa-use-tweak-pro2.test.ts +20 -0
  55. package/__tests__/kaspaSignTransaction.test.ts +578 -0
  56. package/__tests__/known-device-public-types.type-test.ts +3 -0
  57. package/__tests__/logBlockEvent.test.ts +128 -1
  58. package/__tests__/method-protocol-support.test.ts +144 -0
  59. package/__tests__/networkUtils.test.ts +406 -0
  60. package/__tests__/open-wallet-session-error-response.test.ts +43 -0
  61. package/__tests__/open-wallet-session.test.ts +1899 -0
  62. package/__tests__/pro2Nft.test.ts +108 -0
  63. package/__tests__/pro2Wallpaper.test.ts +39 -0
  64. package/__tests__/protocol-binding.test.ts +89 -0
  65. package/__tests__/protocol-v2-bootloader-mode.test.ts +37 -0
  66. package/__tests__/protocol-v2-firmware-targets.test.ts +105 -0
  67. package/__tests__/protocol-v2-ping.test.ts +28 -0
  68. package/__tests__/protocol-v2-resources.test.ts +270 -0
  69. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  70. package/__tests__/protocol-v2-unlock-policy.test.ts +308 -0
  71. package/__tests__/protocol-v2.test.ts +6964 -1766
  72. package/__tests__/protocolV2FileWrite.test.ts +285 -0
  73. package/__tests__/protocolV2UiInteraction.test.ts +329 -0
  74. package/__tests__/public-device-state-api.test.ts +275 -0
  75. package/__tests__/public-pro2-api-boundary.test.ts +107 -0
  76. package/__tests__/refresh-device-state.test.ts +101 -0
  77. package/__tests__/request-context-logging.test.ts +16 -0
  78. package/__tests__/search-devices.test.ts +145 -0
  79. package/__tests__/tron-sign-message-init.test.ts +74 -0
  80. package/__tests__/uiProgressThrottle.test.ts +74 -0
  81. package/__tests__/uiPromiseRegistry.test.ts +115 -0
  82. package/dist/api/BaseMethod.d.ts +16 -1
  83. package/dist/api/BaseMethod.d.ts.map +1 -1
  84. package/dist/api/CheckAllFirmwareRelease.d.ts +25 -1
  85. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  86. package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
  87. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  88. package/dist/api/CheckBootloaderRelease.d.ts +2 -1
  89. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  90. package/dist/api/CheckFirmwareRelease.d.ts +3 -2
  91. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  92. package/dist/api/CipherKeyValue.d.ts +1 -0
  93. package/dist/api/CipherKeyValue.d.ts.map +1 -1
  94. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  95. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  96. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  97. package/dist/api/DirList.d.ts +1 -0
  98. package/dist/api/DirList.d.ts.map +1 -1
  99. package/dist/api/DirMake.d.ts +1 -0
  100. package/dist/api/DirMake.d.ts.map +1 -1
  101. package/dist/api/DirRemove.d.ts +1 -0
  102. package/dist/api/DirRemove.d.ts.map +1 -1
  103. package/dist/api/FileDelete.d.ts +1 -0
  104. package/dist/api/FileDelete.d.ts.map +1 -1
  105. package/dist/api/FileRead.d.ts +1 -0
  106. package/dist/api/FileRead.d.ts.map +1 -1
  107. package/dist/api/FileWrite.d.ts +2 -0
  108. package/dist/api/FileWrite.d.ts.map +1 -1
  109. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  110. package/dist/api/FirmwareUpdateV2.d.ts +13 -2
  111. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  112. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  113. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  114. package/dist/api/FirmwareUpdateV4.d.ts +52 -16
  115. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  116. package/dist/api/GetDeviceState.d.ts +8 -0
  117. package/dist/api/GetDeviceState.d.ts.map +1 -0
  118. package/dist/api/GetFeatures.d.ts +1 -1
  119. package/dist/api/GetFeatures.d.ts.map +1 -1
  120. package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
  121. package/dist/api/GetPassphraseState.d.ts +2 -6
  122. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  123. package/dist/api/OpenWalletSession.d.ts +8 -0
  124. package/dist/api/OpenWalletSession.d.ts.map +1 -0
  125. package/dist/api/PathInfo.d.ts +1 -0
  126. package/dist/api/PathInfo.d.ts.map +1 -1
  127. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  128. package/dist/api/SearchDevices.d.ts +3 -0
  129. package/dist/api/SearchDevices.d.ts.map +1 -1
  130. package/dist/api/UploadPortfolio.d.ts +17 -0
  131. package/dist/api/UploadPortfolio.d.ts.map +1 -0
  132. package/dist/api/alephium/AlephiumGetAddress.d.ts.map +1 -1
  133. package/dist/api/alephium/AlephiumSignMessage.d.ts.map +1 -1
  134. package/dist/api/alephium/AlephiumSignTransaction.d.ts.map +1 -1
  135. package/dist/api/algo/AlgoGetAddress.d.ts +1 -0
  136. package/dist/api/algo/AlgoGetAddress.d.ts.map +1 -1
  137. package/dist/api/algo/AlgoSignTransaction.d.ts +1 -0
  138. package/dist/api/algo/AlgoSignTransaction.d.ts.map +1 -1
  139. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +1 -0
  140. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  141. package/dist/api/aptos/AptosGetAddress.d.ts +1 -0
  142. package/dist/api/aptos/AptosGetAddress.d.ts.map +1 -1
  143. package/dist/api/aptos/AptosGetPublicKey.d.ts +1 -0
  144. package/dist/api/aptos/AptosGetPublicKey.d.ts.map +1 -1
  145. package/dist/api/aptos/AptosSignInMessage.d.ts +1 -0
  146. package/dist/api/aptos/AptosSignInMessage.d.ts.map +1 -1
  147. package/dist/api/aptos/AptosSignMessage.d.ts +1 -0
  148. package/dist/api/aptos/AptosSignMessage.d.ts.map +1 -1
  149. package/dist/api/aptos/AptosSignTransaction.d.ts +1 -0
  150. package/dist/api/aptos/AptosSignTransaction.d.ts.map +1 -1
  151. package/dist/api/benfen/BenfenGetAddress.d.ts.map +1 -1
  152. package/dist/api/benfen/BenfenGetPublicKey.d.ts.map +1 -1
  153. package/dist/api/benfen/BenfenSignMessage.d.ts.map +1 -1
  154. package/dist/api/benfen/BenfenSignTransaction.d.ts.map +1 -1
  155. package/dist/api/btc/BTCGetAddress.d.ts +1 -0
  156. package/dist/api/btc/BTCGetAddress.d.ts.map +1 -1
  157. package/dist/api/btc/BTCGetPublicKey.d.ts +1 -0
  158. package/dist/api/btc/BTCGetPublicKey.d.ts.map +1 -1
  159. package/dist/api/btc/BTCSignMessage.d.ts +1 -0
  160. package/dist/api/btc/BTCSignMessage.d.ts.map +1 -1
  161. package/dist/api/btc/BTCSignPsbt.d.ts +1 -0
  162. package/dist/api/btc/BTCSignPsbt.d.ts.map +1 -1
  163. package/dist/api/btc/BTCSignTransaction.d.ts +1 -0
  164. package/dist/api/btc/BTCSignTransaction.d.ts.map +1 -1
  165. package/dist/api/btc/BTCVerifyMessage.d.ts +1 -0
  166. package/dist/api/btc/BTCVerifyMessage.d.ts.map +1 -1
  167. package/dist/api/btc/helpers/versionLimit.d.ts +2 -0
  168. package/dist/api/btc/helpers/versionLimit.d.ts.map +1 -1
  169. package/dist/api/cardano/CardanoGetAddress.d.ts +1 -0
  170. package/dist/api/cardano/CardanoGetAddress.d.ts.map +1 -1
  171. package/dist/api/cardano/CardanoGetPublicKey.d.ts +1 -0
  172. package/dist/api/cardano/CardanoGetPublicKey.d.ts.map +1 -1
  173. package/dist/api/cardano/CardanoSignMessage.d.ts +1 -0
  174. package/dist/api/cardano/CardanoSignMessage.d.ts.map +1 -1
  175. package/dist/api/cardano/CardanoSignTransaction.d.ts +1 -0
  176. package/dist/api/cardano/CardanoSignTransaction.d.ts.map +1 -1
  177. package/dist/api/conflux/ConfluxGetAddress.d.ts +2 -0
  178. package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
  179. package/dist/api/conflux/ConfluxSignMessage.d.ts +2 -0
  180. package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
  181. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +2 -0
  182. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
  183. package/dist/api/conflux/ConfluxSignTransaction.d.ts +2 -0
  184. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  185. package/dist/api/cosmos/CosmosGetAddress.d.ts +1 -0
  186. package/dist/api/cosmos/CosmosGetAddress.d.ts.map +1 -1
  187. package/dist/api/cosmos/CosmosGetPublicKey.d.ts +1 -0
  188. package/dist/api/cosmos/CosmosGetPublicKey.d.ts.map +1 -1
  189. package/dist/api/cosmos/CosmosSignTransaction.d.ts +1 -0
  190. package/dist/api/cosmos/CosmosSignTransaction.d.ts.map +1 -1
  191. package/dist/api/device/DeviceCancel.d.ts +1 -0
  192. package/dist/api/device/DeviceCancel.d.ts.map +1 -1
  193. package/dist/api/device/DeviceChangePin.d.ts +1 -0
  194. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  195. package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
  196. package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
  197. package/dist/api/device/DeviceLock.d.ts +1 -0
  198. package/dist/api/device/DeviceLock.d.ts.map +1 -1
  199. package/dist/api/device/DeviceRebootToBoardloader.d.ts +1 -0
  200. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  201. package/dist/api/device/DeviceRebootToBootloader.d.ts +1 -0
  202. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  203. package/dist/api/device/DeviceSettings.d.ts +1 -0
  204. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  205. package/dist/api/device/DeviceSupportFeatures.d.ts +1 -0
  206. package/dist/api/device/DeviceSupportFeatures.d.ts.map +1 -1
  207. package/dist/api/device/DeviceUnlock.d.ts +3 -2
  208. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  209. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  210. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  211. package/dist/api/device/DeviceWipe.d.ts +1 -0
  212. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  213. package/dist/api/device/PreInitialize.d.ts +1 -0
  214. package/dist/api/device/PreInitialize.d.ts.map +1 -1
  215. package/dist/api/dynex/DnxGetAddress.d.ts.map +1 -1
  216. package/dist/api/dynex/DnxSignTransaction.d.ts.map +1 -1
  217. package/dist/api/evm/EVMGetAddress.d.ts +1 -0
  218. package/dist/api/evm/EVMGetAddress.d.ts.map +1 -1
  219. package/dist/api/evm/EVMGetPublicKey.d.ts +1 -0
  220. package/dist/api/evm/EVMGetPublicKey.d.ts.map +1 -1
  221. package/dist/api/evm/EVMSignMessage.d.ts +1 -0
  222. package/dist/api/evm/EVMSignMessage.d.ts.map +1 -1
  223. package/dist/api/evm/EVMSignMessageEIP712.d.ts.map +1 -1
  224. package/dist/api/evm/EVMSignTransaction.d.ts +1 -0
  225. package/dist/api/evm/EVMSignTransaction.d.ts.map +1 -1
  226. package/dist/api/evm/EVMSignTypedData.d.ts +1 -0
  227. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  228. package/dist/api/evm/EVMVerifyMessage.d.ts +1 -0
  229. package/dist/api/evm/EVMVerifyMessage.d.ts.map +1 -1
  230. package/dist/api/evm/protocol.d.ts +3 -0
  231. package/dist/api/evm/protocol.d.ts.map +1 -0
  232. package/dist/api/filecoin/FilecoinGetAddress.d.ts +1 -0
  233. package/dist/api/filecoin/FilecoinGetAddress.d.ts.map +1 -1
  234. package/dist/api/filecoin/FilecoinSignTransaction.d.ts +1 -0
  235. package/dist/api/filecoin/FilecoinSignTransaction.d.ts.map +1 -1
  236. package/dist/api/firmware/FirmwareArtifactSource.d.ts +29 -0
  237. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  238. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  239. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  240. package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
  241. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
  242. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  243. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  244. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
  245. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
  246. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +12 -3
  247. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  248. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  249. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  250. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +72 -0
  251. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  252. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +38 -0
  253. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  254. package/dist/api/firmware/getBinary.d.ts +14 -4
  255. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  256. package/dist/api/firmware/protocolV2Release.d.ts +33 -0
  257. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
  258. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  259. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  260. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  261. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  262. package/dist/api/helpers/filesystemValidation.d.ts +3 -0
  263. package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
  264. package/dist/api/helpers/paramsValidator.d.ts +1 -0
  265. package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
  266. package/dist/api/helpers/protocolV2FileWrite.d.ts +43 -0
  267. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -0
  268. package/dist/api/index.d.ts +8 -27
  269. package/dist/api/index.d.ts.map +1 -1
  270. package/dist/api/kaspa/KaspaGetAddress.d.ts +4 -0
  271. package/dist/api/kaspa/KaspaGetAddress.d.ts.map +1 -1
  272. package/dist/api/kaspa/KaspaSignTransaction.d.ts +9 -1
  273. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  274. package/dist/api/kaspa/helpers/TransferSerialize.d.ts.map +1 -1
  275. package/dist/api/lightning/LnurlAuth.d.ts +0 -4
  276. package/dist/api/lightning/LnurlAuth.d.ts.map +1 -1
  277. package/dist/api/near/NearGetAddress.d.ts +1 -0
  278. package/dist/api/near/NearGetAddress.d.ts.map +1 -1
  279. package/dist/api/near/NearSignTransaction.d.ts +1 -0
  280. package/dist/api/near/NearSignTransaction.d.ts.map +1 -1
  281. package/dist/api/nem/NEMGetAddress.d.ts +1 -0
  282. package/dist/api/nem/NEMGetAddress.d.ts.map +1 -1
  283. package/dist/api/nem/NEMSignTransaction.d.ts +1 -0
  284. package/dist/api/nem/NEMSignTransaction.d.ts.map +1 -1
  285. package/dist/api/neo/NeoGetAddress.d.ts.map +1 -1
  286. package/dist/api/neo/NeoSignTransaction.d.ts.map +1 -1
  287. package/dist/api/nervos/NervosGetAddress.d.ts.map +1 -1
  288. package/dist/api/nervos/NervosSignTransaction.d.ts.map +1 -1
  289. package/dist/api/nexa/NexaGetAddress.d.ts.map +1 -1
  290. package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
  291. package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
  292. package/dist/api/nostr/NostrDecryptMessage.d.ts +1 -0
  293. package/dist/api/nostr/NostrDecryptMessage.d.ts.map +1 -1
  294. package/dist/api/nostr/NostrEncryptMessage.d.ts +1 -0
  295. package/dist/api/nostr/NostrEncryptMessage.d.ts.map +1 -1
  296. package/dist/api/nostr/NostrGetPublicKey.d.ts +1 -0
  297. package/dist/api/nostr/NostrGetPublicKey.d.ts.map +1 -1
  298. package/dist/api/nostr/NostrSignEvent.d.ts +1 -0
  299. package/dist/api/nostr/NostrSignEvent.d.ts.map +1 -1
  300. package/dist/api/nostr/NostrSignSchnorr.d.ts +1 -0
  301. package/dist/api/nostr/NostrSignSchnorr.d.ts.map +1 -1
  302. package/dist/api/polkadot/PolkadotGetAddress.d.ts +2 -1
  303. package/dist/api/polkadot/PolkadotGetAddress.d.ts.map +1 -1
  304. package/dist/api/polkadot/PolkadotSignTransaction.d.ts +1 -0
  305. package/dist/api/polkadot/PolkadotSignTransaction.d.ts.map +1 -1
  306. package/dist/api/polkadot/networks.d.ts +1 -1
  307. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +1 -0
  308. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -1
  309. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +1 -0
  310. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -1
  311. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -0
  312. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  313. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +1 -0
  314. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  315. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +7 -0
  316. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -0
  317. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  318. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  319. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -0
  320. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  321. package/dist/api/protocol-v2/DeviceStatusGet.d.ts +1 -0
  322. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -1
  323. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  324. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  325. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +27 -0
  326. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -0
  327. package/dist/api/protocol-v2/FilesystemFormat.d.ts +1 -0
  328. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
  329. package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts +1 -0
  330. package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -1
  331. package/dist/api/protocol-v2/Ping.d.ts +3 -0
  332. package/dist/api/protocol-v2/Ping.d.ts.map +1 -1
  333. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +1 -0
  334. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  335. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  336. package/dist/api/scdo/ScdoGetAddress.d.ts.map +1 -1
  337. package/dist/api/scdo/ScdoSignMessage.d.ts.map +1 -1
  338. package/dist/api/scdo/ScdoSignTransaction.d.ts.map +1 -1
  339. package/dist/api/solana/SolGetAddress.d.ts +1 -0
  340. package/dist/api/solana/SolGetAddress.d.ts.map +1 -1
  341. package/dist/api/solana/SolSignMessage.d.ts +2 -1
  342. package/dist/api/solana/SolSignMessage.d.ts.map +1 -1
  343. package/dist/api/solana/SolSignOffchainMessage.d.ts +2 -1
  344. package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
  345. package/dist/api/solana/SolSignTransaction.d.ts +3 -2
  346. package/dist/api/solana/SolSignTransaction.d.ts.map +1 -1
  347. package/dist/api/starcoin/StarcoinGetAddress.d.ts +1 -0
  348. package/dist/api/starcoin/StarcoinGetAddress.d.ts.map +1 -1
  349. package/dist/api/starcoin/StarcoinGetPublicKey.d.ts +1 -0
  350. package/dist/api/starcoin/StarcoinGetPublicKey.d.ts.map +1 -1
  351. package/dist/api/starcoin/StarcoinSignMessage.d.ts +1 -0
  352. package/dist/api/starcoin/StarcoinSignMessage.d.ts.map +1 -1
  353. package/dist/api/starcoin/StarcoinSignTransaction.d.ts +1 -0
  354. package/dist/api/starcoin/StarcoinSignTransaction.d.ts.map +1 -1
  355. package/dist/api/starcoin/StarcoinVerifyMessage.d.ts +1 -0
  356. package/dist/api/starcoin/StarcoinVerifyMessage.d.ts.map +1 -1
  357. package/dist/api/stellar/StellarGetAddress.d.ts +1 -2
  358. package/dist/api/stellar/StellarGetAddress.d.ts.map +1 -1
  359. package/dist/api/stellar/StellarSignTransaction.d.ts +2 -3
  360. package/dist/api/stellar/StellarSignTransaction.d.ts.map +1 -1
  361. package/dist/api/sui/SuiGetAddress.d.ts +2 -1
  362. package/dist/api/sui/SuiGetAddress.d.ts.map +1 -1
  363. package/dist/api/sui/SuiGetPublicKey.d.ts +2 -1
  364. package/dist/api/sui/SuiGetPublicKey.d.ts.map +1 -1
  365. package/dist/api/sui/SuiSignMessage.d.ts +2 -1
  366. package/dist/api/sui/SuiSignMessage.d.ts.map +1 -1
  367. package/dist/api/sui/SuiSignTransaction.d.ts +2 -1
  368. package/dist/api/sui/SuiSignTransaction.d.ts.map +1 -1
  369. package/dist/api/ton/TonGetAddress.d.ts +2 -1
  370. package/dist/api/ton/TonGetAddress.d.ts.map +1 -1
  371. package/dist/api/ton/TonSignData.d.ts +1 -0
  372. package/dist/api/ton/TonSignData.d.ts.map +1 -1
  373. package/dist/api/ton/TonSignMessage.d.ts +2 -1
  374. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  375. package/dist/api/ton/TonSignProof.d.ts +2 -1
  376. package/dist/api/ton/TonSignProof.d.ts.map +1 -1
  377. package/dist/api/tron/TronGetAddress.d.ts +1 -0
  378. package/dist/api/tron/TronGetAddress.d.ts.map +1 -1
  379. package/dist/api/tron/TronSignMessage.d.ts +6 -1
  380. package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
  381. package/dist/api/tron/TronSignTransaction.d.ts +2 -1
  382. package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
  383. package/dist/api/utils.d.ts +4 -1
  384. package/dist/api/utils.d.ts.map +1 -1
  385. package/dist/api/xrp/XrpGetAddress.d.ts +1 -0
  386. package/dist/api/xrp/XrpGetAddress.d.ts.map +1 -1
  387. package/dist/api/xrp/XrpSignTransaction.d.ts +1 -0
  388. package/dist/api/xrp/XrpSignTransaction.d.ts.map +1 -1
  389. package/dist/constants/index.d.ts +1 -1
  390. package/dist/constants/index.d.ts.map +1 -1
  391. package/dist/core/RequestQueue.d.ts.map +1 -1
  392. package/dist/core/deviceEventRegistration.d.ts +13 -0
  393. package/dist/core/deviceEventRegistration.d.ts.map +1 -0
  394. package/dist/core/index.d.ts +3 -1
  395. package/dist/core/index.d.ts.map +1 -1
  396. package/dist/core/uiPromiseRegistry.d.ts +6 -0
  397. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  398. package/dist/data-manager/DataManager.d.ts +11 -3
  399. package/dist/data-manager/DataManager.d.ts.map +1 -1
  400. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  401. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  402. package/dist/device/Device.d.ts +71 -16
  403. package/dist/device/Device.d.ts.map +1 -1
  404. package/dist/device/DeviceCommands.d.ts +8 -6
  405. package/dist/device/DeviceCommands.d.ts.map +1 -1
  406. package/dist/device/DeviceConnector.d.ts +4 -3
  407. package/dist/device/DeviceConnector.d.ts.map +1 -1
  408. package/dist/device/DeviceFeaturesState.d.ts +3 -0
  409. package/dist/device/DeviceFeaturesState.d.ts.map +1 -0
  410. package/dist/device/DevicePool.d.ts +6 -2
  411. package/dist/device/DevicePool.d.ts.map +1 -1
  412. package/dist/device/DeviceStateMapper.d.ts +13 -0
  413. package/dist/device/DeviceStateMapper.d.ts.map +1 -0
  414. package/dist/device/DeviceStateProjector.d.ts +7 -0
  415. package/dist/device/DeviceStateProjector.d.ts.map +1 -0
  416. package/dist/device/DeviceStateStore.d.ts +21 -0
  417. package/dist/device/DeviceStateStore.d.ts.map +1 -0
  418. package/dist/device/DeviceWalletSessionStore.d.ts +13 -1
  419. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  420. package/dist/device/cloneDeviceState.d.ts +2 -0
  421. package/dist/device/cloneDeviceState.d.ts.map +1 -0
  422. package/dist/device/deviceIdentity.d.ts +5 -0
  423. package/dist/device/deviceIdentity.d.ts.map +1 -0
  424. package/dist/deviceProfile/buildDeviceFeatures.d.ts +9 -3
  425. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  426. package/dist/deviceProfile/index.d.ts +0 -1
  427. package/dist/deviceProfile/index.d.ts.map +1 -1
  428. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
  429. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
  430. package/dist/events/device.d.ts +16 -2
  431. package/dist/events/device.d.ts.map +1 -1
  432. package/dist/events/logBlockEvent.d.ts +2 -0
  433. package/dist/events/logBlockEvent.d.ts.map +1 -1
  434. package/dist/events/ui-promise.d.ts +4 -2
  435. package/dist/events/ui-promise.d.ts.map +1 -1
  436. package/dist/events/ui-request.d.ts +50 -4
  437. package/dist/events/ui-request.d.ts.map +1 -1
  438. package/dist/events/ui-response.d.ts +10 -2
  439. package/dist/events/ui-response.d.ts.map +1 -1
  440. package/dist/index.d.ts +1779 -778
  441. package/dist/index.d.ts.map +1 -1
  442. package/dist/index.js +12020 -4543
  443. package/dist/inject.d.ts +6 -1
  444. package/dist/inject.d.ts.map +1 -1
  445. package/dist/lowLevelInject.d.ts.map +1 -1
  446. package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
  447. package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
  448. package/dist/protocols/protocol-v2/features.d.ts +21 -17
  449. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  450. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  451. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  452. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  453. package/dist/protocols/protocol-v2/resources.d.ts +12 -0
  454. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  455. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +17 -0
  456. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
  457. package/dist/protocols/protocol-v2/uiInteraction.d.ts +42 -0
  458. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -0
  459. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +2 -0
  460. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
  461. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  462. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  463. package/dist/protocols/protocol-v2/walletSession.d.ts +18 -4
  464. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  465. package/dist/topLevelInject.d.ts.map +1 -1
  466. package/dist/types/api/checkAllFirmwareRelease.d.ts +48 -9
  467. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  468. package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
  469. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  470. package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
  471. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  472. package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
  473. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  474. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  475. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  476. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  477. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  478. package/dist/types/api/deviceSettings.d.ts +11 -2
  479. package/dist/types/api/deviceSettings.d.ts.map +1 -1
  480. package/dist/types/api/deviceUnlock.d.ts +6 -2
  481. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  482. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  483. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  484. package/dist/types/api/export.d.ts +8 -3
  485. package/dist/types/api/export.d.ts.map +1 -1
  486. package/dist/types/api/firmwareUpdate.d.ts +68 -4
  487. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  488. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  489. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  490. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  491. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  492. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  493. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  494. package/dist/types/api/getDeviceState.d.ts +12 -0
  495. package/dist/types/api/getDeviceState.d.ts.map +1 -0
  496. package/dist/types/api/getFeatures.d.ts.map +1 -1
  497. package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
  498. package/dist/types/api/getPassphraseState.d.ts +2 -10
  499. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  500. package/dist/types/api/index.d.ts +29 -33
  501. package/dist/types/api/index.d.ts.map +1 -1
  502. package/dist/types/api/kaspaSignTransaction.d.ts +42 -4
  503. package/dist/types/api/kaspaSignTransaction.d.ts.map +1 -1
  504. package/dist/types/api/openWalletSession.d.ts +35 -0
  505. package/dist/types/api/openWalletSession.d.ts.map +1 -0
  506. package/dist/types/api/protocolV2.d.ts +18 -88
  507. package/dist/types/api/protocolV2.d.ts.map +1 -1
  508. package/dist/types/api/sessionCache.d.ts +4 -1
  509. package/dist/types/api/sessionCache.d.ts.map +1 -1
  510. package/dist/types/device.d.ts +142 -10
  511. package/dist/types/device.d.ts.map +1 -1
  512. package/dist/types/params.d.ts +1 -0
  513. package/dist/types/params.d.ts.map +1 -1
  514. package/dist/types/settings.d.ts +59 -16
  515. package/dist/types/settings.d.ts.map +1 -1
  516. package/dist/utils/assets.d.ts +2 -1
  517. package/dist/utils/assets.d.ts.map +1 -1
  518. package/dist/utils/deviceFeaturesCompat.d.ts +14 -0
  519. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
  520. package/dist/utils/deviceFeaturesUtils.d.ts +10 -3
  521. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  522. package/dist/utils/deviceInfoUtils.d.ts +9 -9
  523. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  524. package/dist/utils/deviceSettings.d.ts +38 -7
  525. package/dist/utils/deviceSettings.d.ts.map +1 -1
  526. package/dist/utils/deviceVersionUtils.d.ts +7 -5
  527. package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
  528. package/dist/utils/homescreen.d.ts +4 -0
  529. package/dist/utils/homescreen.d.ts.map +1 -1
  530. package/dist/utils/index.d.ts +6 -3
  531. package/dist/utils/index.d.ts.map +1 -1
  532. package/dist/utils/networkUtils.d.ts +10 -1
  533. package/dist/utils/networkUtils.d.ts.map +1 -1
  534. package/dist/utils/patch.d.ts +1 -1
  535. package/dist/utils/patch.d.ts.map +1 -1
  536. package/dist/utils/pro2Nft.d.ts +31 -0
  537. package/dist/utils/pro2Nft.d.ts.map +1 -0
  538. package/dist/utils/pro2Wallpaper.d.ts +22 -0
  539. package/dist/utils/pro2Wallpaper.d.ts.map +1 -0
  540. package/dist/utils/tracing.d.ts.map +1 -1
  541. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  542. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  543. package/package.json +4 -4
  544. package/src/api/BaseMethod.ts +53 -20
  545. package/src/api/CheckAllFirmwareRelease.ts +442 -4
  546. package/src/api/CheckBLEFirmwareRelease.ts +37 -5
  547. package/src/api/CheckBootloaderRelease.ts +37 -5
  548. package/src/api/CheckFirmwareRelease.ts +36 -9
  549. package/src/api/CipherKeyValue.ts +4 -0
  550. package/src/api/ClearSessionCache.ts +4 -0
  551. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  552. package/src/api/DirList.ts +9 -4
  553. package/src/api/DirMake.ts +7 -4
  554. package/src/api/DirRemove.ts +7 -4
  555. package/src/api/FileDelete.ts +7 -4
  556. package/src/api/FileRead.ts +13 -7
  557. package/src/api/FileWrite.ts +26 -181
  558. package/src/api/FirmwareUpdate.ts +30 -15
  559. package/src/api/FirmwareUpdateV2.ts +532 -169
  560. package/src/api/FirmwareUpdateV3.ts +320 -72
  561. package/src/api/FirmwareUpdateV4.ts +2183 -731
  562. package/src/api/GetDeviceState.ts +59 -0
  563. package/src/api/GetFeatures.ts +6 -4
  564. package/src/api/GetOnekeyFeatures.ts +1 -75
  565. package/src/api/GetPassphraseState.ts +36 -21
  566. package/src/api/OpenWalletSession.ts +268 -0
  567. package/src/api/PathInfo.ts +9 -4
  568. package/src/api/PromptWebDeviceAccess.ts +2 -7
  569. package/src/api/SearchDevices.ts +31 -4
  570. package/src/api/UploadPortfolio.ts +58 -0
  571. package/src/api/alephium/AlephiumGetAddress.ts +0 -4
  572. package/src/api/alephium/AlephiumSignMessage.ts +0 -4
  573. package/src/api/alephium/AlephiumSignTransaction.ts +2 -6
  574. package/src/api/algo/AlgoGetAddress.ts +4 -0
  575. package/src/api/algo/AlgoSignTransaction.ts +4 -0
  576. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  577. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +45 -32
  578. package/src/api/aptos/AptosGetAddress.ts +4 -0
  579. package/src/api/aptos/AptosGetPublicKey.ts +4 -0
  580. package/src/api/aptos/AptosSignInMessage.ts +4 -0
  581. package/src/api/aptos/AptosSignMessage.ts +4 -0
  582. package/src/api/aptos/AptosSignTransaction.ts +4 -0
  583. package/src/api/benfen/BenfenGetAddress.ts +11 -7
  584. package/src/api/benfen/BenfenGetPublicKey.ts +0 -4
  585. package/src/api/benfen/BenfenSignMessage.ts +0 -4
  586. package/src/api/benfen/BenfenSignTransaction.ts +0 -4
  587. package/src/api/btc/BTCGetAddress.ts +8 -1
  588. package/src/api/btc/BTCGetPublicKey.ts +9 -2
  589. package/src/api/btc/BTCSignMessage.ts +8 -1
  590. package/src/api/btc/BTCSignPsbt.ts +4 -0
  591. package/src/api/btc/BTCSignTransaction.ts +8 -1
  592. package/src/api/btc/BTCVerifyMessage.ts +8 -1
  593. package/src/api/btc/helpers/versionLimit.ts +7 -4
  594. package/src/api/cardano/CardanoGetAddress.ts +4 -0
  595. package/src/api/cardano/CardanoGetPublicKey.ts +4 -0
  596. package/src/api/cardano/CardanoSignMessage.ts +5 -1
  597. package/src/api/cardano/CardanoSignTransaction.ts +5 -1
  598. package/src/api/conflux/ConfluxGetAddress.ts +6 -0
  599. package/src/api/conflux/ConfluxSignMessage.ts +6 -0
  600. package/src/api/conflux/ConfluxSignMessageCIP23.ts +6 -0
  601. package/src/api/conflux/ConfluxSignTransaction.ts +8 -5
  602. package/src/api/cosmos/CosmosGetAddress.ts +4 -0
  603. package/src/api/cosmos/CosmosGetPublicKey.ts +4 -0
  604. package/src/api/cosmos/CosmosSignTransaction.ts +4 -0
  605. package/src/api/device/DeviceCancel.ts +5 -0
  606. package/src/api/device/DeviceChangePin.ts +28 -0
  607. package/src/api/device/DeviceFullyUploadResource.ts +4 -5
  608. package/src/api/device/DeviceLock.ts +5 -0
  609. package/src/api/device/DeviceRebootToBoardloader.ts +5 -0
  610. package/src/api/device/DeviceRebootToBootloader.ts +5 -0
  611. package/src/api/device/DeviceSettings.ts +195 -1
  612. package/src/api/device/DeviceSupportFeatures.ts +4 -0
  613. package/src/api/device/DeviceUnlock.ts +21 -3
  614. package/src/api/device/DeviceUpdateBootloader.ts +152 -8
  615. package/src/api/device/DeviceWipe.ts +25 -0
  616. package/src/api/device/PreInitialize.ts +4 -0
  617. package/src/api/dynex/DnxGetAddress.ts +0 -6
  618. package/src/api/dynex/DnxSignTransaction.ts +0 -6
  619. package/src/api/evm/EVMGetAddress.ts +6 -2
  620. package/src/api/evm/EVMGetPublicKey.ts +6 -2
  621. package/src/api/evm/EVMSignMessage.ts +6 -2
  622. package/src/api/evm/EVMSignMessageEIP712.ts +1 -11
  623. package/src/api/evm/EVMSignTransaction.ts +6 -2
  624. package/src/api/evm/EVMSignTypedData.ts +42 -50
  625. package/src/api/evm/EVMVerifyMessage.ts +6 -2
  626. package/src/api/evm/legacyV1/getAddress.ts +2 -2
  627. package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
  628. package/src/api/evm/legacyV1/signMessage.ts +2 -2
  629. package/src/api/evm/legacyV1/signTypedData.ts +2 -2
  630. package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
  631. package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
  632. package/src/api/evm/protocol.ts +6 -0
  633. package/src/api/filecoin/FilecoinGetAddress.ts +4 -0
  634. package/src/api/filecoin/FilecoinSignTransaction.ts +4 -0
  635. package/src/api/firmware/FirmwareArtifactSource.ts +305 -0
  636. package/src/api/firmware/FirmwareHostBinding.ts +123 -0
  637. package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
  638. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  639. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +187 -0
  640. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +214 -77
  641. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  642. package/src/api/firmware/FirmwareUpdatePlan.ts +946 -0
  643. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +486 -0
  644. package/src/api/firmware/getBinary.ts +8 -3
  645. package/src/api/firmware/protocolV2Release.ts +168 -0
  646. package/src/api/firmware/updateBootloader.ts +19 -4
  647. package/src/api/firmware/uploadFirmware.ts +157 -26
  648. package/src/api/helpers/batchGetPublickeys.ts +2 -2
  649. package/src/api/helpers/filesystemValidation.ts +61 -0
  650. package/src/api/helpers/paramsValidator.ts +1 -1
  651. package/src/api/helpers/protocolV2FileWrite.ts +392 -0
  652. package/src/api/index.ts +9 -28
  653. package/src/api/kaspa/KaspaGetAddress.ts +7 -0
  654. package/src/api/kaspa/KaspaSignTransaction.ts +369 -29
  655. package/src/api/kaspa/helpers/TransferSerialize.ts +17 -7
  656. package/src/api/lightning/LnurlAuth.ts +0 -4
  657. package/src/api/near/NearGetAddress.ts +4 -0
  658. package/src/api/near/NearSignTransaction.ts +4 -0
  659. package/src/api/nem/NEMGetAddress.ts +4 -0
  660. package/src/api/nem/NEMSignTransaction.ts +4 -0
  661. package/src/api/neo/NeoGetAddress.ts +0 -4
  662. package/src/api/neo/NeoSignTransaction.ts +0 -4
  663. package/src/api/nervos/NervosGetAddress.ts +0 -4
  664. package/src/api/nervos/NervosSignTransaction.ts +2 -6
  665. package/src/api/nexa/NexaGetAddress.ts +0 -4
  666. package/src/api/nexa/NexaSignTransaction.ts +3 -5
  667. package/src/api/nostr/NostrDecryptMessage.ts +4 -0
  668. package/src/api/nostr/NostrEncryptMessage.ts +4 -0
  669. package/src/api/nostr/NostrGetPublicKey.ts +4 -0
  670. package/src/api/nostr/NostrSignEvent.ts +4 -0
  671. package/src/api/nostr/NostrSignSchnorr.ts +4 -0
  672. package/src/api/polkadot/PolkadotGetAddress.ts +4 -0
  673. package/src/api/polkadot/PolkadotSignTransaction.ts +4 -0
  674. package/src/api/polkadot/networks.ts +3 -3
  675. package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +5 -2
  676. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +5 -2
  677. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +8 -5
  678. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +10 -6
  679. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +21 -0
  680. package/src/api/protocol-v2/DeviceInfoGet.ts +8 -15
  681. package/src/api/protocol-v2/DeviceReboot.ts +7 -3
  682. package/src/api/protocol-v2/DeviceStatusGet.ts +6 -1
  683. package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
  684. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +134 -0
  685. package/src/api/protocol-v2/FilesystemFormat.ts +5 -2
  686. package/src/api/protocol-v2/FilesystemPermissionFix.ts +5 -2
  687. package/src/api/protocol-v2/Ping.ts +37 -3
  688. package/src/api/protocol-v2/ProtocolInfoRequest.ts +8 -3
  689. package/src/api/protocol-v2/helpers.ts +1 -4
  690. package/src/api/scdo/ScdoGetAddress.ts +0 -4
  691. package/src/api/scdo/ScdoSignMessage.ts +0 -4
  692. package/src/api/scdo/ScdoSignTransaction.ts +0 -4
  693. package/src/api/solana/SolGetAddress.ts +4 -0
  694. package/src/api/solana/SolSignMessage.ts +5 -1
  695. package/src/api/solana/SolSignOffchainMessage.ts +5 -1
  696. package/src/api/solana/SolSignTransaction.ts +6 -2
  697. package/src/api/starcoin/StarcoinGetAddress.ts +4 -0
  698. package/src/api/starcoin/StarcoinGetPublicKey.ts +4 -0
  699. package/src/api/starcoin/StarcoinSignMessage.ts +4 -0
  700. package/src/api/starcoin/StarcoinSignTransaction.ts +4 -0
  701. package/src/api/starcoin/StarcoinVerifyMessage.ts +4 -0
  702. package/src/api/stellar/StellarGetAddress.ts +1 -10
  703. package/src/api/stellar/StellarSignTransaction.ts +1 -14
  704. package/src/api/sui/SuiGetAddress.ts +5 -1
  705. package/src/api/sui/SuiGetPublicKey.ts +5 -1
  706. package/src/api/sui/SuiSignMessage.ts +5 -1
  707. package/src/api/sui/SuiSignTransaction.ts +5 -1
  708. package/src/api/ton/TonGetAddress.ts +5 -1
  709. package/src/api/ton/TonSignData.ts +5 -1
  710. package/src/api/ton/TonSignMessage.ts +5 -1
  711. package/src/api/ton/TonSignProof.ts +5 -1
  712. package/src/api/tron/TronGetAddress.ts +4 -0
  713. package/src/api/tron/TronSignMessage.ts +19 -8
  714. package/src/api/tron/TronSignTransaction.ts +5 -1
  715. package/src/api/utils.ts +32 -3
  716. package/src/api/xrp/XrpGetAddress.ts +4 -0
  717. package/src/api/xrp/XrpSignTransaction.ts +5 -1
  718. package/src/constants/index.ts +1 -4
  719. package/src/core/RequestQueue.ts +1 -0
  720. package/src/core/deviceEventRegistration.ts +27 -0
  721. package/src/core/index.ts +381 -152
  722. package/src/core/uiPromiseRegistry.ts +63 -0
  723. package/src/data/messages/messages-protocol-v2.json +1095 -548
  724. package/src/data/messages/messages.json +286 -9
  725. package/src/data-manager/DataManager.ts +154 -43
  726. package/src/data-manager/TransportManager.ts +7 -1
  727. package/src/data-manager/connectSettings.ts +11 -0
  728. package/src/device/Device.ts +858 -264
  729. package/src/device/DeviceCommands.ts +193 -158
  730. package/src/device/DeviceConnector.ts +44 -21
  731. package/src/device/DeviceFeaturesState.ts +25 -0
  732. package/src/device/DevicePool.ts +101 -23
  733. package/src/device/DeviceStateMapper.ts +416 -0
  734. package/src/device/DeviceStateProjector.ts +152 -0
  735. package/src/device/DeviceStateStore.ts +232 -0
  736. package/src/device/DeviceWalletSessionStore.ts +72 -6
  737. package/src/device/cloneDeviceState.ts +39 -0
  738. package/src/device/deviceIdentity.ts +18 -0
  739. package/src/deviceProfile/buildDeviceFeatures.ts +223 -189
  740. package/src/deviceProfile/index.ts +0 -1
  741. package/src/deviceProfile/protocolV2DeviceIdentity.ts +35 -0
  742. package/src/events/device.ts +22 -1
  743. package/src/events/logBlockEvent.ts +80 -2
  744. package/src/events/ui-promise.ts +4 -2
  745. package/src/events/ui-request.ts +61 -4
  746. package/src/events/ui-response.ts +12 -2
  747. package/src/index.ts +17 -0
  748. package/src/inject.ts +94 -47
  749. package/src/lowLevelInject.ts +11 -4
  750. package/src/protocols/protocol-v2/deviceSession.ts +24 -0
  751. package/src/protocols/protocol-v2/features.ts +100 -33
  752. package/src/protocols/protocol-v2/firmware.ts +3 -1
  753. package/src/protocols/protocol-v2/index.ts +7 -1
  754. package/src/protocols/protocol-v2/resources.ts +210 -0
  755. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +65 -0
  756. package/src/protocols/protocol-v2/uiInteraction.ts +250 -0
  757. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -0
  758. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
  759. package/src/protocols/protocol-v2/walletSession.ts +422 -42
  760. package/src/topLevelInject.ts +11 -4
  761. package/src/types/api/checkAllFirmwareRelease.ts +72 -9
  762. package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
  763. package/src/types/api/checkBootloaderRelease.ts +2 -6
  764. package/src/types/api/checkFirmwareRelease.ts +2 -13
  765. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  766. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  767. package/src/types/api/deviceSettings.ts +26 -2
  768. package/src/types/api/deviceUnlock.ts +13 -2
  769. package/src/types/api/deviceUpdateBootloader.ts +7 -0
  770. package/src/types/api/export.ts +33 -1
  771. package/src/types/api/firmwareUpdate.ts +97 -11
  772. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  773. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  774. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  775. package/src/types/api/getDeviceState.ts +19 -0
  776. package/src/types/api/getFeatures.ts +3 -0
  777. package/src/types/api/getOnekeyFeatures.ts +3 -0
  778. package/src/types/api/getPassphraseState.ts +9 -11
  779. package/src/types/api/index.ts +49 -68
  780. package/src/types/api/kaspaSignTransaction.ts +60 -4
  781. package/src/types/api/openWalletSession.ts +54 -0
  782. package/src/types/api/protocolV2.ts +33 -155
  783. package/src/types/api/sessionCache.ts +9 -4
  784. package/src/types/device.ts +216 -10
  785. package/src/types/params.ts +9 -2
  786. package/src/types/settings.ts +67 -25
  787. package/src/utils/assets.ts +4 -1
  788. package/src/utils/deviceFeaturesCompat.ts +191 -0
  789. package/src/utils/deviceFeaturesUtils.ts +45 -17
  790. package/src/utils/deviceInfoUtils.ts +29 -69
  791. package/src/utils/deviceSettings.ts +174 -39
  792. package/src/utils/deviceVersionUtils.ts +25 -44
  793. package/src/utils/findDefectiveBatchDevice.ts +3 -3
  794. package/src/utils/homescreen.ts +40 -0
  795. package/src/utils/index.ts +27 -3
  796. package/src/utils/networkUtils.ts +274 -16
  797. package/src/utils/pro2Nft.ts +142 -0
  798. package/src/utils/pro2Wallpaper.ts +130 -0
  799. package/src/utils/tracing.ts +3 -1
  800. package/src/utils/uiProgressThrottle.ts +63 -0
  801. package/tsconfig.type-tests.json +12 -0
  802. package/dist/api/GetDeviceInfo.d.ts +0 -9
  803. package/dist/api/GetDeviceInfo.d.ts.map +0 -1
  804. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  805. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  806. package/dist/api/protocol-v2/DeviceSessionGet.d.ts +0 -9
  807. package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +0 -1
  808. package/dist/api/protocol-v2/FilesystemDiskControl.d.ts +0 -13
  809. package/dist/api/protocol-v2/FilesystemDiskControl.d.ts.map +0 -1
  810. package/dist/deviceProfile/buildDeviceProfile.d.ts +0 -21
  811. package/dist/deviceProfile/buildDeviceProfile.d.ts.map +0 -1
  812. package/dist/types/api/getDeviceInfo.d.ts +0 -87
  813. package/dist/types/api/getDeviceInfo.d.ts.map +0 -1
  814. package/src/api/GetDeviceInfo.ts +0 -145
  815. package/src/api/firmware/progressThrottle.ts +0 -44
  816. package/src/api/protocol-v2/DeviceSessionGet.ts +0 -26
  817. package/src/api/protocol-v2/FilesystemDiskControl.ts +0 -50
  818. package/src/deviceProfile/buildDeviceProfile.ts +0 -352
  819. package/src/types/api/getDeviceInfo.ts +0 -101
@@ -1,9 +1,13 @@
1
- import { ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
2
+ import JSZip from 'jszip';
2
3
  import {
3
4
  DeviceRebootType,
4
5
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
6
+ PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
7
+ PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
5
8
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
6
9
  } from '@onekeyfe/hd-transport';
10
+ import { sha256 } from '@noble/hashes/sha256';
7
11
 
8
12
  import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
9
13
  import { validateParams } from './helpers/paramsValidator';
@@ -12,10 +16,12 @@ import {
12
16
  getDeviceBLEFirmwareVersion,
13
17
  getDeviceBootloaderVersion,
14
18
  getDeviceFirmwareVersion,
19
+ getDeviceType,
15
20
  getFirmwareType,
16
21
  getLogger,
17
22
  } from '../utils';
18
23
  import { getSysResourceBinary } from './firmware/getBinary';
24
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
19
25
  import { DataManager } from '../data-manager';
20
26
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
21
27
  import { DevicePool } from '../device/DevicePool';
@@ -25,14 +31,39 @@ import {
25
31
  } from '../protocols/protocol-v2';
26
32
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
27
33
  import {
28
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
34
+ parseProtocolV2ResourceManifest,
35
+ selectProtocolV2ResourceManifestFiles,
36
+ } from '../protocols/protocol-v2/resources';
37
+ import {
29
38
  getProtocolV2UnknownErrorText,
30
39
  isProtocolV2DeviceDisconnectedError,
31
40
  } from './protocol-v2/helpers';
41
+ import {
42
+ openFirmwareByteSource,
43
+ readFirmwareByteSourceFully,
44
+ writeFirmwareByteSource,
45
+ } from './firmware/FirmwareArtifactSource';
46
+ import {
47
+ registerFirmwareUpdateHostBinding,
48
+ resolveFirmwareUpdateHostBinding,
49
+ unregisterFirmwareUpdateHostBinding,
50
+ } from './firmware/FirmwareHostBinding';
51
+ import {
52
+ assertFirmwareUpdatePreparedPlanBinding,
53
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
54
+ prepareFirmwareUpdatePlan,
55
+ validateFirmwareUpdatePreparedPlan,
56
+ } from './firmware/FirmwareUpdatePreparedPlan';
57
+ import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
58
+ import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
32
59
 
33
- import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
60
+ import type {
61
+ FirmwareArtifactReference,
62
+ FirmwareUpdateV4Params,
63
+ FirmwareUpdateV4Target,
64
+ } from '../types/api/firmwareUpdate';
34
65
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
35
- import type { PROTO } from '../constants';
66
+ import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
36
67
  import type { TypedResponseMessage } from '../device/DeviceCommands';
37
68
  import type {
38
69
  Features,
@@ -40,14 +71,26 @@ import type {
40
71
  IProtocolV2FirmwareComponent,
41
72
  IVersionArray,
42
73
  } from '../types';
74
+ import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
75
+ import type {
76
+ FirmwareMemoryArtifact,
77
+ FirmwareMemoryArtifactEntry,
78
+ FirmwareUpdateV4MemoryHost,
79
+ } from './firmware/FirmwareMemoryHost';
43
80
 
44
81
  const Log = getLogger(LoggerNames.Method);
45
82
 
83
+ // Restored after a rebase dropped the declaration while keeping its use in
84
+ // fileWriteChunk; without it that error branch throws ReferenceError instead of
85
+ // the intended typed session error.
46
86
  const SESSION_ERROR = 'session not found';
47
- const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
87
+ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
88
+ const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
48
89
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
90
+ const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
49
91
  const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
50
- const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
92
+ const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
93
+ const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
51
94
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
52
95
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
53
96
  const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
@@ -58,12 +101,78 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
58
101
  const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
59
102
  const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
60
103
  const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
61
- const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
104
+ const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
62
105
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
106
+ const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
63
107
  const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
64
108
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
65
109
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
66
110
  const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
111
+ const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
112
+ const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
113
+ const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
114
+
115
+ const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
116
+
117
+ const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
118
+ // loadAsync records central-directory sizes on JSZip's documented private data object.
119
+ // Validate those bounds before calling async(), which allocates the decompressed entry.
120
+ const { compressedSize, uncompressedSize } = (entry as JSZipSizedEntry)._data ?? {};
121
+ if (
122
+ !Number.isSafeInteger(compressedSize) ||
123
+ Number(compressedSize) < 0 ||
124
+ !Number.isSafeInteger(uncompressedSize) ||
125
+ Number(uncompressedSize) <= 0
126
+ ) {
127
+ throw ERRORS.TypedError(
128
+ HardwareErrorCode.RuntimeError,
129
+ `Protocol V2 local resource ZIP entry size is invalid: ${entry.name}`,
130
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
131
+ );
132
+ }
133
+ return {
134
+ compressedSize: Number(compressedSize),
135
+ uncompressedSize: Number(uncompressedSize),
136
+ };
137
+ };
138
+
139
+ export function assertProtocolV2FirmwareTargetsSupported(
140
+ deviceType: EDeviceType | string | undefined,
141
+ params: FirmwareUpdateV4Params,
142
+ hasExplicitTargetSelection = false
143
+ ) {
144
+ const requestedTargets = new Set(params.targetsToUpdate ?? []);
145
+ const unsupportedTargets = new Set(
146
+ Array.from(requestedTargets).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
147
+ );
148
+ if (params.se03Binary && (!hasExplicitTargetSelection || requestedTargets.has('se03'))) {
149
+ unsupportedTargets.add('se03');
150
+ }
151
+ if (params.se04Binary && (!hasExplicitTargetSelection || requestedTargets.has('se04'))) {
152
+ unsupportedTargets.add('se04');
153
+ }
154
+ if (
155
+ params.componentArtifacts?.se03 &&
156
+ (!hasExplicitTargetSelection || requestedTargets.has('se03'))
157
+ ) {
158
+ unsupportedTargets.add('se03');
159
+ }
160
+ if (
161
+ params.componentArtifacts?.se04 &&
162
+ (!hasExplicitTargetSelection || requestedTargets.has('se04'))
163
+ ) {
164
+ unsupportedTargets.add('se04');
165
+ }
166
+
167
+ if (!unsupportedTargets.size || deviceType === EDeviceType.Pro2) return;
168
+
169
+ const targetList = Array.from(unsupportedTargets).join(', ');
170
+ const message =
171
+ deviceType === EDeviceType.Neo
172
+ ? `Neo only supports SE01 and SE02; unsupported firmware targets: ${targetList}`
173
+ : `Cannot safely update ${targetList} without a confirmed Pro2 device type`;
174
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotSupportMethod, message);
175
+ }
67
176
 
68
177
  const getProtocolV2DeviceTransferProgress = (
69
178
  bytesBeforeChunk: number,
@@ -82,11 +191,6 @@ const getProtocolV2DeviceTransferProgress = (
82
191
  return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
83
192
  };
84
193
 
85
- const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
86
- const safeElapsedMs = Math.max(elapsedMs, 1);
87
- return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
88
- };
89
-
90
194
  type ProtocolV2FirmwareUpdateStatusTarget = {
91
195
  target_id: number | string;
92
196
  status?: number | string;
@@ -94,46 +198,62 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
94
198
  path?: string;
95
199
  };
96
200
 
97
- type ProtocolV2FirmwareUpdateStartResponse =
98
- | TypedResponseMessage<'Success'>
99
- | TypedResponseMessage<'DeviceFirmwareUpdateStatus'>
100
- | undefined;
201
+ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
101
202
 
102
203
  type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
103
204
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
104
205
  kind: ProtocolV2RemoteComponentTarget['kind'];
105
206
  };
106
- type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
107
- path: string;
207
+
208
+ type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
209
+ binary: ArrayBuffer;
210
+ };
211
+
212
+ type ProtocolV2RemoteComponentTarget = {
213
+ fileName: string;
214
+ targetId: number;
215
+ kind: 'bootloader' | 'firmware';
108
216
  };
109
217
 
110
- type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
111
- filePath: string;
112
- processedSize?: number;
113
- totalSize?: number;
114
- onTransferredBytes?: (transferredBytes: number) => void;
218
+ type ProtocolV2InstallSource = {
219
+ fileName: string;
220
+ source: FirmwareByteSource;
221
+ targetId: number;
222
+ kind: ProtocolV2RemoteComponentTarget['kind'];
115
223
  };
116
224
 
117
- /** RESC bundle okpkg(FileWrite 直写模式),每个独立同步到 devicePath */
118
- type ProtocolV2ResourceBundleBinary = {
225
+ type ProtocolV2ResourceBundleSource = {
119
226
  name: string;
120
- binary: ArrayBuffer;
227
+ source: FirmwareByteSource;
121
228
  devicePath: string;
122
- /** 远端配置模式下的下载 URL(手动模式不填) */
123
- url?: string;
124
229
  version?: IVersionArray;
125
230
  payloadHash?: string;
126
231
  headerHash?: string;
127
232
  };
128
233
 
129
- type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
234
+ type ProtocolV2LocalResourceArchive = {
130
235
  binary: ArrayBuffer;
236
+ materializedEntries: FirmwareMemoryArtifactEntry[];
131
237
  };
132
238
 
133
- type ProtocolV2RemoteComponentTarget = {
134
- fileName: string;
135
- targetId: number;
136
- kind: 'bootloader' | 'firmware';
239
+ type JSZipSizedEntry = JSZip.JSZipObject & {
240
+ _data?: {
241
+ compressedSize?: unknown;
242
+ uncompressedSize?: unknown;
243
+ };
244
+ };
245
+
246
+ type ProtocolV2ExecutionPhaseKind =
247
+ | 'resource-sync'
248
+ | 'bootloader-install'
249
+ | 'bootloader-verify'
250
+ | 'component-install'
251
+ | 'final-verify';
252
+
253
+ type ProtocolV2ExecutionPhase = {
254
+ kind: ProtocolV2ExecutionPhaseKind;
255
+ installSources: ProtocolV2InstallSource[];
256
+ resourceSources: ProtocolV2ResourceBundleSource[];
137
257
  };
138
258
 
139
259
  type ProtocolV2OkppHeader = {
@@ -188,11 +308,57 @@ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
188
308
  },
189
309
  };
190
310
 
311
+ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
312
+ Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(
313
+ target => `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${target.fileName}`
314
+ )
315
+ );
316
+
317
+ const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH = 'vol0:/loaders/bootloader/boot_resource.okpkg';
318
+ const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH = `${PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH}.staging`;
319
+
320
+ const isProtocolV2BootResourcePackagePath = (devicePath: string) =>
321
+ typeof devicePath === 'string' &&
322
+ devicePath.replace(/^vol0:(?!\/)/i, 'vol0:/').toLowerCase() ===
323
+ PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH;
324
+
325
+ const resolveProtocolV2ResourceWritePath = (devicePath: string) =>
326
+ isProtocolV2BootResourcePackagePath(devicePath)
327
+ ? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH
328
+ : devicePath;
329
+
330
+ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
331
+ number,
332
+ Exclude<FirmwareUpdateV4Target, 'boot_resources'>
333
+ >([
334
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
335
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
336
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
337
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR, 'coprocessor'],
338
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, 'se01'],
339
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, 'se02'],
340
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, 'se03'],
341
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
342
+ ]);
343
+
344
+ const PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET = new Map<
345
+ Exclude<FirmwareUpdateV4Target, 'resource'>,
346
+ ProtocolV2RemoteComponentTarget
347
+ >(
348
+ Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(target => [
349
+ PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId) as Exclude<
350
+ FirmwareUpdateV4Target,
351
+ 'resource'
352
+ >,
353
+ target,
354
+ ])
355
+ );
356
+
191
357
  const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
192
358
  'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
193
359
 
194
- // hd-transport 的历史 decode 行为会把单值 enum 输出为枚举名字符串;
195
- // Protocol V2 沿用这个 SDK 语义,内部比较前再映射回固件协议数值。
360
+ // hd-transport historically decodes scalar enums as enum-name strings.
361
+ // Map them back to firmware protocol values before internal comparisons.
196
362
  const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
197
363
  Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
198
364
  );
@@ -218,27 +384,20 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
218
384
  );
219
385
  };
220
386
 
221
- const isProtocolV2PollingTransientError = (error: unknown) => {
387
+ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
222
388
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
223
389
  return (
224
- isProtocolV2DeviceDisconnectedError(error) ||
225
- isProtocolV2ReconnectProbeError(error) ||
226
- message.includes('libusb_transfer_timed_out') ||
227
- (message.includes('response timeout') && message.includes('devicefirmwareupdatestatusget')) ||
228
- message.includes('device not found') ||
229
- message.includes('transportnotfound')
390
+ message.includes('handler not registered') ||
391
+ message.includes('message handler not found') ||
392
+ message.includes('unsupported message')
230
393
  );
231
394
  };
232
395
 
233
- const isProtocolV2StartUpdateTransientError = (error: unknown) => {
234
- const message = getProtocolV2UnknownErrorText(error).toLowerCase();
235
- return (
236
- isProtocolV2DeviceDisconnectedError(error) ||
237
- isProtocolV2ReconnectProbeError(error) ||
238
- message.includes('libusb_transfer_timed_out') ||
239
- (message.includes('response timeout') && message.includes('devicefirmwareupdaterequest'))
240
- );
241
- };
396
+ const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
397
+ error instanceof HardwareError &&
398
+ (error.errorCode === HardwareErrorCode.FirmwareError ||
399
+ error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
400
+ error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
242
401
 
243
402
  const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
244
403
  normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
@@ -282,13 +441,6 @@ const versionArrayToNumber = (version?: IVersionArray) => {
282
441
  return version[0] * 0x10000 + version[1] * 0x100 + version[2];
283
442
  };
284
443
 
285
- const versionStringToArray = (version?: string | null): IVersionArray | undefined => {
286
- if (!version) return undefined;
287
- const parts = version.split('.').map(part => Number(part));
288
- if (parts.length !== 3 || parts.some(part => !Number.isFinite(part))) return undefined;
289
- return parts as IVersionArray;
290
- };
291
-
292
444
  const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
293
445
  const currentNumber = versionArrayToNumber(current);
294
446
  const targetNumber = versionArrayToNumber(target);
@@ -375,6 +527,43 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
375
527
  };
376
528
  };
377
529
 
530
+ export const isProtocolV2FirmwareFingerprintValid = (
531
+ binary: ArrayBuffer | Uint8Array,
532
+ fingerprint: string | undefined
533
+ ) => {
534
+ const expectedFingerprint = normalizeProtocolV2Hex(fingerprint);
535
+ if (!expectedFingerprint) return true;
536
+ return bytesToHex(sha256(toProtocolV2Bytes(binary))) === expectedFingerprint;
537
+ };
538
+
539
+ export const assertProtocolV2ReconnectIdentity = (
540
+ expectedSerialNumber?: string,
541
+ actualSerialNumber?: string,
542
+ expectedPath?: string,
543
+ actualPath?: string
544
+ ) => {
545
+ if (expectedSerialNumber && actualSerialNumber) {
546
+ if (actualSerialNumber !== expectedSerialNumber) {
547
+ throw ERRORS.TypedError(
548
+ HardwareErrorCode.DeviceNotFound,
549
+ `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
550
+ );
551
+ }
552
+ return;
553
+ }
554
+
555
+ if (expectedPath && actualPath && expectedPath === actualPath) {
556
+ return;
557
+ }
558
+
559
+ throw ERRORS.TypedError(
560
+ HardwareErrorCode.DeviceNotFound,
561
+ `Protocol V2 reconnect physical identity unavailable: expected path ${
562
+ expectedPath ?? 'unknown'
563
+ }, received ${actualPath ?? 'unknown'}`
564
+ );
565
+ };
566
+
378
567
  /**
379
568
  * FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
380
569
  *
@@ -384,14 +573,57 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
384
573
  * - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
385
574
  */
386
575
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
576
+ private protocolV2ExpectedSerialNumber?: string;
577
+
578
+ private protocolV2ExpectedPath?: string;
579
+
580
+ private protocolV2HasExplicitTargetSelection = false;
581
+
582
+ getSupportedProtocols() {
583
+ return ['V2'] as const;
584
+ }
585
+
586
+ private protocolV2PreparedSources: FirmwareByteSource[] = [];
587
+
588
+ private protocolV2ExecutionInLoader = false;
589
+
590
+ private protocolV2BootResourceStagingSafe = false;
591
+
592
+ private protocolV2CompletedTargetVersions = new Map<number, number>();
593
+
594
+ private protocolV2LatestFinalFeatures?: Features;
595
+
596
+ private protocolV2FinalStatusVerified = false;
597
+
387
598
  init() {
388
599
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
389
600
  this.requireDeviceMode = [];
601
+ this.unlockPolicy = 'unlock-before-run';
390
602
  this.useDevicePassphraseState = false;
391
603
  this.skipForceUpdateCheck = true;
392
604
 
393
605
  const { payload } = this;
394
606
 
607
+ const removedResourceInputs = ['resourceFiles', 'resourceBundleArtifacts'].filter(input =>
608
+ Object.prototype.hasOwnProperty.call(payload, input)
609
+ );
610
+ if (removedResourceInputs.length > 0) {
611
+ throw ERRORS.TypedError(
612
+ HardwareErrorCode.CallMethodInvalidParameter,
613
+ `Protocol V2 ${removedResourceInputs.join(
614
+ ' and '
615
+ )} inputs are no longer supported; use resourceArchiveBinary for local updates or preparedPlan with hostBindingGeneration for remote updates`
616
+ );
617
+ }
618
+ if (payload.hostBindingGeneration !== undefined && !payload.preparedPlan) {
619
+ throw ERRORS.TypedError(
620
+ HardwareErrorCode.CallMethodInvalidParameter,
621
+ 'Protocol V2 hostBindingGeneration requires preparedPlan for remote updates; use direct binaries or resourceArchiveBinary for local updates'
622
+ );
623
+ }
624
+
625
+ this.protocolV2HasExplicitTargetSelection = payload.targetsToUpdate !== undefined;
626
+
395
627
  if (typeof payload.retryCount !== 'number') {
396
628
  payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
397
629
  }
@@ -417,12 +649,112 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
417
649
  { name: 'se02Binary', type: 'buffer' },
418
650
  { name: 'se03Binary', type: 'buffer' },
419
651
  { name: 'se04Binary', type: 'buffer' },
652
+ { name: 'resourceArchiveBinary', type: 'buffer' },
420
653
  { name: 'firmwareType', type: 'string' },
654
+ { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
421
655
  { name: 'platform', type: 'string' },
422
- { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
656
+ { name: 'expectedDeviceId', type: 'string' },
423
657
  ]);
658
+ if (
659
+ payload.expectedDeviceId !== undefined &&
660
+ (payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
661
+ ) {
662
+ throw ERRORS.TypedError(
663
+ HardwareErrorCode.CallMethodInvalidParameter,
664
+ 'Protocol V2 expected device identity is invalid'
665
+ );
666
+ }
667
+ const preparedPlan = payload.preparedPlan
668
+ ? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
669
+ : undefined;
670
+ const hasLocalArtifacts = [
671
+ payload.bootloaderBinary,
672
+ payload.romloaderBinary,
673
+ payload.applicationP1Binary,
674
+ payload.applicationP2Binary,
675
+ payload.coprocessorBinary,
676
+ payload.se01Binary,
677
+ payload.se02Binary,
678
+ payload.se03Binary,
679
+ payload.se04Binary,
680
+ payload.resourceArchiveBinary,
681
+ ].some(Boolean);
682
+ if (preparedPlan && hasLocalArtifacts) {
683
+ throw ERRORS.TypedError(
684
+ HardwareErrorCode.CallMethodInvalidParameter,
685
+ 'Prepared firmware plans cannot be combined with legacy or local firmware inputs'
686
+ );
687
+ }
688
+ let { artifactReader } = payload;
689
+ if (preparedPlan) {
690
+ artifactReader = resolveFirmwareUpdateHostBinding(
691
+ payload.hostBindingGeneration,
692
+ preparedPlan.preparedPlanDigest
693
+ ).artifactReader;
694
+ }
695
+ if (preparedPlan) {
696
+ assertFirmwareUpdatePreparedPlanBinding({
697
+ preparedPlan,
698
+ executor: 'v4',
699
+ platform: payload.platform,
700
+ scopeTargets: [],
701
+ bindings: [],
702
+ });
703
+ if (payload.componentArtifacts) {
704
+ const componentBindings: Array<{
705
+ target: Exclude<FirmwareUpdateV4Target, 'resource'>;
706
+ artifact: FirmwareArtifactReference;
707
+ }> = Object.entries(payload.componentArtifacts).flatMap(([target, artifact]) =>
708
+ artifact
709
+ ? [
710
+ {
711
+ target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
712
+ artifact: artifact as FirmwareArtifactReference,
713
+ },
714
+ ]
715
+ : []
716
+ );
717
+ assertFirmwareUpdatePreparedPlanBinding({
718
+ preparedPlan,
719
+ executor: 'v4',
720
+ platform: payload.platform,
721
+ scopeTargets: componentBindings.map(binding => binding.target),
722
+ bindings: componentBindings,
723
+ });
724
+ }
725
+ }
726
+
727
+ const preparedExpectedTargetVersions = preparedPlan?.artifacts.reduce<
728
+ NonNullable<FirmwareUpdateV4Params['expectedTargetVersions']>
729
+ >((result, artifact) => {
730
+ if (
731
+ artifact.role === 'component' &&
732
+ artifact.target !== 'resource' &&
733
+ artifact.targetVersion &&
734
+ PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.has(
735
+ artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>
736
+ )
737
+ ) {
738
+ result[artifact.target as FirmwareUpdateV4Target] = artifact.targetVersion;
739
+ }
740
+ return result;
741
+ }, {});
742
+ const localTargetsToUpdate = payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
743
+ target === 'boot_resources' ? 'resource' : target
744
+ );
745
+ if (
746
+ payload.resourceArchiveBinary &&
747
+ localTargetsToUpdate &&
748
+ !localTargetsToUpdate.includes('resource')
749
+ ) {
750
+ localTargetsToUpdate.push('resource');
751
+ }
752
+ // 本地 ZIP 本身就是明确的资源升级请求,不要求调用方重复声明 target。
753
+ const resolvedLocalTargetsToUpdate =
754
+ localTargetsToUpdate ?? (payload.resourceArchiveBinary ? ['resource'] : undefined);
424
755
 
425
756
  this.params = {
757
+ preparedPlan,
426
758
  chunkSize: payload.chunkSize,
427
759
  forcedUpdateRes: payload.forcedUpdateRes,
428
760
  bootloaderBinary: payload.bootloaderBinary,
@@ -434,19 +766,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
434
766
  se02Binary: payload.se02Binary,
435
767
  se03Binary: payload.se03Binary,
436
768
  se04Binary: payload.se04Binary,
437
- resourceBundleFiles: payload.resourceBundleFiles,
438
- firmwareType: payload.firmwareType,
769
+ resourceArchiveBinary: payload.resourceArchiveBinary,
770
+ firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
771
+ targetsToUpdate: preparedPlan
772
+ ? ([...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[])
773
+ : resolvedLocalTargetsToUpdate,
774
+ expectedTargetVersions: preparedPlan
775
+ ? preparedExpectedTargetVersions
776
+ : payload.expectedTargetVersions,
439
777
  platform: payload.platform,
778
+ expectedDeviceId: payload.expectedDeviceId,
779
+ artifactReader,
780
+ componentArtifacts: preparedPlan ? undefined : payload.componentArtifacts,
440
781
  };
441
782
  }
442
783
 
443
- private getProtocolV2FirmwareChunkSize() {
784
+ private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write', filePath?: string) {
444
785
  const payloadChunkSize = Number(this.params?.chunkSize);
445
786
  const env = DataManager.getSettings('env');
446
- const maxChunkSize =
447
- this.params?.platform === 'native' || (env && DataManager.isBleConnect(env))
448
- ? PROTOCOL_V2_BLE_FILE_CHUNK_SIZE
449
- : PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
787
+ const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
788
+ let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
789
+ if (isBle) {
790
+ if (direction === 'read') {
791
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
792
+ } else {
793
+ // Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
794
+ maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
795
+ ? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
796
+ : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
797
+ }
798
+ }
450
799
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
451
800
  return maxChunkSize;
452
801
  }
@@ -457,21 +806,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
457
806
  }
458
807
 
459
808
  async run() {
460
- if (!this.device.isProtocolV2()) {
461
- throw ERRORS.TypedError(
462
- HardwareErrorCode.RuntimeError,
463
- 'firmwareUpdateV4 requires a Protocol V2 device'
464
- );
465
- }
466
-
467
809
  Log.debug('FirmwareUpdateV4 strategy: Protocol V2');
468
810
  return this.runProtocolV2();
469
811
  }
470
812
 
471
813
  private async runProtocolV2() {
814
+ await this.captureProtocolV2PhysicalIdentity();
472
815
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
816
+ const currentDeviceType = this.device.getCurrentDeviceType();
817
+ const capabilityDeviceType =
818
+ currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
819
+ ? currentDeviceType
820
+ : getDeviceType(deviceFeatures);
821
+ assertProtocolV2FirmwareTargetsSupported(
822
+ capabilityDeviceType,
823
+ this.params,
824
+ this.protocolV2HasExplicitTargetSelection
825
+ );
473
826
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
474
827
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
828
+ this.validateExpectedTargetVersions();
829
+
830
+ if (
831
+ !this.params.preparedPlan &&
832
+ this.params.resourceArchiveBinary &&
833
+ this.params.targetsToUpdate?.includes('resource')
834
+ ) {
835
+ const localMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
836
+ features: deviceFeatures,
837
+ firmwareType,
838
+ });
839
+ try {
840
+ return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
841
+ } finally {
842
+ localMemoryHost.release();
843
+ }
844
+ }
845
+
846
+ const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
847
+ Boolean
848
+ );
849
+ if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
850
+ return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
851
+ }
852
+ const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
475
853
 
476
854
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
477
855
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -480,252 +858,973 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
480
858
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
481
859
  fwBinaryMap = this.collectExplicitTargetBinaries();
482
860
  bootloaderBinary = this.prepareBootloaderBinary();
483
- if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
861
+ const explicitInstallItems = this.buildProtocolV2InstallItems({
862
+ bootloaderBinary,
863
+ fwBinaryMap,
864
+ });
865
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
866
+ const needsRemoteFirmware = this.params.targetsToUpdate?.length
867
+ ? missingFirmwareTargets.length > 0
868
+ : explicitInstallItems.length === 0;
869
+ if (wantsResources) {
870
+ throw ERRORS.TypedError(
871
+ HardwareErrorCode.RuntimeError,
872
+ 'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
873
+ {
874
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
875
+ }
876
+ );
877
+ }
878
+ if (
879
+ needsRemoteFirmware &&
880
+ (this.params.artifactReader ||
881
+ DataManager.getSettings('firmwareManifestMode') === 'external-only')
882
+ ) {
883
+ throw ERRORS.TypedError(
884
+ HardwareErrorCode.RuntimeError,
885
+ 'Protocol V2 firmware artifacts must be prepared by the external firmware host',
886
+ {
887
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
888
+ }
889
+ );
890
+ }
891
+ if (needsRemoteFirmware) {
892
+ // Remote updates must use a freshly fetched config before any reboot or file write.
893
+ await DataManager.forceReloadData();
894
+ }
895
+ if (needsRemoteFirmware) {
484
896
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
485
897
  firmwareType,
486
- deviceFeatures
898
+ deviceFeatures,
899
+ explicitInstallItems
487
900
  );
488
901
  bootloaderBinary = remoteBinaries.bootloaderBinary;
489
902
  fwBinaryMap = remoteBinaries.fwBinaryMap;
490
903
  installItems = remoteBinaries.installItems;
904
+ } else {
905
+ const selectedInstallItems = this.filterProtocolV2LocalInstallItems(explicitInstallItems);
906
+ bootloaderBinary =
907
+ selectedInstallItems.find(item => item.kind === 'bootloader')?.binary ?? null;
908
+ fwBinaryMap = selectedInstallItems
909
+ .filter(item => item.kind === 'firmware')
910
+ .map(item => ({
911
+ fileName: item.fileName,
912
+ binary: item.binary,
913
+ targetId: item.targetId,
914
+ }));
491
915
  }
492
916
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
493
917
  } catch (err) {
494
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
918
+ if (
919
+ typeof err === 'object' &&
920
+ err !== null &&
921
+ 'params' in err &&
922
+ (err as HardwareError).params?.firmwareUpdateCode === 'FirmwareArtifactsNotPrepared'
923
+ ) {
924
+ throw err;
925
+ }
926
+ if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
927
+ throw err;
928
+ }
929
+ throw normalizeFirmwarePreparationError(err);
495
930
  }
496
931
 
497
- const resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
498
-
499
- if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
932
+ if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
500
933
  throw ERRORS.TypedError(
501
934
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
502
935
  'No firmware to update'
503
936
  );
504
937
  }
505
938
 
506
- await this.enterProtocolV2BootloaderMode();
507
-
508
- await this.executeProtocolV2Update({
939
+ return this.executeProtocolV2Update({
509
940
  fwBinaryMap,
510
941
  bootloaderBinary,
511
942
  ...(installItems ? { installItems } : undefined),
512
- ...(resourceBundles?.length ? { resourceBundles } : undefined),
513
943
  });
514
-
515
- await this.exitProtocolV2BootloaderToNormal();
516
-
517
- const versions = await this.waitForProtocolV2FinalFeatures();
518
- this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
519
- DevicePool.resetState();
520
-
521
- return versions;
522
944
  }
523
945
 
524
- private async getProtocolV2DeviceFeatures(): Promise<Features> {
525
- if (this.device.features) {
526
- return this.device.features;
527
- }
528
- if (typeof this.device.getFeatures === 'function') {
529
- const features = await this.device.getFeatures();
530
- if (features) return features;
946
+ private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
947
+ const source = await openFirmwareByteSource({
948
+ artifact,
949
+ reader: this.params.artifactReader,
950
+ });
951
+ if (!source) {
952
+ throw ERRORS.TypedError(
953
+ HardwareErrorCode.RuntimeError,
954
+ `Firmware artifact ${artifact.artifactRef} is not prepared`,
955
+ {
956
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
957
+ }
958
+ );
531
959
  }
532
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
960
+ this.protocolV2PreparedSources.push(source);
961
+ return source;
533
962
  }
534
963
 
535
- private prepareBootloaderBinary(): ArrayBuffer | null {
536
- return this.params.bootloaderBinary ?? null;
964
+ private async openProtocolV2MemorySource(binary: ArrayBuffer) {
965
+ const source = await openFirmwareByteSource({ binary });
966
+ if (!source) {
967
+ throw ERRORS.TypedError(
968
+ HardwareErrorCode.RuntimeError,
969
+ 'Protocol V2 firmware binary is empty'
970
+ );
971
+ }
972
+ this.protocolV2PreparedSources.push(source);
973
+ return source;
537
974
  }
538
975
 
539
- private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
540
- return (
541
- !!this.params.resourceBundleFiles?.length ||
542
- !!this.params.bootloaderBinary ||
543
- fwBinaryMap.length > 0
544
- );
976
+ private async closeProtocolV2PreparedSources() {
977
+ const sources = this.protocolV2PreparedSources.splice(0);
978
+ await Promise.all(sources.map(source => source.close().catch(() => undefined)));
545
979
  }
546
980
 
547
- private buildProtocolV2InstallItems({
548
- bootloaderBinary,
549
- fwBinaryMap,
550
- }: {
551
- bootloaderBinary: ArrayBuffer | null;
552
- fwBinaryMap: ProtocolV2TargetBinary[];
553
- }): ProtocolV2InstallItem[] {
554
- const installItems: ProtocolV2InstallItem[] = [];
555
-
556
- if (bootloaderBinary) {
557
- installItems.push({
558
- fileName: 'bootloader.bin',
559
- binary: bootloaderBinary,
560
- targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
561
- kind: 'bootloader',
562
- });
981
+ private async prepareProtocolV2InstallSources(
982
+ firmwareType: EFirmwareType,
983
+ features: Features
984
+ ): Promise<ProtocolV2InstallSource[]> {
985
+ if (this.params.preparedPlan) {
986
+ const requestedTargets = new Set(
987
+ this.params.preparedPlan.targetsToUpdate.filter(
988
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource'> => target !== 'resource'
989
+ )
990
+ );
991
+ const installSources: ProtocolV2InstallSource[] = [];
992
+ const preparedTargets = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>();
993
+ for (const artifact of this.params.preparedPlan.artifacts) {
994
+ if (artifact.target !== 'resource') {
995
+ const target = artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>;
996
+ const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(target);
997
+ if (
998
+ !requestedTargets.has(target) ||
999
+ artifact.role !== 'component' ||
1000
+ artifact.container !== 'raw' ||
1001
+ !installTarget ||
1002
+ preparedTargets.has(target)
1003
+ ) {
1004
+ throw ERRORS.TypedError(
1005
+ HardwareErrorCode.RuntimeError,
1006
+ `Protocol V2 prepared component artifact is invalid: ${artifact.artifactId}`,
1007
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1008
+ );
1009
+ }
1010
+ installSources.push({
1011
+ ...installTarget,
1012
+ source: await this.openProtocolV2PreparedSource(artifact.artifact),
1013
+ });
1014
+ preparedTargets.add(target);
1015
+ }
1016
+ }
1017
+ const missingTarget = Array.from(requestedTargets).find(
1018
+ target => !preparedTargets.has(target)
1019
+ );
1020
+ if (missingTarget) {
1021
+ throw ERRORS.TypedError(
1022
+ HardwareErrorCode.RuntimeError,
1023
+ `Protocol V2 ${missingTarget} artifact is not prepared`,
1024
+ {
1025
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1026
+ artifactName: missingTarget,
1027
+ }
1028
+ );
1029
+ }
1030
+ return installSources;
563
1031
  }
564
1032
 
565
- installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
566
- return installItems;
567
- }
568
-
569
- private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
570
- const { components } = release;
571
- if (!components) return [];
572
-
573
- const orderedKeys = [
574
- ...(release.installOrder ?? []),
575
- ...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
576
- ];
577
-
578
- return orderedKeys
579
- .map(key => {
580
- const component = components[key];
581
- return component ? ([key, component] as const) : undefined;
582
- })
583
- .filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
584
- }
585
-
586
- private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
587
- const targetName = component.target?.toUpperCase();
588
- if (targetName === 'ROMLOADER') {
1033
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
1034
+ if (!release) {
589
1035
  throw ERRORS.TypedError(
590
1036
  HardwareErrorCode.RuntimeError,
591
- PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
1037
+ 'Protocol V2 firmware release is unavailable'
592
1038
  );
593
1039
  }
594
- const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
595
- if (!target) {
1040
+ const componentArtifacts = this.params.componentArtifacts ?? {};
1041
+ const requestedTargets = new Set(
1042
+ this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
1043
+ );
1044
+ const installSources: ProtocolV2InstallSource[] = [];
1045
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
1046
+
1047
+ for (const [key, component] of this.getRemoteComponentEntries(release)) {
1048
+ const target = this.getRemoteComponentTarget(key, component);
1049
+ const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
1050
+ if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
1051
+ const artifact = componentArtifacts[updateTarget];
1052
+ if (!artifact) {
1053
+ throw ERRORS.TypedError(
1054
+ HardwareErrorCode.RuntimeError,
1055
+ `Protocol V2 ${updateTarget} artifact is not prepared`,
1056
+ {
1057
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1058
+ artifactName: updateTarget,
1059
+ }
1060
+ );
1061
+ }
1062
+ installSources.push({
1063
+ ...target,
1064
+ source: await this.openProtocolV2PreparedSource(artifact),
1065
+ });
1066
+ preparedTargets.add(updateTarget);
1067
+ }
1068
+ }
1069
+
1070
+ const unknownTarget = Array.from(requestedTargets).find(
1071
+ target => target !== 'resource' && !preparedTargets.has(target)
1072
+ );
1073
+ if (unknownTarget) {
596
1074
  throw ERRORS.TypedError(
597
1075
  HardwareErrorCode.RuntimeError,
598
- `Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
1076
+ `Protocol V2 release does not contain requested target ${unknownTarget}`
599
1077
  );
600
1078
  }
601
- return target;
1079
+ return installSources;
602
1080
  }
603
1081
 
604
- private getProtocolV2ComponentTargetVersion(
605
- release: IFirmwareReleaseInfo,
606
- component: IProtocolV2FirmwareComponent,
607
- target: ProtocolV2RemoteComponentTarget
608
- ) {
609
- if (component.version) return component.version;
610
- if (target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER) {
611
- return release.bootloaderVersion;
612
- }
613
- if (
614
- target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1 ||
615
- target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
616
- ) {
617
- return release.version;
1082
+ private async prepareProtocolV2LocalMemoryHost({
1083
+ features,
1084
+ firmwareType,
1085
+ }: {
1086
+ features: Features;
1087
+ firmwareType: EFirmwareType;
1088
+ }): Promise<FirmwareUpdateV4MemoryHost> {
1089
+ const availableInstallItems = this.buildProtocolV2InstallItems({
1090
+ bootloaderBinary: this.prepareBootloaderBinary(),
1091
+ fwBinaryMap: this.collectExplicitTargetBinaries(),
1092
+ });
1093
+ const requestedComponentTargets = new Set(
1094
+ (this.params.targetsToUpdate ?? []).filter(
1095
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
1096
+ target !== 'resource' && target !== 'boot_resources'
1097
+ )
1098
+ );
1099
+ const localComponentTargets = new Set(
1100
+ availableInstallItems.flatMap(item => {
1101
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1102
+ return target ? [target] : [];
1103
+ })
1104
+ );
1105
+ const missingTarget = Array.from(requestedComponentTargets).find(
1106
+ target => !localComponentTargets.has(target)
1107
+ );
1108
+ if (missingTarget) {
1109
+ throw ERRORS.TypedError(
1110
+ HardwareErrorCode.RuntimeError,
1111
+ `Protocol V2 local update has no binary for requested target ${missingTarget}`,
1112
+ {
1113
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1114
+ artifactName: missingTarget,
1115
+ }
1116
+ );
1117
+ }
1118
+ const installItems = this.filterProtocolV2LocalInstallItems(availableInstallItems);
1119
+
1120
+ const planArtifacts: Parameters<typeof buildProtocolV2LocalFirmwareUpdatePlan>[0]['artifacts'] =
1121
+ [];
1122
+ const memoryArtifacts: FirmwareMemoryArtifact[] = [];
1123
+ for (const item of installItems) {
1124
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1125
+ if (!target || item.binary.byteLength <= 0) {
1126
+ throw ERRORS.TypedError(
1127
+ HardwareErrorCode.RuntimeError,
1128
+ `Protocol V2 local firmware artifact is invalid: ${item.fileName}`,
1129
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1130
+ );
1131
+ }
1132
+ const artifactId = `component:${target}`;
1133
+ planArtifacts.push({
1134
+ artifactId,
1135
+ target,
1136
+ container: 'raw',
1137
+ logicalName: item.fileName,
1138
+ expectedSize: item.binary.byteLength,
1139
+ expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
1140
+ ...(this.params.expectedTargetVersions?.[target]
1141
+ ? { targetVersion: this.params.expectedTargetVersions[target] }
1142
+ : {}),
1143
+ });
1144
+ memoryArtifacts.push({ artifactId, binary: item.binary });
1145
+ }
1146
+
1147
+ const resourceArchive = await this.prepareProtocolV2LocalResourceArchive(
1148
+ this.params.resourceArchiveBinary as ArrayBuffer
1149
+ );
1150
+ const resourceArtifactId = 'resource:archive';
1151
+ planArtifacts.push({
1152
+ artifactId: resourceArtifactId,
1153
+ target: 'resource',
1154
+ container: 'zip',
1155
+ logicalName: 'protocol-v2-local-resource-archive',
1156
+ expectedSize: resourceArchive.binary.byteLength,
1157
+ expectedSha256: bytesToHex(sha256(new Uint8Array(resourceArchive.binary))),
1158
+ });
1159
+ memoryArtifacts.push({
1160
+ artifactId: resourceArtifactId,
1161
+ binary: resourceArchive.binary,
1162
+ materializedEntries: resourceArchive.materializedEntries,
1163
+ });
1164
+
1165
+ const plan = buildProtocolV2LocalFirmwareUpdatePlan({
1166
+ features,
1167
+ firmwareType,
1168
+ platform: this.params.platform,
1169
+ artifacts: planArtifacts,
1170
+ });
1171
+ let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
1172
+ try {
1173
+ memoryHost = prepareFirmwareUpdateV4MemoryHost({
1174
+ sdk: {
1175
+ prepareFirmwareUpdatePlan,
1176
+ registerFirmwareUpdateHostBinding,
1177
+ unregisterFirmwareUpdateHostBinding,
1178
+ },
1179
+ plan,
1180
+ artifacts: memoryArtifacts,
1181
+ });
1182
+ const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
1183
+ assertFirmwareUpdatePreparedPlanBinding({
1184
+ preparedPlan,
1185
+ executor: 'v4',
1186
+ platform: this.params.platform,
1187
+ scopeTargets: [],
1188
+ bindings: [],
1189
+ });
1190
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
1191
+ preparedPlan,
1192
+ deviceIdentity: this.protocolV2ExpectedSerialNumber,
1193
+ deviceModel: this.getProtocolV2PreparedPlanDeviceModel(features),
1194
+ });
1195
+ const hostBinding = resolveFirmwareUpdateHostBinding(
1196
+ memoryHost.hostBindingGeneration,
1197
+ preparedPlan.preparedPlanDigest
1198
+ );
1199
+ this.params.preparedPlan = preparedPlan;
1200
+ this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
1201
+ this.params.artifactReader = hostBinding.artifactReader;
1202
+ this.params.componentArtifacts = undefined;
1203
+ return memoryHost;
1204
+ } catch (error) {
1205
+ memoryHost?.release();
1206
+ throw error;
618
1207
  }
619
- return undefined;
620
1208
  }
621
1209
 
622
- private getProtocolV2ComponentCurrentVersion(
623
- features: Features,
624
- target: ProtocolV2RemoteComponentTarget
625
- ) {
626
- switch (target.targetId) {
627
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER:
628
- return getDeviceBootloaderVersion(features);
629
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1:
630
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2:
631
- return getDeviceFirmwareVersion(features);
632
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR:
633
- return getDeviceBLEFirmwareVersion(features);
634
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01:
635
- return versionStringToArray(features.se01Version);
636
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02:
637
- return versionStringToArray(features.se02Version);
638
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03:
639
- return versionStringToArray(features.se03Version);
640
- case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04:
641
- return versionStringToArray(features.se04Version);
642
- default:
643
- return undefined;
644
- }
645
- }
646
-
647
- private isProtocolV2ComponentVersionSatisfied(
648
- release: IFirmwareReleaseInfo,
649
- component: IProtocolV2FirmwareComponent,
650
- target: ProtocolV2RemoteComponentTarget,
651
- features: Features
652
- ) {
653
- const targetVersion = this.getProtocolV2ComponentTargetVersion(release, component, target);
654
- if (!targetVersion) return false;
1210
+ private async prepareProtocolV2LocalResourceArchive(
1211
+ binary: ArrayBuffer
1212
+ ): Promise<ProtocolV2LocalResourceArchive> {
1213
+ if (binary.byteLength <= 0 || binary.byteLength > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1214
+ throw ERRORS.TypedError(
1215
+ HardwareErrorCode.RuntimeError,
1216
+ 'Protocol V2 local resource ZIP archive size is invalid',
1217
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1218
+ );
1219
+ }
1220
+ let zip: JSZip;
1221
+ try {
1222
+ zip = await JSZip.loadAsync(binary);
1223
+ } catch {
1224
+ throw ERRORS.TypedError(
1225
+ HardwareErrorCode.RuntimeError,
1226
+ 'Protocol V2 resource ZIP cannot be parsed',
1227
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1228
+ );
1229
+ }
1230
+ const zipEntries = Object.values(zip.files);
1231
+ if (
1232
+ zipEntries.some(entry => entry.unsafeOriginalName && entry.unsafeOriginalName !== entry.name)
1233
+ ) {
1234
+ throw ERRORS.TypedError(
1235
+ HardwareErrorCode.RuntimeError,
1236
+ 'Protocol V2 local resource ZIP contains an unsafe entry path',
1237
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1238
+ );
1239
+ }
1240
+ const entries = zipEntries.filter(entry => !entry.dir);
1241
+ if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
1242
+ throw ERRORS.TypedError(
1243
+ HardwareErrorCode.RuntimeError,
1244
+ 'Protocol V2 local resource ZIP entry set is invalid',
1245
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1246
+ );
1247
+ }
1248
+ let declaredUncompressedSize = 0;
1249
+ let declaredCompressedSize = 0;
1250
+ for (const entry of entries) {
1251
+ const sizes = getProtocolV2ZipEntrySizes(entry);
1252
+ declaredCompressedSize += sizes.compressedSize;
1253
+ declaredUncompressedSize += sizes.uncompressedSize;
1254
+ const entryLimit =
1255
+ entry.name === 'manifest.json'
1256
+ ? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1257
+ : PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
1258
+ if (
1259
+ sizes.uncompressedSize > entryLimit ||
1260
+ declaredCompressedSize > binary.byteLength ||
1261
+ declaredUncompressedSize >
1262
+ PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1263
+ ) {
1264
+ throw ERRORS.TypedError(
1265
+ HardwareErrorCode.RuntimeError,
1266
+ 'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
1267
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1268
+ );
1269
+ }
1270
+ }
1271
+ const manifestEntry = zip.file('manifest.json');
1272
+ if (!manifestEntry) {
1273
+ throw ERRORS.TypedError(
1274
+ HardwareErrorCode.RuntimeError,
1275
+ 'Protocol V2 local resource ZIP has no manifest.json',
1276
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1277
+ );
1278
+ }
1279
+ const manifestBinary = await manifestEntry.async('arraybuffer');
1280
+ if (
1281
+ manifestBinary.byteLength <= 0 ||
1282
+ manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1283
+ ) {
1284
+ throw ERRORS.TypedError(
1285
+ HardwareErrorCode.RuntimeError,
1286
+ 'Protocol V2 local resource manifest size is invalid',
1287
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1288
+ );
1289
+ }
655
1290
 
656
- const currentVersion = this.getProtocolV2ComponentCurrentVersion(features, target);
657
- const compareResult = compareProtocolV2Versions(currentVersion, targetVersion);
658
- return compareResult !== undefined && compareResult >= 0;
1291
+ let manifestValue: unknown;
1292
+ try {
1293
+ manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
1294
+ } catch (error) {
1295
+ throw ERRORS.TypedError(
1296
+ HardwareErrorCode.RuntimeError,
1297
+ `Protocol V2 local resource manifest is invalid: ${String(error)}`,
1298
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1299
+ );
1300
+ }
1301
+ const manifest = parseProtocolV2ResourceManifest(manifestValue);
1302
+ const selectedFiles = selectProtocolV2ResourceManifestFiles({
1303
+ manifest,
1304
+ targetsToUpdate: this.params.targetsToUpdate ?? [],
1305
+ });
1306
+ const expectedEntryNames = new Set([
1307
+ 'manifest.json',
1308
+ ...selectedFiles.map(file => file.archive_path),
1309
+ ]);
1310
+ if (
1311
+ entries.length !== expectedEntryNames.size ||
1312
+ entries.some(entry => !expectedEntryNames.has(entry.name))
1313
+ ) {
1314
+ throw ERRORS.TypedError(
1315
+ HardwareErrorCode.RuntimeError,
1316
+ 'Protocol V2 local resource ZIP contains an unexpected entry',
1317
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1318
+ );
1319
+ }
1320
+
1321
+ let totalSize = 0;
1322
+ const materializedEntries: FirmwareMemoryArtifactEntry[] = [
1323
+ { entryName: 'manifest.json', binary: manifestBinary },
1324
+ ];
1325
+ for (const file of selectedFiles) {
1326
+ const entry = zip.file(file.archive_path);
1327
+ if (!entry) {
1328
+ throw ERRORS.TypedError(
1329
+ HardwareErrorCode.RuntimeError,
1330
+ `Protocol V2 local resource ZIP is missing ${file.archive_path}`,
1331
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1332
+ );
1333
+ }
1334
+ const { uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
1335
+ totalSize += uncompressedSize;
1336
+ if (uncompressedSize !== file.size || totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1337
+ throw ERRORS.TypedError(
1338
+ HardwareErrorCode.RuntimeError,
1339
+ `Protocol V2 local resource file declared size is invalid: ${file.archive_path}`,
1340
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1341
+ );
1342
+ }
1343
+ const fileBinary = await entry.async('arraybuffer');
1344
+ const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
1345
+ if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
1346
+ throw ERRORS.TypedError(
1347
+ HardwareErrorCode.RuntimeError,
1348
+ `Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
1349
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1350
+ );
1351
+ }
1352
+ materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
1353
+ }
1354
+ return { binary, materializedEntries };
659
1355
  }
660
1356
 
661
- private getProtocolV2ResourceFilePath(path: string) {
662
- if (path.startsWith('vol')) return path;
663
- if (path.startsWith('/')) return `vol0:${path}`;
664
- return `vol0:/${path}`;
1357
+ private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
1358
+ const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
1359
+ if (!resourceRequested) {
1360
+ return [];
1361
+ }
1362
+ const archiveSources = await this.prepareProtocolV2ResourceArchiveSources();
1363
+ if (archiveSources) {
1364
+ return archiveSources;
1365
+ }
1366
+ throw ERRORS.TypedError(
1367
+ HardwareErrorCode.RuntimeError,
1368
+ 'Protocol V2 resource archive is not prepared',
1369
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1370
+ );
665
1371
  }
666
1372
 
667
- private async readProtocolV2DeviceFileHeader(path: string) {
668
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
669
- const filePath = this.getProtocolV2ResourceFilePath(path);
670
- const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
671
- path: filePath,
672
- });
673
- const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1373
+ private async prepareProtocolV2ResourceArchiveSources(): Promise<
1374
+ ProtocolV2ResourceBundleSource[] | undefined
1375
+ > {
1376
+ const archiveArtifacts =
1377
+ this.params.preparedPlan?.artifacts.filter(
1378
+ artifact =>
1379
+ artifact.role === 'resourceBundle' &&
1380
+ artifact.target === 'resource' &&
1381
+ artifact.container === 'zip'
1382
+ ) ?? [];
1383
+ if (archiveArtifacts.length === 0) {
1384
+ return undefined;
1385
+ }
1386
+ if (archiveArtifacts.length !== 1) {
1387
+ throw ERRORS.TypedError(
1388
+ HardwareErrorCode.RuntimeError,
1389
+ 'Protocol V2 prepared plan must contain exactly one resource archive',
1390
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1391
+ );
1392
+ }
1393
+
1394
+ const archiveArtifact = archiveArtifacts[0];
1395
+ const archiveSource = await this.openProtocolV2PreparedSource(archiveArtifact.artifact);
1396
+ if (archiveSource.size > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1397
+ throw ERRORS.TypedError(
1398
+ HardwareErrorCode.RuntimeError,
1399
+ 'Protocol V2 prepared resource archive exceeds the total size limit',
1400
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1401
+ );
1402
+ }
1403
+
1404
+ let archiveBinary: ArrayBuffer;
1405
+ try {
1406
+ archiveBinary = await readFirmwareByteSourceFully(archiveSource);
1407
+ } finally {
1408
+ await archiveSource.close();
1409
+ }
1410
+ const archiveDigest = bytesToHex(sha256(new Uint8Array(archiveBinary)));
1411
+ if (archiveDigest !== archiveArtifact.artifact.sha256.toLowerCase()) {
1412
+ throw ERRORS.TypedError(
1413
+ HardwareErrorCode.RuntimeError,
1414
+ 'Protocol V2 prepared resource archive does not match its approved receipt',
1415
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1416
+ );
1417
+ }
1418
+
1419
+ // materializedEntries 由宿主生成,只有与获批 ZIP 的规范字节完全一致后才能使用。
1420
+ const verifiedArchive = await this.prepareProtocolV2LocalResourceArchive(archiveBinary);
1421
+ const entries = archiveArtifact.materializedEntries ?? [];
1422
+ const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
674
1423
  if (
675
- !pathInfoRes.message?.exist ||
676
- pathInfoRes.message?.directory ||
677
- fileSize === undefined ||
678
- fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
1424
+ entries.length !== verifiedArchive.materializedEntries.length ||
1425
+ verifiedArchive.materializedEntries.some(entry => {
1426
+ const preparedEntry = entriesByName.get(entry.entryName);
1427
+ const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
1428
+ return (
1429
+ !preparedEntry ||
1430
+ preparedEntry.artifact.size !== entry.binary.byteLength ||
1431
+ preparedEntry.artifact.sha256.toLowerCase() !== digest
1432
+ );
1433
+ })
679
1434
  ) {
680
- return null;
1435
+ throw ERRORS.TypedError(
1436
+ HardwareErrorCode.RuntimeError,
1437
+ 'Protocol V2 prepared resource entries do not match the approved archive',
1438
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1439
+ );
681
1440
  }
682
1441
 
683
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
684
- const chunks: Uint8Array[] = [];
685
- let offset = 0;
686
- while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
687
- const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
688
- const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
689
- file: {
690
- path: filePath,
691
- offset,
692
- total_size: 0,
693
- },
694
- chunk_len: readLen,
695
- ui_percentage: undefined,
1442
+ const verifiedEntriesByName = new Map(
1443
+ verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
1444
+ );
1445
+ const manifestBinary = verifiedEntriesByName.get('manifest.json');
1446
+ if (!manifestBinary) {
1447
+ throw ERRORS.TypedError(
1448
+ HardwareErrorCode.RuntimeError,
1449
+ 'Protocol V2 prepared resource archive has no valid manifest.json',
1450
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1451
+ );
1452
+ }
1453
+
1454
+ let manifestValue: unknown;
1455
+ try {
1456
+ manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
1457
+ } catch (error) {
1458
+ throw ERRORS.TypedError(
1459
+ HardwareErrorCode.RuntimeError,
1460
+ `Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
1461
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1462
+ );
1463
+ }
1464
+ const manifest = parseProtocolV2ResourceManifest(manifestValue);
1465
+ const selectedFiles = selectProtocolV2ResourceManifestFiles({
1466
+ manifest,
1467
+ targetsToUpdate: this.params.targetsToUpdate ?? [],
1468
+ });
1469
+ if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
1470
+ throw ERRORS.TypedError(
1471
+ HardwareErrorCode.RuntimeError,
1472
+ 'Protocol V2 prepared resource archive contains too many files',
1473
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1474
+ );
1475
+ }
1476
+
1477
+ let totalSize = 0;
1478
+ const sources: ProtocolV2ResourceBundleSource[] = [];
1479
+ for (const [index, file] of selectedFiles.entries()) {
1480
+ const binary = verifiedEntriesByName.get(file.archive_path);
1481
+ if (!binary || binary.byteLength !== file.size) {
1482
+ throw ERRORS.TypedError(
1483
+ HardwareErrorCode.RuntimeError,
1484
+ `Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
1485
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1486
+ );
1487
+ }
1488
+ totalSize += binary.byteLength;
1489
+ if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1490
+ throw ERRORS.TypedError(
1491
+ HardwareErrorCode.RuntimeError,
1492
+ 'Protocol V2 prepared resource archive exceeds the total size limit',
1493
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1494
+ );
1495
+ }
1496
+ // 使用从获批 ZIP 中提取的规范字节,避免宿主在校验后替换 entry reader 内容。
1497
+ const source = await this.openProtocolV2MemorySource(binary);
1498
+ const header =
1499
+ source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
1500
+ ? parseProtocolV2OkppHeader(
1501
+ new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
1502
+ )
1503
+ : null;
1504
+ sources.push({
1505
+ name: file.original_name || `resource-${index}`,
1506
+ source,
1507
+ devicePath: file.device_path,
1508
+ ...(header
1509
+ ? {
1510
+ version: header.version,
1511
+ payloadHash: header.payloadHash,
1512
+ headerHash: header.headerHash,
1513
+ }
1514
+ : {}),
696
1515
  });
697
- const data = toProtocolV2Bytes(res.message?.data);
698
- if (data.byteLength === 0) return null;
699
- chunks.push(data);
700
- offset += data.byteLength;
701
1516
  }
1517
+ return sources;
1518
+ }
702
1519
 
703
- const headerBytes = new Uint8Array(offset);
704
- let cursor = 0;
705
- chunks.forEach(chunk => {
706
- headerBytes.set(chunk, cursor);
707
- cursor += chunk.byteLength;
1520
+ private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
1521
+ try {
1522
+ this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
1523
+ const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
1524
+ const resourceSources = await this.prepareProtocolV2ResourceSources();
1525
+ if (installSources.length === 0 && resourceSources.length === 0) {
1526
+ throw ERRORS.TypedError(
1527
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1528
+ 'No firmware to update'
1529
+ );
1530
+ }
1531
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
1532
+
1533
+ return await this.executeProtocolV2SourceUpdate({
1534
+ installSources,
1535
+ resourceSources,
1536
+ });
1537
+ } finally {
1538
+ await this.closeProtocolV2PreparedSources();
1539
+ }
1540
+ }
1541
+
1542
+ private validateExpectedTargetVersions() {
1543
+ const expected = this.params.expectedTargetVersions;
1544
+ if (expected === undefined) return;
1545
+ if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
1546
+ throw ERRORS.TypedError(
1547
+ HardwareErrorCode.RuntimeError,
1548
+ 'Protocol V2 expected target versions are invalid'
1549
+ );
1550
+ }
1551
+ for (const [target, version] of Object.entries(expected)) {
1552
+ if (
1553
+ !Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
1554
+ target as Exclude<FirmwareUpdateV4Target, 'boot_resources'>
1555
+ ) ||
1556
+ typeof version !== 'string' ||
1557
+ !/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
1558
+ version.length > 64
1559
+ ) {
1560
+ throw ERRORS.TypedError(
1561
+ HardwareErrorCode.RuntimeError,
1562
+ 'Protocol V2 expected target version is invalid'
1563
+ );
1564
+ }
1565
+ }
1566
+ }
1567
+
1568
+ private getExpectedProtocolV2TargetVersion(targetId: number) {
1569
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
1570
+ const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
1571
+ if (!version) return undefined;
1572
+ const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
1573
+ if (
1574
+ parts.length !== 3 ||
1575
+ parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
1576
+ ) {
1577
+ return undefined;
1578
+ }
1579
+ return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
1580
+ }
1581
+
1582
+ private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
1583
+ const expected = this.params?.expectedTargetVersions;
1584
+ if (!expected) return;
1585
+ const features = this.protocolV2LatestFinalFeatures;
1586
+ const requestedTargets = this.getProtocolV2RequestedTargets();
1587
+ const applicationTargets = requestedTargets.filter(
1588
+ target => target === 'app_v1' || target === 'app_v2'
1589
+ );
1590
+ const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
1591
+ boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
1592
+ coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
1593
+ se01: features?.se01Version ?? undefined,
1594
+ se02: features?.se02Version ?? undefined,
1595
+ se03: features?.se03Version ?? undefined,
1596
+ se04: features?.se04Version ?? undefined,
1597
+ };
1598
+ if (features && applicationTargets.length === 1) {
1599
+ visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
1600
+ }
1601
+ const expectedEntries = (
1602
+ Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
1603
+ ).filter(([target]) => !targets || targets.includes(target));
1604
+ for (const [target, expectedVersion] of expectedEntries) {
1605
+ const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
1606
+ ([, mappedTarget]) => mappedTarget === target
1607
+ )?.[0];
1608
+ const statusVersion =
1609
+ targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
1610
+ const observedVersion =
1611
+ statusVersion === undefined
1612
+ ? visibleVersions[target]
1613
+ : `${Math.floor(statusVersion / 0x10000) % 0x100}.${
1614
+ Math.floor(statusVersion / 0x100) % 0x100
1615
+ }.${statusVersion % 0x100}`;
1616
+ if (!observedVersion) {
1617
+ if (this.protocolV2FinalStatusVerified) {
1618
+ Log.warn(
1619
+ `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
1620
+ );
1621
+ } else {
1622
+ throw ERRORS.TypedError(
1623
+ HardwareErrorCode.FirmwareVerificationFailed,
1624
+ `Protocol V2 target ${target} has no observable final version after status fallback`
1625
+ );
1626
+ }
1627
+ } else if (observedVersion !== expectedVersion) {
1628
+ throw ERRORS.TypedError(
1629
+ HardwareErrorCode.FirmwareVerificationFailed,
1630
+ `Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
1631
+ );
1632
+ }
1633
+ }
1634
+ }
1635
+
1636
+ private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
1637
+ if (this.params.targetsToUpdate?.length) {
1638
+ return [...new Set(this.params.targetsToUpdate)];
1639
+ }
1640
+ if (this.params.preparedPlan?.targetsToUpdate.length) {
1641
+ return [...new Set(this.params.preparedPlan.targetsToUpdate)] as FirmwareUpdateV4Target[];
1642
+ }
1643
+ const targets = new Set<FirmwareUpdateV4Target>();
1644
+ Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
1645
+ targets.add(target as FirmwareUpdateV4Target)
1646
+ );
1647
+ if (this.params.bootloaderBinary) targets.add('boot');
1648
+ if (this.params.applicationP1Binary) targets.add('app_v1');
1649
+ if (this.params.applicationP2Binary) targets.add('app_v2');
1650
+ if (this.params.coprocessorBinary) targets.add('coprocessor');
1651
+ if (this.params.se01Binary) targets.add('se01');
1652
+ if (this.params.se02Binary) targets.add('se02');
1653
+ if (this.params.se03Binary) targets.add('se03');
1654
+ if (this.params.se04Binary) targets.add('se04');
1655
+ return Array.from(targets);
1656
+ }
1657
+
1658
+ private async getProtocolV2DeviceFeatures(): Promise<Features> {
1659
+ if (this.device.features) {
1660
+ return this.device.features;
1661
+ }
1662
+ if (typeof this.device.getFeatures === 'function') {
1663
+ const features = await this.device.getFeatures();
1664
+ if (features) return features;
1665
+ }
1666
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
1667
+ }
1668
+
1669
+ private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
1670
+ const serialNumber = deviceInfo.hw?.serial_no?.trim();
1671
+ return serialNumber || undefined;
1672
+ }
1673
+
1674
+ private getProtocolV2PreparedPlanDeviceModel(features?: Features) {
1675
+ const currentDeviceType = this.device.getCurrentDeviceType();
1676
+ const featureDeviceType = features ? getDeviceType(features) : undefined;
1677
+ const deviceType =
1678
+ currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
1679
+ ? currentDeviceType
1680
+ : featureDeviceType;
1681
+ return deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo
1682
+ ? String(deviceType)
1683
+ : undefined;
1684
+ }
1685
+
1686
+ private getProtocolV2ConnectionRoute() {
1687
+ const descriptor = this.device.originalDescriptor;
1688
+ return (
1689
+ descriptor?.path?.trim() ||
1690
+ this.device.getConnectId?.()?.trim() ||
1691
+ descriptor?.id?.trim() ||
1692
+ undefined
1693
+ );
1694
+ }
1695
+
1696
+ private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
1697
+ assertProtocolV2ReconnectIdentity(
1698
+ this.protocolV2ExpectedSerialNumber,
1699
+ this.getProtocolV2SerialNumber(deviceInfo),
1700
+ this.protocolV2ExpectedPath,
1701
+ this.getProtocolV2ConnectionRoute()
1702
+ );
1703
+ }
1704
+
1705
+ private getProtocolV2DeviceInfoTimeout() {
1706
+ return this.isBleReconnect()
1707
+ ? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
1708
+ : PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
1709
+ }
1710
+
1711
+ private async requestProtocolV2PhysicalIdentity() {
1712
+ return requestProtocolV2DeviceInfo({
1713
+ commands: this.device.getCommands(),
1714
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
708
1715
  });
709
- return parseProtocolV2OkppHeader(headerBytes);
710
1716
  }
711
1717
 
712
- private shouldInstallRemoteProtocolV2Component(
713
- release: IFirmwareReleaseInfo,
714
- key: string,
715
- component: IProtocolV2FirmwareComponent,
716
- target: ProtocolV2RemoteComponentTarget,
717
- features: Features
718
- ) {
719
- const versionSatisfied = this.isProtocolV2ComponentVersionSatisfied(
720
- release,
721
- component,
722
- target,
723
- features
1718
+ private async captureProtocolV2PhysicalIdentity() {
1719
+ const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
1720
+ const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
1721
+ const path = this.getProtocolV2ConnectionRoute();
1722
+ if (this.params?.preparedPlan) {
1723
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
1724
+ preparedPlan: this.params.preparedPlan,
1725
+ deviceIdentity: serialNumber,
1726
+ deviceModel: this.getProtocolV2PreparedPlanDeviceModel(this.device.features),
1727
+ });
1728
+ } else if (this.params?.expectedDeviceId) {
1729
+ assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
1730
+ } else {
1731
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
1732
+ }
1733
+ this.protocolV2ExpectedSerialNumber = serialNumber;
1734
+ this.protocolV2ExpectedPath = path;
1735
+ }
1736
+
1737
+ private async verifyProtocolV2ReconnectIdentity() {
1738
+ const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
1739
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1740
+ return deviceInfo;
1741
+ }
1742
+
1743
+ private prepareBootloaderBinary(): ArrayBuffer | null {
1744
+ return this.params.bootloaderBinary ?? null;
1745
+ }
1746
+
1747
+ private getMissingProtocolV2FirmwareTargets(installItems: ProtocolV2InstallItem[]) {
1748
+ const preparedTargets = new Set(
1749
+ installItems.flatMap(item => {
1750
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1751
+ return target ? [target] : [];
1752
+ })
724
1753
  );
725
- if (versionSatisfied) {
726
- Log.log(`[FirmwareUpdateV4] skip Protocol V2 component ${key}; version is up to date`);
1754
+ return (this.params.targetsToUpdate ?? [])
1755
+ .filter(
1756
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
1757
+ target !== 'resource' && target !== 'boot_resources'
1758
+ )
1759
+ .filter(target => !preparedTargets.has(target));
1760
+ }
1761
+
1762
+ private filterProtocolV2LocalInstallItems(installItems: ProtocolV2InstallItem[]) {
1763
+ if (!this.protocolV2HasExplicitTargetSelection) {
1764
+ return installItems;
727
1765
  }
728
- return !versionSatisfied;
1766
+ const requestedTargets = new Set(this.params.targetsToUpdate ?? []);
1767
+ return installItems.filter(item => {
1768
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1769
+ return target !== undefined && requestedTargets.has(target);
1770
+ });
1771
+ }
1772
+
1773
+ private buildProtocolV2InstallItems({
1774
+ bootloaderBinary,
1775
+ fwBinaryMap,
1776
+ }: {
1777
+ bootloaderBinary: ArrayBuffer | null;
1778
+ fwBinaryMap: ProtocolV2TargetBinary[];
1779
+ }): ProtocolV2InstallItem[] {
1780
+ const installItems: ProtocolV2InstallItem[] = [];
1781
+
1782
+ if (bootloaderBinary) {
1783
+ installItems.push({
1784
+ fileName: 'bootloader.bin',
1785
+ binary: bootloaderBinary,
1786
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
1787
+ kind: 'bootloader',
1788
+ });
1789
+ }
1790
+
1791
+ installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
1792
+ return installItems;
1793
+ }
1794
+
1795
+ private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
1796
+ const { components } = release;
1797
+ if (!components) return [];
1798
+
1799
+ const orderedKeys = [
1800
+ ...(release.installOrder ?? []),
1801
+ ...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
1802
+ ];
1803
+
1804
+ return orderedKeys
1805
+ .map(key => {
1806
+ const component = components[key];
1807
+ return component ? ([key, component] as const) : undefined;
1808
+ })
1809
+ .filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
1810
+ }
1811
+
1812
+ private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
1813
+ const targetName = component.target?.toUpperCase();
1814
+ if (targetName === 'ROMLOADER') {
1815
+ throw ERRORS.TypedError(
1816
+ HardwareErrorCode.RuntimeError,
1817
+ PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
1818
+ );
1819
+ }
1820
+ const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
1821
+ if (!target) {
1822
+ throw ERRORS.TypedError(
1823
+ HardwareErrorCode.RuntimeError,
1824
+ `Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
1825
+ );
1826
+ }
1827
+ return target;
729
1828
  }
730
1829
 
731
1830
  private async downloadRemoteProtocolV2Component(
@@ -739,15 +1838,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
739
1838
  `Missing Protocol V2 firmware component url: ${key}/${component.target}`
740
1839
  );
741
1840
  }
1841
+ const expectedFingerprint = normalizeProtocolV2Hex(component.fingerprint);
1842
+ if (
1843
+ !Number.isSafeInteger(component.expectedSize) ||
1844
+ Number(component.expectedSize) <= 0 ||
1845
+ !expectedFingerprint ||
1846
+ !/^[0-9a-f]{64}$/u.test(expectedFingerprint)
1847
+ ) {
1848
+ throw ERRORS.TypedError(
1849
+ HardwareErrorCode.RuntimeError,
1850
+ `Protocol V2 firmware component integrity metadata is invalid: ${key}/${component.target}`,
1851
+ { firmwareUpdateCode: 'FirmwarePlanInvalid' }
1852
+ );
1853
+ }
742
1854
 
743
1855
  const { binary } = await getSysResourceBinary(component.url);
1856
+ if (binary.byteLength !== component.expectedSize) {
1857
+ throw ERRORS.TypedError(
1858
+ HardwareErrorCode.RuntimeError,
1859
+ `Protocol V2 firmware size mismatch: ${key}/${component.target}`,
1860
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1861
+ );
1862
+ }
1863
+ if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
1864
+ throw ERRORS.TypedError(
1865
+ HardwareErrorCode.RuntimeError,
1866
+ `Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
1867
+ );
1868
+ }
1869
+ const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
1870
+ if (expectedPayloadHash) {
1871
+ const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
1872
+ if (!header || header.payloadHash !== expectedPayloadHash) {
1873
+ throw ERRORS.TypedError(
1874
+ HardwareErrorCode.RuntimeError,
1875
+ `Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
1876
+ );
1877
+ }
1878
+ }
744
1879
  return {
745
1880
  ...target,
746
1881
  binary,
747
1882
  };
748
1883
  }
749
1884
 
750
- private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
1885
+ private async prepareRemoteProtocolV2Binaries(
1886
+ firmwareType: EFirmwareType,
1887
+ features: Features,
1888
+ explicitInstallItems: ProtocolV2InstallItem[] = []
1889
+ ) {
751
1890
  const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
752
1891
 
753
1892
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -755,6 +1894,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
755
1894
  const installItems: ProtocolV2InstallItem[] = [];
756
1895
 
757
1896
  if (!release) {
1897
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
1898
+ if (missingFirmwareTargets.length > 0) {
1899
+ throw ERRORS.TypedError(
1900
+ HardwareErrorCode.RuntimeError,
1901
+ `Protocol V2 firmware release is unavailable for requested targets: ${missingFirmwareTargets.join(
1902
+ ', '
1903
+ )}`
1904
+ );
1905
+ }
758
1906
  return {
759
1907
  bootloaderBinary,
760
1908
  fwBinaryMap,
@@ -763,38 +1911,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
763
1911
  }
764
1912
 
765
1913
  const entries = this.getRemoteComponentEntries(release);
1914
+ const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
1915
+ const explicitInstallItemByTargetId = new Map(
1916
+ explicitInstallItems.map(item => [item.targetId, item] as const)
1917
+ );
1918
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
766
1919
 
767
1920
  for (const [key, component] of entries) {
768
- const target = this.getRemoteComponentTarget(key, component);
769
- const shouldInstall = this.shouldInstallRemoteProtocolV2Component(
770
- release,
771
- key,
772
- component,
773
- target,
774
- features
775
- );
776
- if (shouldInstall) {
777
- const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
778
- if (remoteBinary.kind === 'bootloader') {
779
- bootloaderBinary = remoteBinary.binary;
780
- installItems.push({
781
- fileName: remoteBinary.fileName,
782
- binary: remoteBinary.binary,
783
- targetId: remoteBinary.targetId,
784
- kind: remoteBinary.kind,
785
- });
1921
+ const targetName = component.target?.toUpperCase();
1922
+ const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
1923
+ const updateTarget = target
1924
+ ? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
1925
+ : undefined;
1926
+ if (updateTarget && targetsToUpdate.has(updateTarget)) {
1927
+ const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
1928
+ const installItem =
1929
+ explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
1930
+ if (installItem.kind === 'bootloader') {
1931
+ bootloaderBinary = installItem.binary;
786
1932
  } else {
787
1933
  const binaryEntry = {
788
- fileName: remoteBinary.fileName,
789
- binary: remoteBinary.binary,
790
- targetId: remoteBinary.targetId,
1934
+ fileName: installItem.fileName,
1935
+ binary: installItem.binary,
1936
+ targetId: installItem.targetId,
791
1937
  };
792
1938
  fwBinaryMap.push(binaryEntry);
793
- installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
794
1939
  }
1940
+ installItems.push({ ...installItem });
1941
+ preparedTargets.add(updateTarget);
795
1942
  }
796
1943
  }
797
1944
 
1945
+ const missingTarget = Array.from(targetsToUpdate).find(
1946
+ target => target !== 'resource' && !preparedTargets.has(target)
1947
+ );
1948
+ if (missingTarget) {
1949
+ throw ERRORS.TypedError(
1950
+ HardwareErrorCode.RuntimeError,
1951
+ `Protocol V2 release does not contain requested target ${missingTarget}`
1952
+ );
1953
+ }
1954
+
798
1955
  return {
799
1956
  bootloaderBinary,
800
1957
  fwBinaryMap,
@@ -802,140 +1959,83 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
802
1959
  };
803
1960
  }
804
1961
 
805
- // ============================================================
806
- // RESC bundle 直写增量同步(FileRead 比对 + FilesystemFileWrite 直写)
807
- // ============================================================
808
-
809
- /**
810
- * 准备 RESC bundle 列表。
811
- *
812
- * 两种模式(同 FirmwareUpdateV3 的 binary vs version 模式):
813
- * - 用户传了 resourceBundleFiles(binary 数组):直接用,不做版本比对。
814
- * - 用户没传:从远端 config.json 的 release.resourceBundles 拉取,
815
- * 此时 syncProtocolV2ResourceBundles 会按需下载 + 比对设备已有 header 跳过。
816
- */
817
- private prepareProtocolV2ResourceBundles(
818
- firmwareType: EFirmwareType,
819
- features: Features
820
- ): ProtocolV2ResourceBundleBinary[] | undefined {
821
- // 模式1:用户手动传入 binary,直接安装,不比对
822
- if (this.params.resourceBundleFiles?.length) {
823
- return this.params.resourceBundleFiles.map(file => ({
824
- name: file.devicePath.split('/').pop() ?? file.devicePath,
825
- binary: file.binary,
826
- devicePath: file.devicePath,
827
- }));
828
- }
829
-
830
- // 模式2:远端配置模式,后续按需下载 + 比对
831
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
832
- if (!release?.resourceBundles?.length) return undefined;
833
-
834
- return release.resourceBundles.map(bundle => ({
835
- name: bundle.name,
836
- binary: new ArrayBuffer(0),
837
- devicePath: bundle.devicePath,
838
- url: bundle.url,
839
- version: bundle.version,
840
- payloadHash: bundle.payloadHash,
841
- headerHash: bundle.headerHash,
842
- })) as ProtocolV2ResourceBundleBinary[];
1962
+ private getProtocolV2ResourceFilePath(path: string) {
1963
+ if (path.startsWith('vol')) return path;
1964
+ if (path.startsWith('/')) return `vol0:${path}`;
1965
+ return `vol0:/${path}`;
843
1966
  }
844
1967
 
845
- /**
846
- * 同步 RESC bundles 到设备。
847
- *
848
- * 手动传入模式(有 binary):直接 FileWrite 直写,不比对。
849
- * 远端配置模式(无 binary,有 url):按需下载 + FileRead 比对跳过已更新的。
850
- */
851
- private async syncProtocolV2ResourceBundles(
852
- bundles: ProtocolV2ResourceBundleBinary[],
853
- firmwareSize: number
854
- ): Promise<{ processedSize: number; totalSize: number }> {
855
- const transferStartTime = Date.now();
856
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
857
-
858
- const isManualMode = bundles.every(b => b.binary.byteLength > 0);
859
- let bundlesToSync = bundles;
860
-
861
- // 远端配置模式:按需下载 + 比对跳过
862
- if (!isManualMode) {
863
- const filtered: ProtocolV2ResourceBundleBinary[] = [];
864
- for (const bundle of bundles) {
865
- // 下载 binary
866
- if (bundle.binary.byteLength === 0 && bundle.url) {
867
- Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
868
- const { binary } = await getSysResourceBinary(bundle.url);
869
- bundle.binary = binary;
870
- }
871
- // 比对设备上已有 header
872
- const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
873
- if (upToDate) {
874
- Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
875
- } else {
876
- filtered.push(bundle);
877
- }
878
- }
879
- bundlesToSync = filtered;
880
- }
881
-
882
- if (bundlesToSync.length === 0) {
883
- Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
884
- return { processedSize: 0, totalSize: firmwareSize };
1968
+ private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
1969
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1970
+ const filePath = this.getProtocolV2ResourceFilePath(path);
1971
+ const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
1972
+ path: filePath,
1973
+ });
1974
+ const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1975
+ if (
1976
+ !pathInfoRes.message?.exist ||
1977
+ pathInfoRes.message?.directory ||
1978
+ fileSize === undefined ||
1979
+ fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE ||
1980
+ (expectedSize !== undefined && fileSize !== expectedSize)
1981
+ ) {
1982
+ return null;
885
1983
  }
886
1984
 
887
- // FileWrite 直写到设备
888
- let totalSize = 0;
889
- for (const b of bundlesToSync) totalSize += b.binary.byteLength;
890
-
891
- const transferTotalSize = totalSize + firmwareSize;
892
- let processedSize = 0;
893
- for (const bundle of bundlesToSync) {
894
- Log.log(
895
- `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
896
- );
897
- processedSize = await this.protocolV2CommonUpdateProcess({
898
- payload: bundle.binary,
899
- filePath: bundle.devicePath,
900
- processedSize,
901
- totalSize: transferTotalSize,
1985
+ const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
1986
+ const chunks: Uint8Array[] = [];
1987
+ let offset = 0;
1988
+ while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
1989
+ const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
1990
+ const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
1991
+ file: {
1992
+ path: filePath,
1993
+ offset,
1994
+ total_size: 0,
1995
+ },
1996
+ chunk_len: readLen,
1997
+ ui_percentage: undefined,
902
1998
  });
1999
+ const data = toProtocolV2Bytes(res.message?.data);
2000
+ if (data.byteLength === 0) return null;
2001
+ chunks.push(data);
2002
+ offset += data.byteLength;
903
2003
  }
904
2004
 
905
- const elapsedMs = Date.now() - transferStartTime;
906
- Log.log(
907
- `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
908
- elapsedMs / 1000
909
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
910
- );
911
- return { processedSize, totalSize: transferTotalSize };
2005
+ const headerBytes = new Uint8Array(offset);
2006
+ let cursor = 0;
2007
+ chunks.forEach(chunk => {
2008
+ headerBytes.set(chunk, cursor);
2009
+ cursor += chunk.byteLength;
2010
+ });
2011
+ return parseProtocolV2OkppHeader(headerBytes);
912
2012
  }
913
2013
 
914
- /**
915
- * 比对设备上已有 okpkg 的 OKPP header(仅远端配置模式使用)。
916
- */
2014
+ /** Compare the downloaded and installed okpkg headers before transferring a resource. */
917
2015
  private async isProtocolV2ResourceBundleUpToDate(
918
- bundle: ProtocolV2ResourceBundleBinary
2016
+ bundle: Pick<
2017
+ ProtocolV2ResourceBundleSource,
2018
+ 'name' | 'source' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
2019
+ >
919
2020
  ): Promise<boolean> {
920
- if (this.params.forcedUpdateRes) return false;
921
- if (!bundle.version && !bundle.payloadHash) return false;
2021
+ if (this.params?.forcedUpdateRes) return false;
2022
+ if (!bundle.payloadHash || !bundle.headerHash) return false;
922
2023
 
923
2024
  try {
924
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
2025
+ const header = await this.readProtocolV2DeviceFileHeader(
2026
+ bundle.devicePath,
2027
+ bundle.source.size
2028
+ );
925
2029
  if (!header) return false;
926
2030
 
927
2031
  if (bundle.version) {
928
2032
  const cmp = compareProtocolV2Versions(header.version, bundle.version);
929
2033
  if (cmp === undefined || cmp !== 0) return false;
930
2034
  }
931
- if (bundle.payloadHash) {
932
- const expected = normalizeProtocolV2Hex(bundle.payloadHash);
933
- if (expected && header.payloadHash !== expected) return false;
934
- }
935
- if (bundle.headerHash) {
936
- const expected = normalizeProtocolV2Hex(bundle.headerHash);
937
- if (expected && header.headerHash !== expected) return false;
938
- }
2035
+ const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
2036
+ const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
2037
+ if (!expectedPayloadHash || header.payloadHash !== expectedPayloadHash) return false;
2038
+ if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
939
2039
  return true;
940
2040
  } catch (error) {
941
2041
  Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
@@ -945,23 +2045,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
945
2045
 
946
2046
  private isProtocolV2BootloaderMode() {
947
2047
  if (typeof this.device.isBootloader === 'function') {
948
- return this.device.isBootloader();
2048
+ return !!this.device.isBootloader();
949
2049
  }
950
- return !!this.device.features?.bootloaderMode;
2050
+ return (
2051
+ this.device.features?.mode === 'bootloader' ||
2052
+ (this.device.features?.mode == null && !!this.device.features?.bootloaderMode)
2053
+ );
2054
+ }
2055
+
2056
+ private isProtocolV2RomloaderMode() {
2057
+ const deviceType = this.device.getCurrentDeviceType();
2058
+ if (
2059
+ !this.device.isProtocolV2() ||
2060
+ (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
2061
+ ) {
2062
+ return false;
2063
+ }
2064
+ if (typeof this.device.isRomloader === 'function') {
2065
+ return this.device.isRomloader();
2066
+ }
2067
+ return this.device.features?.mode === 'romloader';
951
2068
  }
952
2069
 
953
2070
  async enterProtocolV2BootloaderMode() {
2071
+ // romloader is the first update environment and forwards targets to bootloader.
2072
+ // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
2073
+ if (this.isProtocolV2RomloaderMode()) {
2074
+ Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
2075
+ this.protocolV2ExecutionInLoader = true;
2076
+ return false;
2077
+ }
954
2078
  if (this.isProtocolV2BootloaderMode()) {
955
- Log.debug('Protocol V2 device is already in bootloader mode, skip reboot to bootloader');
2079
+ Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
2080
+ this.protocolV2ExecutionInLoader = true;
2081
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
956
2082
  return false;
957
2083
  }
958
2084
 
959
2085
  try {
960
2086
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
961
2087
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
962
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
963
2088
  await wait(1000);
964
2089
  await this.waitForProtocolV2BootloaderMode();
2090
+ this.protocolV2ExecutionInLoader = true;
2091
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
965
2092
  return true;
966
2093
  } catch (error) {
967
2094
  if (error instanceof HardwareError) {
@@ -978,20 +2105,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
978
2105
  ) {
979
2106
  const startTime = Date.now();
980
2107
  let lastError: unknown;
2108
+ let shouldReconnect = true;
981
2109
 
982
2110
  while (Date.now() - startTime < timeout) {
983
2111
  try {
984
- await this.reconnectProtocolV2Device();
2112
+ if (shouldReconnect) {
2113
+ await this.reconnectProtocolV2Device();
2114
+ shouldReconnect = false;
2115
+ }
985
2116
  const deviceInfo = await requestProtocolV2DeviceInfo({
986
2117
  commands: this.device.getCommands(),
987
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
2118
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
988
2119
  });
989
- const features = this.device.updateProtocolV2Features(deviceInfo);
990
- if (features?.bootloaderMode) {
2120
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2121
+ const features = await this.device.probeProtocolV2RuntimeState(
2122
+ deviceInfo,
2123
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2124
+ );
2125
+ if (features?.mode === 'bootloader') {
991
2126
  return features;
992
2127
  }
993
2128
  lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
994
2129
  } catch (error) {
2130
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2131
+ throw error;
2132
+ }
2133
+ shouldReconnect = true;
995
2134
  lastError = error;
996
2135
  Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
997
2136
  }
@@ -1007,8 +2146,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1007
2146
  }
1008
2147
 
1009
2148
  /**
1010
- * 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
1011
- * 文件名仅用于 staging 路径展示,target_id 已显式给定。
2149
+ * Collect explicit target binaries grouped by DeviceFirmwareTargetType.
2150
+ * Filenames are display-only staging paths because target_id is explicit.
1012
2151
  */
1013
2152
  private collectExplicitTargetBinaries() {
1014
2153
  const entries: ProtocolV2TargetBinary[] = [];
@@ -1044,115 +2183,325 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1044
2183
  return entries;
1045
2184
  }
1046
2185
 
2186
+ private buildProtocolV2ExecutionPhases({
2187
+ installSources,
2188
+ resourceSources,
2189
+ }: {
2190
+ installSources: ProtocolV2InstallSource[];
2191
+ resourceSources: ProtocolV2ResourceBundleSource[];
2192
+ }): ProtocolV2ExecutionPhase[] {
2193
+ const phases: ProtocolV2ExecutionPhase[] = [];
2194
+ const bootResourceSources = resourceSources.filter(source =>
2195
+ isProtocolV2BootResourcePackagePath(source.devicePath)
2196
+ );
2197
+ const remainingResourceSources = resourceSources.filter(
2198
+ source => !isProtocolV2BootResourcePackagePath(source.devicePath)
2199
+ );
2200
+ // A previous interrupted run may have left a promotable staging file. Replace
2201
+ // it with the current approved package before any firmware-triggered reboot.
2202
+ if (bootResourceSources.length > 0) {
2203
+ phases.push({
2204
+ kind: 'resource-sync',
2205
+ installSources: [],
2206
+ resourceSources: bootResourceSources,
2207
+ });
2208
+ }
2209
+ const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
2210
+ if (bootloaderSources.length > 0) {
2211
+ phases.push(
2212
+ {
2213
+ kind: 'bootloader-install',
2214
+ installSources: bootloaderSources,
2215
+ resourceSources: [],
2216
+ },
2217
+ {
2218
+ kind: 'bootloader-verify',
2219
+ installSources: [],
2220
+ resourceSources: [],
2221
+ }
2222
+ );
2223
+ }
2224
+ if (remainingResourceSources.length > 0) {
2225
+ phases.push({
2226
+ kind: 'resource-sync',
2227
+ installSources: [],
2228
+ resourceSources: remainingResourceSources,
2229
+ });
2230
+ }
2231
+ const componentSources = installSources.filter(source => source.kind !== 'bootloader');
2232
+ if (componentSources.length > 0) {
2233
+ phases.push({
2234
+ kind: 'component-install',
2235
+ installSources: componentSources,
2236
+ resourceSources: [],
2237
+ });
2238
+ }
2239
+ phases.push({
2240
+ kind: 'final-verify',
2241
+ installSources: [],
2242
+ resourceSources: [],
2243
+ });
2244
+ return phases;
2245
+ }
2246
+
2247
+ private async executeProtocolV2Phases({
2248
+ installSources,
2249
+ resourceSources,
2250
+ }: {
2251
+ installSources: ProtocolV2InstallSource[];
2252
+ resourceSources: ProtocolV2ResourceBundleSource[];
2253
+ }) {
2254
+ this.protocolV2BootResourceStagingSafe = false;
2255
+ const phases = this.buildProtocolV2ExecutionPhases({
2256
+ installSources,
2257
+ resourceSources,
2258
+ });
2259
+ for (const phase of phases) {
2260
+ if (phase.kind === 'final-verify') {
2261
+ return this.completeProtocolV2FinalVerification();
2262
+ }
2263
+ if (phase.kind === 'resource-sync') {
2264
+ await this.enterProtocolV2BootloaderMode();
2265
+ if (
2266
+ !phase.resourceSources.some(source =>
2267
+ isProtocolV2BootResourcePackagePath(source.devicePath)
2268
+ )
2269
+ ) {
2270
+ await this.ensureProtocolV2BootResourceStagingIsEmpty();
2271
+ }
2272
+ await this.executeProtocolV2TransferPhase(phase);
2273
+ } else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
2274
+ await this.enterProtocolV2BootloaderMode();
2275
+ await this.ensureProtocolV2BootResourceStagingIsEmpty();
2276
+ await this.executeProtocolV2TransferPhase(phase);
2277
+ await this.exitProtocolV2BootloaderToNormal();
2278
+ } else if (phase.kind === 'bootloader-verify') {
2279
+ await this.waitForProtocolV2FinalFeatures();
2280
+ this.assertExpectedProtocolV2Versions(['boot']);
2281
+ }
2282
+ }
2283
+ throw ERRORS.TypedError(
2284
+ HardwareErrorCode.RuntimeError,
2285
+ 'Protocol V2 execution has no final verification phase'
2286
+ );
2287
+ }
2288
+
2289
+ private async ensureProtocolV2BootResourceStagingIsEmpty() {
2290
+ if (this.protocolV2BootResourceStagingSafe) return;
2291
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
2292
+ const query = () =>
2293
+ typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
2294
+ path: PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
2295
+ });
2296
+ const current = await query();
2297
+ if (current.message?.exist) {
2298
+ if (current.message.directory) {
2299
+ throw ERRORS.TypedError(
2300
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2301
+ 'Protocol V2 boot resource staging path is not a file'
2302
+ );
2303
+ }
2304
+ await typedCall('FilesystemFileDelete', 'Success', {
2305
+ path: PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
2306
+ });
2307
+ const remaining = await query();
2308
+ if (remaining.message?.exist) {
2309
+ throw ERRORS.TypedError(
2310
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2311
+ 'Protocol V2 stale boot resource staging file could not be removed'
2312
+ );
2313
+ }
2314
+ }
2315
+ this.protocolV2BootResourceStagingSafe = true;
2316
+ }
2317
+
2318
+ private async executeProtocolV2SourceUpdate({
2319
+ installSources,
2320
+ resourceSources,
2321
+ }: {
2322
+ installSources: ProtocolV2InstallSource[];
2323
+ resourceSources: ProtocolV2ResourceBundleSource[];
2324
+ }) {
2325
+ return this.executeProtocolV2Phases({
2326
+ installSources,
2327
+ resourceSources,
2328
+ });
2329
+ }
2330
+
1047
2331
  private async executeProtocolV2Update({
1048
2332
  fwBinaryMap,
1049
2333
  bootloaderBinary,
1050
2334
  installItems,
1051
- resourceBundles,
1052
2335
  }: {
1053
2336
  fwBinaryMap?: ProtocolV2TargetBinary[];
1054
2337
  bootloaderBinary?: ArrayBuffer | null;
1055
2338
  installItems?: ProtocolV2InstallItem[];
1056
- resourceBundles?: ProtocolV2ResourceBundleBinary[];
1057
2339
  }) {
1058
- const orderedInstallItems =
2340
+ const memoryInstallItems =
1059
2341
  installItems ??
1060
2342
  this.buildProtocolV2InstallItems({
1061
- bootloaderBinary: bootloaderBinary ?? null,
1062
2343
  fwBinaryMap: fwBinaryMap ?? [],
2344
+ bootloaderBinary: bootloaderBinary ?? null,
1063
2345
  });
1064
-
1065
- let firmwareSize = 0;
1066
- for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
1067
-
1068
- this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
1069
-
1070
- let processedSize = 0;
1071
- let totalSize = firmwareSize;
1072
- if (resourceBundles?.length) {
1073
- const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1074
- resourceBundles,
1075
- firmwareSize
2346
+ try {
2347
+ const installSources = await Promise.all(
2348
+ memoryInstallItems.map(async item => ({
2349
+ fileName: item.fileName,
2350
+ source: await this.openProtocolV2MemorySource(item.binary),
2351
+ targetId: item.targetId,
2352
+ kind: item.kind,
2353
+ }))
1076
2354
  );
1077
- processedSize = resourceTransfer.processedSize;
1078
- totalSize = resourceTransfer.totalSize;
2355
+ return await this.executeProtocolV2Phases({
2356
+ installSources,
2357
+ resourceSources: [],
2358
+ });
2359
+ } finally {
2360
+ await this.closeProtocolV2PreparedSources();
1079
2361
  }
2362
+ }
1080
2363
 
1081
- // 只有 RESC bundle、没有固件 target 时跳过 staging/install 阶段
1082
- if (orderedInstallItems.length === 0) {
1083
- Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1084
- if (totalSize > 0) {
1085
- this.postProgressMessage(100, 'transferData');
2364
+ private async executeProtocolV2TransferPhase({
2365
+ installSources,
2366
+ resourceSources,
2367
+ }: ProtocolV2ExecutionPhase) {
2368
+ let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
2369
+ const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
2370
+ for (const resource of resourceSources) {
2371
+ // Early boot promotes this mounted package's staging file. Always replace any
2372
+ // stale staging bytes with the artifact approved by the current PreparedPlan,
2373
+ // even when the mounted final file itself is already current.
2374
+ const requiresFreshStaging = isProtocolV2BootResourcePackagePath(resource.devicePath);
2375
+ if (!requiresFreshStaging && (await this.isProtocolV2ResourceBundleUpToDate(resource))) {
2376
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
2377
+ } else {
2378
+ resourcesToSync.push(resource);
2379
+ totalSize += resource.source.size;
1086
2380
  }
1087
- return;
1088
2381
  }
1089
- let transferredSize = processedSize;
1090
- const transferStartTime = Date.now();
1091
- const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1092
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1093
- const onTransferredBytes = (bytes: number) => {
1094
- transferredSize = bytes;
1095
- };
1096
- Log.log(
1097
- `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1098
- );
1099
-
1100
- const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
1101
-
1102
- try {
1103
- for (const item of orderedInstallItems) {
1104
- const filePath = this.getProtocolV2InstallItemStagingPath(item);
1105
- Log.log(
1106
- `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1107
- );
1108
- processedSize = await this.protocolV2CommonUpdateProcess({
1109
- payload: item.binary,
1110
- filePath,
1111
- processedSize,
1112
- totalSize,
1113
- onTransferredBytes,
1114
- });
1115
- transferredSize = processedSize;
1116
- await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1117
2382
 
1118
- stagedInstallTargets.push({
1119
- ...item,
1120
- path: filePath,
1121
- });
2383
+ this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
2384
+ let processedSize = 0;
2385
+ for (const resource of resourcesToSync) {
2386
+ // The bootloader keeps its live resource package mounted. FatFs rejects
2387
+ // replacing an open file, so early boot promotes this staging file before mounting it.
2388
+ const writePath = resolveProtocolV2ResourceWritePath(resource.devicePath);
2389
+ processedSize = await this.protocolV2SourceUpdateProcess({
2390
+ source: resource.source,
2391
+ filePath: writePath,
2392
+ processedSize,
2393
+ totalSize,
2394
+ });
2395
+ await this.verifyProtocolV2StagedFile(writePath, resource.source.size);
2396
+ if (isProtocolV2BootResourcePackagePath(resource.devicePath)) {
2397
+ this.protocolV2BootResourceStagingSafe = true;
1122
2398
  }
2399
+ }
1123
2400
 
1124
- if (totalSize > 0) {
1125
- this.postProgressMessage(100, 'transferData');
1126
- }
2401
+ const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
2402
+ for (const item of installSources) {
2403
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
2404
+ processedSize = await this.protocolV2SourceUpdateProcess({
2405
+ source: item.source,
2406
+ filePath,
2407
+ processedSize,
2408
+ totalSize,
2409
+ });
2410
+ await this.verifyProtocolV2StagedFile(filePath, item.source.size);
2411
+ stagedInstallTargets.push({
2412
+ targetId: item.targetId,
2413
+ path: filePath,
2414
+ });
2415
+ }
1127
2416
 
1128
- const elapsedMs = Date.now() - transferStartTime;
1129
- Log.log(
1130
- `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1131
- elapsedMs / 1000
1132
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1133
- );
1134
- } catch (error) {
1135
- const elapsedMs = Date.now() - transferStartTime;
1136
- Log.warn(
1137
- `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1138
- elapsedMs / 1000
1139
- ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1140
- );
1141
- throw error;
2417
+ if (totalSize > 0) {
2418
+ this.postProgressMessage(100, 'transferData');
2419
+ }
2420
+ if (stagedInstallTargets.length === 0) {
2421
+ return;
1142
2422
  }
1143
2423
 
1144
2424
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1145
-
1146
- const allTargets = stagedInstallTargets.map(item => ({
2425
+ const targets = stagedInstallTargets.map(item => ({
1147
2426
  target_id: item.targetId,
1148
2427
  path: item.path,
1149
2428
  }));
1150
- Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1151
- const startResponse = await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1152
- await this.waitForProtocolV2FirmwareUpdateComplete(allTargets, startResponse);
2429
+ await this.protocolV2StartFirmwareUpdate({ targets });
2430
+ await this.waitForProtocolV2FirmwareUpdateComplete(targets);
2431
+ }
2432
+
2433
+ private async protocolV2SourceUpdateProcess({
2434
+ source,
2435
+ filePath,
2436
+ processedSize,
2437
+ totalSize,
2438
+ }: {
2439
+ source: FirmwareByteSource;
2440
+ filePath: string;
2441
+ processedSize: number;
2442
+ totalSize: number;
2443
+ }) {
2444
+ let lastError: unknown;
2445
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
2446
+ try {
2447
+ const transferStartedAt = Date.now();
2448
+ await writeFirmwareByteSource({
2449
+ source,
2450
+ chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
2451
+ write: async ({ data, sourceOffset, length, first }) => {
2452
+ const chunkEnd = sourceOffset + length;
2453
+ const deviceProgress = getProtocolV2DeviceTransferProgress(
2454
+ processedSize + sourceOffset,
2455
+ processedSize + chunkEnd,
2456
+ totalSize
2457
+ );
2458
+ const response = await this.fileWriteChunk(
2459
+ filePath,
2460
+ source.size,
2461
+ sourceOffset,
2462
+ data,
2463
+ first,
2464
+ deviceProgress
2465
+ );
2466
+ const rawProcessedByte = response.message.processed_byte;
2467
+ const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
2468
+ if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
2469
+ throw ERRORS.TypedError(
2470
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2471
+ `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
2472
+ );
2473
+ }
2474
+ const transferredBytes = processedSize + chunkEnd;
2475
+ const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
2476
+ this.postProgressMessage(
2477
+ Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
2478
+ 'transferData',
2479
+ {
2480
+ transferredBytes,
2481
+ totalBytes: totalSize,
2482
+ rateBytesPerSecond:
2483
+ elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
2484
+ elapsedMs,
2485
+ }
2486
+ );
2487
+ return length;
2488
+ },
2489
+ });
2490
+ return processedSize + source.size;
2491
+ } catch (error) {
2492
+ lastError = error;
2493
+ if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
2494
+ await this.recoverProtocolV2FileTransfer();
2495
+ }
2496
+ }
2497
+ }
2498
+ throw ERRORS.TypedError(
2499
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2500
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
2501
+ );
1153
2502
  }
1154
2503
 
1155
- private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
2504
+ private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1156
2505
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1157
2506
  }
1158
2507
 
@@ -1169,168 +2518,315 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1169
2518
  }
1170
2519
  }
1171
2520
 
1172
- private async queryProtocolV2FirmwareUpdateStatus() {
1173
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1174
- return typedCall(
1175
- 'DeviceFirmwareUpdateStatusGet',
1176
- 'DeviceFirmwareUpdateStatus',
1177
- {},
1178
- {
1179
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1180
- }
1181
- );
1182
- }
1183
-
1184
- private async pingProtocolV2Device() {
1185
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1186
- await typedCall(
1187
- 'Ping',
1188
- 'Success',
1189
- { message: 'firmware-update' },
1190
- {
1191
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1192
- }
1193
- );
1194
- }
1195
-
1196
- private isProtocolV2NormalModeFeatures(features?: Features | null) {
1197
- return !!features && !features.bootloaderMode && features.mode !== 'bootloader';
1198
- }
1199
-
1200
- private async probeProtocolV2NormalMode() {
1201
- const deviceInfo = await requestProtocolV2DeviceInfo({
1202
- commands: this.device.getCommands(),
1203
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1204
- request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
1205
- });
1206
- const features = this.device.updateProtocolV2Features(deviceInfo);
1207
- if (this.isProtocolV2NormalModeFeatures(features)) {
1208
- Log.log('Protocol V2 firmware install finished; device is back in normal mode');
1209
- return true;
1210
- }
1211
- return false;
1212
- }
1213
-
1214
2521
  private assertProtocolV2TargetStatus(
1215
2522
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1216
- expectedTargetIds: Set<number>
2523
+ expectedTargetIds: Set<number>,
2524
+ expectedPaths = new Map<number, string>()
1217
2525
  ) {
2526
+ Log.log(
2527
+ `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
2528
+ );
1218
2529
  const failedTarget = statusTargets.find(
1219
2530
  target =>
1220
2531
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1221
2532
  isProtocolV2TargetStatusFailed(target.status)
1222
2533
  );
1223
2534
  if (failedTarget) {
2535
+ const failedTargetDetails = JSON.stringify({
2536
+ targetId: failedTarget.target_id,
2537
+ status: failedTarget.status,
2538
+ payloadVersion: failedTarget.payload_version,
2539
+ path: failedTarget.path,
2540
+ });
2541
+ Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
1224
2542
  throw ERRORS.TypedError(
1225
2543
  HardwareErrorCode.FirmwareError,
1226
- `Protocol V2 firmware target ${failedTarget.target_id} failed`
2544
+ `Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
2545
+ failedTarget.status ?? 'unknown'
2546
+ } payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
2547
+ failedTarget.path ?? 'unknown'
2548
+ }`
1227
2549
  );
1228
2550
  }
1229
2551
 
1230
- const completedTargets = statusTargets.filter(
1231
- target =>
1232
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1233
- isProtocolV2TargetStatusFinished(target.status)
2552
+ const matchingTargets = statusTargets.filter(target =>
2553
+ expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
2554
+ );
2555
+ const seenTargetIds = new Set<number>();
2556
+ for (const target of matchingTargets) {
2557
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2558
+ if (
2559
+ targetId === undefined ||
2560
+ seenTargetIds.has(targetId) ||
2561
+ (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
2562
+ ) {
2563
+ throw ERRORS.TypedError(
2564
+ HardwareErrorCode.RuntimeError,
2565
+ `Protocol V2 install status conflicts with target ${target.target_id}`,
2566
+ {
2567
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
2568
+ }
2569
+ );
2570
+ }
2571
+ seenTargetIds.add(targetId);
2572
+ }
2573
+ const completedTargets = matchingTargets.filter(target =>
2574
+ isProtocolV2TargetStatusFinished(target.status)
1234
2575
  );
1235
- if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
2576
+ const completedTargetIds = new Set<number>();
2577
+ completedTargets.forEach(target => {
2578
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2579
+ if (targetId !== undefined) {
2580
+ completedTargetIds.add(targetId);
2581
+ }
2582
+ });
2583
+ if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
2584
+ for (const target of completedTargets) {
2585
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2586
+ const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
2587
+ if (targetId === undefined) {
2588
+ throw ERRORS.TypedError(
2589
+ HardwareErrorCode.RuntimeError,
2590
+ 'Protocol V2 completed target identity is invalid'
2591
+ );
2592
+ }
2593
+ const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
2594
+ if (expectedVersion !== undefined && payloadVersion !== undefined) {
2595
+ if (payloadVersion !== expectedVersion) {
2596
+ throw ERRORS.TypedError(
2597
+ HardwareErrorCode.FirmwareVerificationFailed,
2598
+ `Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
2599
+ );
2600
+ }
2601
+ }
2602
+ if (payloadVersion !== undefined) {
2603
+ this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
2604
+ }
2605
+ }
2606
+ this.postProgressMessage(100, 'installingFirmware');
1236
2607
  return true;
1237
2608
  }
1238
2609
 
1239
- const inProgressTarget = statusTargets.find(
1240
- target =>
1241
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
2610
+ if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
2611
+ const hasInProgressTarget = matchingTargets.some(target =>
1242
2612
  isProtocolV2TargetStatusInProgress(target.status)
1243
- );
1244
- if (inProgressTarget) {
1245
- this.postProgressMessage(99, 'installingFirmware');
2613
+ );
2614
+ const completedProgress = Math.floor(
2615
+ (completedTargetIds.size / expectedTargetIds.size) * 100
2616
+ );
2617
+ // The protocol exposes no per-target percentage, so report coarse progress by
2618
+ // completed targets and use 1% once work starts to keep the UI responsive.
2619
+ const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
2620
+ this.postProgressMessage(progress, 'installingFirmware');
1246
2621
  }
1247
2622
 
1248
2623
  return false;
1249
2624
  }
1250
2625
 
1251
- private async waitForProtocolV2FirmwareUpdateComplete(
1252
- targets: Array<{ target_id: number; path: string }>,
1253
- startResponse?: ProtocolV2FirmwareUpdateStartResponse
2626
+ private getProtocolV2MissingTargetIds(
2627
+ statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
2628
+ expectedTargetIds: Set<number>
1254
2629
  ) {
1255
- const expectedTargetIds = new Set(targets.map(target => target.target_id));
1256
- if (startResponse?.type === 'DeviceFirmwareUpdateStatus') {
1257
- const statusTargets = (startResponse.message.records ??
1258
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
1259
- if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
1260
- return;
2630
+ const reportedTargetIds = new Set<number>();
2631
+ statusTargets.forEach(target => {
2632
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2633
+ if (targetId !== undefined) {
2634
+ reportedTargetIds.add(targetId);
1261
2635
  }
1262
- }
2636
+ });
2637
+ return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
2638
+ }
1263
2639
 
2640
+ private async waitForProtocolV2FirmwareUpdateComplete(
2641
+ targets: Array<{ target_id: number; path: string }>
2642
+ ) {
2643
+ this.protocolV2FinalStatusVerified = false;
2644
+ const expectedTargetIds = new Set(targets.map(target => target.target_id));
2645
+ const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1264
2646
  const startTime = Date.now();
1265
2647
  let lastError: unknown;
2648
+ let shouldReconnect = true;
2649
+ let deviceInfo: ProtocolV2DeviceInfo | undefined;
2650
+ let missingTargetStatusSince: number | undefined;
2651
+ let missingTargetStatusKey: string | undefined;
2652
+ const resetMissingTargetStatusGrace = () => {
2653
+ missingTargetStatusSince = undefined;
2654
+ missingTargetStatusKey = undefined;
2655
+ };
1266
2656
 
1267
2657
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
1268
2658
  try {
1269
- const statusRes = await this.queryProtocolV2FirmwareUpdateStatus();
1270
- const statusTargets = (statusRes.message.records ??
1271
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
1272
- if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
1273
- return;
1274
- }
1275
- } catch (error) {
1276
- lastError = error;
1277
- if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
1278
- throw error;
2659
+ if (shouldReconnect) {
2660
+ await this.reconnectProtocolV2Device();
2661
+ deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
2662
+ shouldReconnect = false;
1279
2663
  }
1280
- Log.log('Protocol V2 firmware install status polling failed: ', error);
1281
- if (isProtocolV2PollingTransientError(error)) {
1282
- try {
1283
- await this.reconnectProtocolV2Device();
1284
- if (await this.probeProtocolV2NormalMode()) {
2664
+ const currentDeviceInfo = deviceInfo;
2665
+ try {
2666
+ const statusResponse = await this.device.getCommands().typedCall(
2667
+ 'DeviceFirmwareUpdateStatusGet',
2668
+ 'DeviceFirmwareUpdateStatus',
2669
+ {
2670
+ fields: {
2671
+ status: true,
2672
+ payload_version: true,
2673
+ path: true,
2674
+ },
2675
+ },
2676
+ { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
2677
+ );
2678
+ const statusTargets = (statusResponse.message.records ??
2679
+ []) as ProtocolV2FirmwareUpdateStatusTarget[];
2680
+ if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
2681
+ this.protocolV2FinalStatusVerified = true;
2682
+ return;
2683
+ }
2684
+
2685
+ if (
2686
+ statusTargets.length === 0 &&
2687
+ currentDeviceInfo &&
2688
+ (await this.probeProtocolV2NormalMode(currentDeviceInfo))
2689
+ ) {
2690
+ Log.log(
2691
+ '[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
2692
+ );
2693
+ this.postProgressMessage(100, 'installingFirmware');
2694
+ return;
2695
+ }
2696
+
2697
+ const missingTargetIds = this.getProtocolV2MissingTargetIds(
2698
+ statusTargets,
2699
+ expectedTargetIds
2700
+ );
2701
+ if (missingTargetIds.length > 0) {
2702
+ const now = Date.now();
2703
+ const missingKey = missingTargetIds.join(',');
2704
+ if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
2705
+ missingTargetStatusSince = now;
2706
+ missingTargetStatusKey = missingKey;
2707
+ } else if (
2708
+ now - missingTargetStatusSince >=
2709
+ PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
2710
+ ) {
2711
+ const reportedTargetIds = statusTargets
2712
+ .map(target => normalizeProtocolV2TargetId(target.target_id))
2713
+ .filter((targetId): targetId is number => targetId !== undefined);
2714
+ throw ERRORS.TypedError(
2715
+ HardwareErrorCode.FirmwareError,
2716
+ `Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
2717
+ ','
2718
+ )}`
2719
+ );
2720
+ }
2721
+ lastError = new Error(
2722
+ `Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
2723
+ );
2724
+ } else {
2725
+ resetMissingTargetStatusGrace();
2726
+ lastError = new Error('Protocol V2 firmware targets are still installing');
2727
+ }
2728
+ } catch (error) {
2729
+ if (isProtocolV2TerminalInstallStatusError(error)) {
2730
+ throw error;
2731
+ }
2732
+ // Only consecutive full status dumps can prove that a record is absent.
2733
+ // A reboot or transport interruption starts a fresh grace window.
2734
+ resetMissingTargetStatusGrace();
2735
+ // App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
2736
+ // missing endpoint as completion only after the runtime probe confirms App mode.
2737
+ if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
2738
+ if (!currentDeviceInfo) {
2739
+ throw ERRORS.TypedError(
2740
+ HardwareErrorCode.RuntimeError,
2741
+ 'Protocol V2 device identity is unavailable during install polling'
2742
+ );
2743
+ }
2744
+ if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
2745
+ Log.log(
2746
+ '[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
2747
+ );
2748
+ this.postProgressMessage(100, 'installingFirmware');
1285
2749
  return;
1286
2750
  }
1287
- } catch (reconnectError) {
1288
- lastError = reconnectError;
2751
+ lastError = new Error(
2752
+ 'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
2753
+ );
2754
+ } else {
2755
+ shouldReconnect = true;
2756
+ deviceInfo = undefined;
2757
+ lastError = error;
1289
2758
  Log.log(
1290
- 'Protocol V2 firmware install reconnect/normal-mode probe failed: ',
1291
- reconnectError
2759
+ '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
2760
+ error
1292
2761
  );
1293
2762
  }
1294
- try {
1295
- await this.pingProtocolV2Device();
1296
- Log.log('Protocol V2 firmware status unavailable, Ping is ready');
1297
- } catch (pingError) {
1298
- lastError = pingError;
1299
- Log.log('Protocol V2 firmware install Ping polling failed: ', pingError);
1300
- }
1301
2763
  }
2764
+ } catch (error) {
2765
+ lastError = error;
2766
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2767
+ throw error;
2768
+ }
2769
+ if (isProtocolV2TerminalInstallStatusError(error)) {
2770
+ throw error;
2771
+ }
2772
+ if (
2773
+ error instanceof HardwareError &&
2774
+ error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
2775
+ ) {
2776
+ throw error;
2777
+ }
2778
+ shouldReconnect = true;
2779
+ deviceInfo = undefined;
2780
+ resetMissingTargetStatusGrace();
2781
+ Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1302
2782
  }
1303
2783
  await wait(1000);
1304
2784
  }
1305
2785
 
1306
2786
  throw ERRORS.TypedError(
1307
2787
  HardwareErrorCode.RuntimeError,
1308
- `Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
2788
+ `Protocol V2 firmware update status not complete within ${
2789
+ PROTOCOL_V2_INSTALL_TIMEOUT / 1000
2790
+ }s: ${this.normalizeErrorMessage(lastError)}`
1309
2791
  );
1310
2792
  }
1311
2793
 
1312
2794
  private async exitProtocolV2BootloaderToNormal() {
1313
- this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
2795
+ await this.reconnectProtocolV2Device();
2796
+ const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1314
2797
  try {
1315
- await this.reconnectProtocolV2Device();
1316
- if (await this.probeProtocolV2NormalMode()) {
1317
- Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
2798
+ if (await this.probeProtocolV2NormalMode(deviceInfo)) {
2799
+ Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
1318
2800
  return;
1319
2801
  }
1320
2802
  } catch (error) {
1321
- Log.log('Protocol V2 normal-mode probe before reboot failed: ', error);
2803
+ Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
1322
2804
  }
1323
2805
  await this.protocolV2Reboot(DeviceRebootType.Normal);
2806
+ this.protocolV2ExecutionInLoader = false;
2807
+ }
2808
+
2809
+ private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
2810
+ const features = await this.device.probeProtocolV2RuntimeState(
2811
+ deviceInfo,
2812
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2813
+ );
2814
+ return features.mode === 'normal' && !features.bootloaderMode;
1324
2815
  }
1325
2816
 
1326
2817
  private async waitForProtocolV2FinalFeatures() {
1327
2818
  const features = await this.waitForProtocolV2ReconnectAndFeatures(
1328
- PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT
2819
+ PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1329
2820
  );
1330
2821
 
2822
+ return this.getProtocolV2VersionResult(features);
2823
+ }
2824
+
2825
+ private getProtocolV2VersionResult(features: Features) {
1331
2826
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1332
2827
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1333
2828
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
2829
+ this.protocolV2LatestFinalFeatures = features;
1334
2830
  if (firmwareVersion === '0.0.0') {
1335
2831
  Log.warn(
1336
2832
  'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
@@ -1344,32 +2840,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1344
2840
  };
1345
2841
  }
1346
2842
 
2843
+ private async completeProtocolV2FinalVerification() {
2844
+ if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
2845
+ await this.exitProtocolV2BootloaderToNormal();
2846
+ }
2847
+ const versions = await this.waitForProtocolV2FinalFeatures();
2848
+ this.assertExpectedProtocolV2Versions();
2849
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
2850
+ DevicePool.resetState();
2851
+ return versions;
2852
+ }
2853
+
1347
2854
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1348
2855
  const startTime = Date.now();
1349
2856
  let lastError: unknown;
2857
+ let shouldReconnect = true;
1350
2858
 
1351
2859
  while (Date.now() - startTime < timeout) {
1352
2860
  try {
1353
- await this.reconnectProtocolV2Device();
2861
+ if (shouldReconnect) {
2862
+ await this.reconnectProtocolV2Device();
2863
+ shouldReconnect = false;
2864
+ }
1354
2865
  const deviceInfo = await requestProtocolV2DeviceInfo({
1355
2866
  commands: this.device.getCommands(),
1356
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1357
- // 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
2867
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
2868
+ // Completion needs target versions only; keep scope aligned with the request.
1358
2869
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
1359
2870
  });
1360
- const features = this.device.updateProtocolV2Features(deviceInfo);
1361
- if (features.bootloaderMode || features.mode === 'bootloader') {
1362
- throw ERRORS.TypedError(
1363
- HardwareErrorCode.DeviceNotFound,
1364
- 'Protocol V2 device is still in bootloader mode'
1365
- );
2871
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2872
+ const features = await this.device.probeProtocolV2RuntimeState(
2873
+ deviceInfo,
2874
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2875
+ );
2876
+ if (features.mode === 'normal' && !features.bootloaderMode) {
2877
+ return features;
1366
2878
  }
1367
- return features;
2879
+ lastError = ERRORS.TypedError(
2880
+ HardwareErrorCode.DeviceNotFound,
2881
+ 'Protocol V2 device is still in bootloader mode'
2882
+ );
1368
2883
  } catch (error) {
2884
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2885
+ throw error;
2886
+ }
2887
+ shouldReconnect = true;
1369
2888
  lastError = error;
1370
2889
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
1371
- await wait(1000);
1372
2890
  }
2891
+ await wait(1000);
1373
2892
  }
1374
2893
 
1375
2894
  throw ERRORS.TypedError(
@@ -1393,130 +2912,71 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1393
2912
  DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
1394
2913
  devicesDescriptor.length === 1
1395
2914
  ) {
2915
+ const descriptor = devicesDescriptor[0];
2916
+ if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
2917
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
2918
+ }
1396
2919
  this.device.updateDescriptor(
1397
2920
  {
1398
- ...devicesDescriptor[0],
2921
+ ...descriptor,
1399
2922
  protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
1400
2923
  },
1401
2924
  true
1402
2925
  );
1403
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
2926
+ await this.ensureProtocolV2DeviceAcquired();
1404
2927
  this.device.commands.disposed = false;
1405
2928
  this.device.getCommands().mainId = this.device.mainId ?? '';
1406
- await this.device.initialize();
1407
2929
  return;
1408
2930
  }
1409
2931
 
1410
- // App bootloader 序列号暂时可能不一致。V4 升级重连阶段只接受唯一枚举设备,
1411
- // 避免继续按旧 app connectId 查缓存导致反复输出 path mismatch 日志。
2932
+ // Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
2933
+ // Enumeration order is not stable and must never decide the reconnect target.
1412
2934
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
1413
2935
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1414
2936
  });
1415
- if (deviceList.length !== 1) {
2937
+ const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
2938
+ const expectedPath = this.protocolV2ExpectedPath?.trim();
2939
+ const reconnectDevice = deviceList.find(candidate => {
2940
+ const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
2941
+ if (expectedSerialNumber && candidateSerialNumber) {
2942
+ return candidateSerialNumber === expectedSerialNumber;
2943
+ }
2944
+ return !!expectedPath && candidate.getConnectId() === expectedPath;
2945
+ });
2946
+ if (!reconnectDevice) {
1416
2947
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1417
2948
  }
1418
2949
 
1419
2950
  Log.debug(
1420
- 'Protocol V2 firmware reconnect using single enumerated device:',
1421
- deviceList[0].getConnectId()
2951
+ 'Protocol V2 firmware reconnect using matched device:',
2952
+ reconnectDevice.getConnectId()
1422
2953
  );
1423
- this.device.updateFromCache(deviceList[0]);
1424
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
2954
+ this.device.updateFromCache(reconnectDevice);
2955
+ await this.ensureProtocolV2DeviceAcquired();
1425
2956
  this.device.commands.disposed = false;
1426
2957
  this.device.getCommands().mainId = this.device.mainId ?? '';
1427
- await this.device.initialize();
1428
- }
1429
-
1430
- private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1431
- let lastError: unknown;
1432
- for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1433
- try {
1434
- return await this.protocolV2WriteWholeFile(params);
1435
- } catch (error) {
1436
- lastError = error;
1437
- Log.error(
1438
- `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1439
- error
1440
- );
1441
- if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1442
- break;
1443
- }
1444
- await this.recoverProtocolV2FileTransfer();
1445
- }
1446
- }
1447
-
1448
- throw ERRORS.TypedError(
1449
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1450
- `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1451
- );
1452
2958
  }
1453
2959
 
1454
- private async protocolV2WriteWholeFile({
1455
- payload,
1456
- filePath,
1457
- processedSize,
1458
- totalSize,
1459
- onTransferredBytes,
1460
- }: ProtocolV2FileTransferParams) {
1461
- const chunkSize = this.getProtocolV2FirmwareChunkSize();
1462
- let offset = 0;
1463
- const getUploadProgress = (fileOffset: number) => {
1464
- if (totalSize !== undefined && processedSize !== undefined) {
1465
- return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
1466
- }
1467
- return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
1468
- };
1469
-
1470
- while (offset < payload.byteLength) {
1471
- const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
1472
- const chunkLength = chunkEnd - offset;
1473
- const chunk = payload.slice(offset, chunkEnd);
1474
- const overwrite = offset === 0;
1475
- const progress = getProtocolV2DeviceTransferProgress(
1476
- (processedSize ?? 0) + offset,
1477
- (processedSize ?? 0) + chunkEnd,
1478
- totalSize ?? payload.byteLength
1479
- );
1480
-
1481
- const writeRes = await this.fileWriteChunk(
1482
- filePath,
1483
- payload.byteLength,
1484
- offset,
1485
- chunk,
1486
- overwrite,
1487
- progress
1488
- );
1489
- const processedByte = Number(writeRes.message.processed_byte);
1490
- const nextOffset =
1491
- Number.isFinite(processedByte) && processedByte > offset
1492
- ? processedByte
1493
- : offset + chunkLength;
1494
- if (nextOffset <= offset || nextOffset > payload.byteLength) {
1495
- throw ERRORS.TypedError(
1496
- HardwareErrorCode.EmmcFileWriteFirmwareError,
1497
- `invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
1498
- );
1499
- }
1500
- offset = nextOffset;
1501
- onTransferredBytes?.((processedSize ?? 0) + offset);
1502
- this.postProgressMessage(getUploadProgress(offset), 'transferData');
2960
+ /**
2961
+ * After acquiring a USB session, polling reuses its command channel. Reacquire only
2962
+ * after reboot or enumeration changes cause hasDeviceAcquire() to become false.
2963
+ */
2964
+ private async ensureProtocolV2DeviceAcquired() {
2965
+ const commands = this.device.getCommands();
2966
+ if (this.device.hasDeviceAcquire() && !commands.disposed) {
2967
+ return;
1503
2968
  }
1504
-
1505
- return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
2969
+ await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, {
2970
+ throwOnRunPromiseError: true,
2971
+ });
1506
2972
  }
1507
2973
 
1508
- private getProtocolV2FirmwareTransferTransport() {
1509
- const env = DataManager.getSettings('env');
1510
- if (env && DataManager.isBleConnect(env)) {
1511
- return 'BLE';
1512
- }
1513
- if (
1514
- env &&
1515
- (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1516
- ) {
1517
- return 'WebUSB';
1518
- }
1519
- return env ?? 'unknown';
2974
+ private isProtocolV2ReconnectIdentityError(error: unknown) {
2975
+ // A serial can be temporarily unavailable while Loader starts, and BLE has no stable path.
2976
+ // Keep polling unless the device explicitly reports a different serial.
2977
+ return this.normalizeErrorMessage(error).includes(
2978
+ 'Protocol V2 reconnect physical identity mismatch'
2979
+ );
1520
2980
  }
1521
2981
 
1522
2982
  private async fileWriteChunk(
@@ -1528,17 +2988,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1528
2988
  progress: number | null
1529
2989
  ): Promise<TypedResponseMessage<'FilesystemFile'>> {
1530
2990
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1531
- const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
1532
- file: {
1533
- path: filePath,
1534
- offset,
1535
- total_size: totalFileSize,
1536
- data: chunk,
2991
+ const writeRes = await typedCall(
2992
+ 'FilesystemFileWrite',
2993
+ 'FilesystemFile',
2994
+ {
2995
+ file: {
2996
+ path: filePath,
2997
+ offset,
2998
+ total_size: totalFileSize,
2999
+ data: chunk,
3000
+ },
3001
+ overwrite,
3002
+ append: false,
3003
+ ui_percentage: progress ?? undefined,
1537
3004
  },
1538
- overwrite,
1539
- append: false,
1540
- ui_percentage: progress ?? undefined,
1541
- });
3005
+ {
3006
+ writeWithResponse: false,
3007
+ onWriteCompleted: () => undefined,
3008
+ }
3009
+ );
1542
3010
  if (writeRes.type !== 'FilesystemFile') {
1543
3011
  if ((writeRes as any).type === 'CallMethodError') {
1544
3012
  if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
@@ -1554,9 +3022,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1554
3022
  const env = DataManager.getSettings('env');
1555
3023
  if (DataManager.isBleConnect(env)) {
1556
3024
  await wait(3000);
1557
- await this.acquireProtocolV2BleDevice();
1558
- await this.device.initialize();
1559
3025
  }
3026
+ await this.reconnectProtocolV2Device();
3027
+ await this.verifyProtocolV2ReconnectIdentity();
3028
+ await this.device.initialize();
1560
3029
  await wait(2000);
1561
3030
  }
1562
3031
 
@@ -1575,35 +3044,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1575
3044
  targets: Array<{ target_id: number; path: string }>;
1576
3045
  }) {
1577
3046
  const commands = this.device.getCommands();
1578
- let response: ProtocolV2FirmwareUpdateStartResponse;
1579
- try {
1580
- response = await commands.typedCall(
1581
- 'DeviceFirmwareUpdateRequest',
1582
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
1583
- {
1584
- targets,
1585
- },
1586
- {
1587
- intermediateTypes: ['DeviceFirmwareUpdateStatus'],
1588
- timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT,
1589
- onIntermediateResponse: (response: { type?: string }) => {
1590
- if (response.type === 'DeviceFirmwareUpdateStatus') {
1591
- this.postProgressMessage(99, 'installingFirmware');
1592
- }
1593
- },
1594
- }
1595
- );
1596
- } catch (error) {
1597
- if (isProtocolV2StartUpdateTransientError(error)) {
1598
- Log.log(
1599
- 'Protocol V2 firmware update request did not return; continue status polling',
1600
- error
1601
- );
1602
- } else {
1603
- throw error;
1604
- }
1605
- }
3047
+ const response: ProtocolV2FirmwareUpdateStartResponse = await commands.typedCall(
3048
+ 'DeviceFirmwareUpdateRequest',
3049
+ 'Success',
3050
+ { targets },
3051
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
3052
+ );
3053
+ // Success acknowledges that the device accepted installation. End confirmation
3054
+ // and begin status polling only after receiving this ACK.
1606
3055
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
3056
+ this.postProgressMessage(0, 'installingFirmware');
1607
3057
  return response;
1608
3058
  }
1609
3059
 
@@ -1613,9 +3063,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1613
3063
  const res = await typedCall('DeviceReboot', 'Success', {
1614
3064
  reboot_type: rebootType,
1615
3065
  });
3066
+ this.device.markProtocolV2Reboot(rebootType);
1616
3067
  return res.message;
1617
3068
  } catch (error) {
1618
3069
  if (isProtocolV2DeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
3070
+ this.device.markProtocolV2Reboot(rebootType);
1619
3071
  return { message: 'Device rebooted successfully' };
1620
3072
  }
1621
3073
  throw error;