@onekeyfe/hd-core 1.2.0-alpha.16 → 1.2.0-alpha.161

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