@onekeyfe/hd-core 1.2.0-alpha.18 → 1.2.0-alpha.181

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