@onekeyfe/hd-core 1.2.0-alpha.9 → 1.2.0-alpha.91

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