@onekeyfe/hd-core 1.2.0-alpha.1 → 1.2.0-alpha.100

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