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

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 +6669 -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 +1786 -776
  436. package/dist/index.d.ts.map +1 -1
  437. package/dist/index.js +11556 -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 +76 -2
  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 +2005 -732
  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 +105 -9
  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,107 @@ 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
+ }, {});
424
709
 
425
710
  this.params = {
711
+ preparedPlan,
426
712
  chunkSize: payload.chunkSize,
427
713
  forcedUpdateRes: payload.forcedUpdateRes,
428
714
  bootloaderBinary: payload.bootloaderBinary,
@@ -434,19 +720,38 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
434
720
  se02Binary: payload.se02Binary,
435
721
  se03Binary: payload.se03Binary,
436
722
  se04Binary: payload.se04Binary,
437
- resourceBundleFiles: payload.resourceBundleFiles,
438
- firmwareType: payload.firmwareType,
723
+ resourceArchiveBinary: payload.resourceArchiveBinary,
724
+ firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
725
+ targetsToUpdate: preparedPlan
726
+ ? ([...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[])
727
+ : payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
728
+ target === 'boot_resources' ? 'resource' : target
729
+ ),
730
+ expectedTargetVersions: preparedPlan
731
+ ? preparedExpectedTargetVersions
732
+ : payload.expectedTargetVersions,
439
733
  platform: payload.platform,
734
+ expectedDeviceId: payload.expectedDeviceId,
735
+ artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
736
+ componentArtifacts: preparedPlan ? undefined : payload.componentArtifacts,
440
737
  };
441
738
  }
442
739
 
443
- private getProtocolV2FirmwareChunkSize() {
740
+ private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write', filePath?: string) {
444
741
  const payloadChunkSize = Number(this.params?.chunkSize);
445
742
  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;
743
+ const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
744
+ let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
745
+ if (isBle) {
746
+ if (direction === 'read') {
747
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
748
+ } else {
749
+ // Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
750
+ maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
751
+ ? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
752
+ : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
753
+ }
754
+ }
450
755
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
451
756
  return maxChunkSize;
452
757
  }
@@ -457,21 +762,46 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
457
762
  }
458
763
 
459
764
  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
765
  Log.debug('FirmwareUpdateV4 strategy: Protocol V2');
468
766
  return this.runProtocolV2();
469
767
  }
470
768
 
471
769
  private async runProtocolV2() {
770
+ await this.captureProtocolV2PhysicalIdentity();
472
771
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
772
+ const currentDeviceType = this.device.getCurrentDeviceType();
773
+ const capabilityDeviceType =
774
+ currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
775
+ ? currentDeviceType
776
+ : getDeviceType(deviceFeatures);
777
+ assertProtocolV2FirmwareTargetsSupported(capabilityDeviceType, this.params);
473
778
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
474
779
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
780
+ this.validateExpectedTargetVersions();
781
+
782
+ if (
783
+ !this.params.preparedPlan &&
784
+ this.params.resourceArchiveBinary &&
785
+ this.params.targetsToUpdate?.includes('resource')
786
+ ) {
787
+ const localMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
788
+ features: deviceFeatures,
789
+ firmwareType,
790
+ });
791
+ try {
792
+ return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
793
+ } finally {
794
+ localMemoryHost.release();
795
+ }
796
+ }
797
+
798
+ const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
799
+ Boolean
800
+ );
801
+ if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
802
+ return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
803
+ }
804
+ const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
475
805
 
476
806
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
477
807
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -480,10 +810,45 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
480
810
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
481
811
  fwBinaryMap = this.collectExplicitTargetBinaries();
482
812
  bootloaderBinary = this.prepareBootloaderBinary();
483
- if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
813
+ const explicitInstallItems = this.buildProtocolV2InstallItems({
814
+ bootloaderBinary,
815
+ fwBinaryMap,
816
+ });
817
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
818
+ const needsRemoteFirmware = this.params.targetsToUpdate?.length
819
+ ? missingFirmwareTargets.length > 0
820
+ : explicitInstallItems.length === 0;
821
+ if (wantsResources) {
822
+ throw ERRORS.TypedError(
823
+ HardwareErrorCode.RuntimeError,
824
+ 'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
825
+ {
826
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
827
+ }
828
+ );
829
+ }
830
+ if (
831
+ needsRemoteFirmware &&
832
+ (this.params.artifactReader ||
833
+ DataManager.getSettings('firmwareManifestMode') === 'external-only')
834
+ ) {
835
+ throw ERRORS.TypedError(
836
+ HardwareErrorCode.RuntimeError,
837
+ 'Protocol V2 firmware artifacts must be prepared by the external firmware host',
838
+ {
839
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
840
+ }
841
+ );
842
+ }
843
+ if (needsRemoteFirmware) {
844
+ // Remote updates must use a freshly fetched config before any reboot or file write.
845
+ await DataManager.forceReloadData();
846
+ }
847
+ if (needsRemoteFirmware) {
484
848
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
485
849
  firmwareType,
486
- deviceFeatures
850
+ deviceFeatures,
851
+ explicitInstallItems
487
852
  );
488
853
  bootloaderBinary = remoteBinaries.bootloaderBinary;
489
854
  fwBinaryMap = remoteBinaries.fwBinaryMap;
@@ -491,241 +856,844 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
491
856
  }
492
857
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
493
858
  } catch (err) {
494
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
859
+ if (
860
+ typeof err === 'object' &&
861
+ err !== null &&
862
+ 'params' in err &&
863
+ (err as HardwareError).params?.firmwareUpdateCode === 'FirmwareArtifactsNotPrepared'
864
+ ) {
865
+ throw err;
866
+ }
867
+ if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
868
+ throw err;
869
+ }
870
+ throw normalizeFirmwarePreparationError(err);
495
871
  }
496
872
 
497
- const resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
498
-
499
- if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
873
+ if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
500
874
  throw ERRORS.TypedError(
501
875
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
502
876
  'No firmware to update'
503
877
  );
504
878
  }
505
879
 
506
- await this.enterProtocolV2BootloaderMode();
507
-
508
- await this.executeProtocolV2Update({
880
+ return this.executeProtocolV2Update({
509
881
  fwBinaryMap,
510
882
  bootloaderBinary,
511
883
  ...(installItems ? { installItems } : undefined),
512
- ...(resourceBundles?.length ? { resourceBundles } : undefined),
513
884
  });
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
885
  }
523
886
 
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;
887
+ private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
888
+ const source = await openFirmwareByteSource({
889
+ artifact,
890
+ reader: this.params.artifactReader,
891
+ });
892
+ if (!source) {
893
+ throw ERRORS.TypedError(
894
+ HardwareErrorCode.RuntimeError,
895
+ `Firmware artifact ${artifact.artifactRef} is not prepared`,
896
+ {
897
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
898
+ }
899
+ );
531
900
  }
532
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
901
+ this.protocolV2PreparedSources.push(source);
902
+ return source;
533
903
  }
534
904
 
535
- private prepareBootloaderBinary(): ArrayBuffer | null {
536
- return this.params.bootloaderBinary ?? null;
905
+ private async openProtocolV2MemorySource(binary: ArrayBuffer) {
906
+ const source = await openFirmwareByteSource({ binary });
907
+ if (!source) {
908
+ throw ERRORS.TypedError(
909
+ HardwareErrorCode.RuntimeError,
910
+ 'Protocol V2 firmware binary is empty'
911
+ );
912
+ }
913
+ this.protocolV2PreparedSources.push(source);
914
+ return source;
537
915
  }
538
916
 
539
- private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
540
- return (
541
- !!this.params.resourceBundleFiles?.length ||
542
- !!this.params.bootloaderBinary ||
543
- fwBinaryMap.length > 0
544
- );
917
+ private async closeProtocolV2PreparedSources() {
918
+ const sources = this.protocolV2PreparedSources.splice(0);
919
+ await Promise.all(sources.map(source => source.close().catch(() => undefined)));
545
920
  }
546
921
 
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
- });
922
+ private async prepareProtocolV2InstallSources(
923
+ firmwareType: EFirmwareType,
924
+ features: Features
925
+ ): Promise<ProtocolV2InstallSource[]> {
926
+ if (this.params.preparedPlan) {
927
+ const requestedTargets = new Set(
928
+ this.params.preparedPlan.targetsToUpdate.filter(
929
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource'> => target !== 'resource'
930
+ )
931
+ );
932
+ const installSources: ProtocolV2InstallSource[] = [];
933
+ const preparedTargets = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>();
934
+ for (const artifact of this.params.preparedPlan.artifacts) {
935
+ if (artifact.target !== 'resource') {
936
+ const target = artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>;
937
+ const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(target);
938
+ if (
939
+ !requestedTargets.has(target) ||
940
+ artifact.role !== 'component' ||
941
+ artifact.container !== 'raw' ||
942
+ !installTarget ||
943
+ preparedTargets.has(target)
944
+ ) {
945
+ throw ERRORS.TypedError(
946
+ HardwareErrorCode.RuntimeError,
947
+ `Protocol V2 prepared component artifact is invalid: ${artifact.artifactId}`,
948
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
949
+ );
950
+ }
951
+ installSources.push({
952
+ ...installTarget,
953
+ source: await this.openProtocolV2PreparedSource(artifact.artifact),
954
+ });
955
+ preparedTargets.add(target);
956
+ }
957
+ }
958
+ const missingTarget = Array.from(requestedTargets).find(
959
+ target => !preparedTargets.has(target)
960
+ );
961
+ if (missingTarget) {
962
+ throw ERRORS.TypedError(
963
+ HardwareErrorCode.RuntimeError,
964
+ `Protocol V2 ${missingTarget} artifact is not prepared`,
965
+ {
966
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
967
+ artifactName: missingTarget,
968
+ }
969
+ );
970
+ }
971
+ return installSources;
563
972
  }
564
973
 
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') {
974
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
975
+ if (!release) {
589
976
  throw ERRORS.TypedError(
590
977
  HardwareErrorCode.RuntimeError,
591
- PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
978
+ 'Protocol V2 firmware release is unavailable'
592
979
  );
593
980
  }
594
- const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
595
- if (!target) {
981
+ const componentArtifacts = this.params.componentArtifacts ?? {};
982
+ const requestedTargets = new Set(
983
+ this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
984
+ );
985
+ const installSources: ProtocolV2InstallSource[] = [];
986
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
987
+
988
+ for (const [key, component] of this.getRemoteComponentEntries(release)) {
989
+ const target = this.getRemoteComponentTarget(key, component);
990
+ const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
991
+ if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
992
+ const artifact = componentArtifacts[updateTarget];
993
+ if (!artifact) {
994
+ throw ERRORS.TypedError(
995
+ HardwareErrorCode.RuntimeError,
996
+ `Protocol V2 ${updateTarget} artifact is not prepared`,
997
+ {
998
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
999
+ artifactName: updateTarget,
1000
+ }
1001
+ );
1002
+ }
1003
+ installSources.push({
1004
+ ...target,
1005
+ source: await this.openProtocolV2PreparedSource(artifact),
1006
+ });
1007
+ preparedTargets.add(updateTarget);
1008
+ }
1009
+ }
1010
+
1011
+ const unknownTarget = Array.from(requestedTargets).find(
1012
+ target => target !== 'resource' && !preparedTargets.has(target)
1013
+ );
1014
+ if (unknownTarget) {
596
1015
  throw ERRORS.TypedError(
597
1016
  HardwareErrorCode.RuntimeError,
598
- `Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
1017
+ `Protocol V2 release does not contain requested target ${unknownTarget}`
599
1018
  );
600
1019
  }
601
- return target;
1020
+ return installSources;
602
1021
  }
603
1022
 
604
- private getProtocolV2ComponentTargetVersion(
605
- release: IFirmwareReleaseInfo,
606
- component: IProtocolV2FirmwareComponent,
607
- target: ProtocolV2RemoteComponentTarget
608
- ) {
609
- if (component.version) return component.version;
610
- if (target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER) {
611
- return release.bootloaderVersion;
612
- }
613
- if (
614
- target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1 ||
615
- target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
616
- ) {
617
- return release.version;
1023
+ private async prepareProtocolV2LocalMemoryHost({
1024
+ features,
1025
+ firmwareType,
1026
+ }: {
1027
+ features: Features;
1028
+ firmwareType: EFirmwareType;
1029
+ }): Promise<FirmwareUpdateV4MemoryHost> {
1030
+ const installItems = this.buildProtocolV2InstallItems({
1031
+ bootloaderBinary: this.prepareBootloaderBinary(),
1032
+ fwBinaryMap: this.collectExplicitTargetBinaries(),
1033
+ });
1034
+ const requestedComponentTargets = new Set(
1035
+ (this.params.targetsToUpdate ?? []).filter(
1036
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
1037
+ target !== 'resource' && target !== 'boot_resources'
1038
+ )
1039
+ );
1040
+ const localComponentTargets = new Set(
1041
+ installItems.flatMap(item => {
1042
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1043
+ return target ? [target] : [];
1044
+ })
1045
+ );
1046
+ const missingTarget = Array.from(requestedComponentTargets).find(
1047
+ target => !localComponentTargets.has(target)
1048
+ );
1049
+ if (missingTarget) {
1050
+ throw ERRORS.TypedError(
1051
+ HardwareErrorCode.RuntimeError,
1052
+ `Protocol V2 local update has no binary for requested target ${missingTarget}`,
1053
+ {
1054
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
1055
+ artifactName: missingTarget,
1056
+ }
1057
+ );
618
1058
  }
619
- return undefined;
620
- }
621
1059
 
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;
1060
+ const planArtifacts: Parameters<typeof buildProtocolV2LocalFirmwareUpdatePlan>[0]['artifacts'] =
1061
+ [];
1062
+ const memoryArtifacts: FirmwareMemoryArtifact[] = [];
1063
+ for (const item of installItems) {
1064
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1065
+ if (!target || item.binary.byteLength <= 0) {
1066
+ throw ERRORS.TypedError(
1067
+ HardwareErrorCode.RuntimeError,
1068
+ `Protocol V2 local firmware artifact is invalid: ${item.fileName}`,
1069
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1070
+ );
1071
+ }
1072
+ const artifactId = `component:${target}`;
1073
+ planArtifacts.push({
1074
+ artifactId,
1075
+ target,
1076
+ container: 'raw',
1077
+ logicalName: item.fileName,
1078
+ expectedSize: item.binary.byteLength,
1079
+ expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
1080
+ ...(this.params.expectedTargetVersions?.[target]
1081
+ ? { targetVersion: this.params.expectedTargetVersions[target] }
1082
+ : {}),
1083
+ });
1084
+ memoryArtifacts.push({ artifactId, binary: item.binary });
1085
+ }
655
1086
 
656
- const currentVersion = this.getProtocolV2ComponentCurrentVersion(features, target);
657
- const compareResult = compareProtocolV2Versions(currentVersion, targetVersion);
658
- return compareResult !== undefined && compareResult >= 0;
659
- }
1087
+ const resourceArchive = await this.prepareProtocolV2LocalResourceArchive(
1088
+ this.params.resourceArchiveBinary as ArrayBuffer
1089
+ );
1090
+ const resourceArtifactId = 'resource:archive';
1091
+ planArtifacts.push({
1092
+ artifactId: resourceArtifactId,
1093
+ target: 'resource',
1094
+ container: 'zip',
1095
+ logicalName: 'protocol-v2-local-resource-archive',
1096
+ expectedSize: resourceArchive.binary.byteLength,
1097
+ expectedSha256: bytesToHex(sha256(new Uint8Array(resourceArchive.binary))),
1098
+ });
1099
+ memoryArtifacts.push({
1100
+ artifactId: resourceArtifactId,
1101
+ binary: resourceArchive.binary,
1102
+ materializedEntries: resourceArchive.materializedEntries,
1103
+ });
660
1104
 
661
- private getProtocolV2ResourceFilePath(path: string) {
662
- if (path.startsWith('vol')) return path;
663
- if (path.startsWith('/')) return `vol0:${path}`;
664
- return `vol0:/${path}`;
1105
+ const plan = buildProtocolV2LocalFirmwareUpdatePlan({
1106
+ features,
1107
+ firmwareType,
1108
+ platform: this.params.platform,
1109
+ artifacts: planArtifacts,
1110
+ });
1111
+ let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
1112
+ try {
1113
+ memoryHost = prepareFirmwareUpdateV4MemoryHost({
1114
+ sdk: {
1115
+ prepareFirmwareUpdatePlan,
1116
+ registerFirmwareUpdateHostBinding,
1117
+ unregisterFirmwareUpdateHostBinding,
1118
+ },
1119
+ plan,
1120
+ artifacts: memoryArtifacts,
1121
+ });
1122
+ const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
1123
+ assertFirmwareUpdatePreparedPlanBinding({
1124
+ preparedPlan,
1125
+ executor: 'v4',
1126
+ platform: this.params.platform,
1127
+ scopeTargets: [],
1128
+ bindings: [],
1129
+ });
1130
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
1131
+ preparedPlan,
1132
+ deviceIdentity: this.protocolV2ExpectedSerialNumber,
1133
+ });
1134
+ const hostBinding = resolveFirmwareUpdateHostBinding(
1135
+ memoryHost.hostBindingGeneration,
1136
+ preparedPlan.preparedPlanDigest
1137
+ );
1138
+ this.params.preparedPlan = preparedPlan;
1139
+ this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
1140
+ this.params.artifactReader = hostBinding.artifactReader;
1141
+ this.params.componentArtifacts = undefined;
1142
+ return memoryHost;
1143
+ } catch (error) {
1144
+ memoryHost?.release();
1145
+ throw error;
1146
+ }
665
1147
  }
666
1148
 
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,
1149
+ private async prepareProtocolV2LocalResourceArchive(
1150
+ binary: ArrayBuffer
1151
+ ): Promise<ProtocolV2LocalResourceArchive> {
1152
+ if (binary.byteLength <= 0 || binary.byteLength > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1153
+ throw ERRORS.TypedError(
1154
+ HardwareErrorCode.RuntimeError,
1155
+ 'Protocol V2 local resource ZIP archive size is invalid',
1156
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1157
+ );
1158
+ }
1159
+ const zip = await JSZip.loadAsync(binary);
1160
+ const zipEntries = Object.values(zip.files);
1161
+ if (
1162
+ zipEntries.some(entry => entry.unsafeOriginalName && entry.unsafeOriginalName !== entry.name)
1163
+ ) {
1164
+ throw ERRORS.TypedError(
1165
+ HardwareErrorCode.RuntimeError,
1166
+ 'Protocol V2 local resource ZIP contains an unsafe entry path',
1167
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1168
+ );
1169
+ }
1170
+ const entries = zipEntries.filter(entry => !entry.dir);
1171
+ if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
1172
+ throw ERRORS.TypedError(
1173
+ HardwareErrorCode.RuntimeError,
1174
+ 'Protocol V2 local resource ZIP entry set is invalid',
1175
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1176
+ );
1177
+ }
1178
+ let declaredUncompressedSize = 0;
1179
+ let declaredCompressedSize = 0;
1180
+ for (const entry of entries) {
1181
+ const sizes = getProtocolV2ZipEntrySizes(entry);
1182
+ declaredCompressedSize += sizes.compressedSize;
1183
+ declaredUncompressedSize += sizes.uncompressedSize;
1184
+ const entryLimit =
1185
+ entry.name === 'manifest.json'
1186
+ ? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1187
+ : PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
1188
+ if (
1189
+ sizes.uncompressedSize > entryLimit ||
1190
+ declaredCompressedSize > binary.byteLength ||
1191
+ declaredUncompressedSize >
1192
+ PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1193
+ ) {
1194
+ throw ERRORS.TypedError(
1195
+ HardwareErrorCode.RuntimeError,
1196
+ 'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
1197
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1198
+ );
1199
+ }
1200
+ }
1201
+ const manifestEntry = zip.file('manifest.json');
1202
+ if (!manifestEntry) {
1203
+ throw ERRORS.TypedError(
1204
+ HardwareErrorCode.RuntimeError,
1205
+ 'Protocol V2 local resource ZIP has no manifest.json',
1206
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1207
+ );
1208
+ }
1209
+ const manifestBinary = await manifestEntry.async('arraybuffer');
1210
+ if (
1211
+ manifestBinary.byteLength <= 0 ||
1212
+ manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1213
+ ) {
1214
+ throw ERRORS.TypedError(
1215
+ HardwareErrorCode.RuntimeError,
1216
+ 'Protocol V2 local resource manifest size is invalid',
1217
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1218
+ );
1219
+ }
1220
+
1221
+ let manifestValue: unknown;
1222
+ try {
1223
+ manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
1224
+ } catch (error) {
1225
+ throw ERRORS.TypedError(
1226
+ HardwareErrorCode.RuntimeError,
1227
+ `Protocol V2 local resource manifest is invalid: ${String(error)}`,
1228
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1229
+ );
1230
+ }
1231
+ const manifest = parseProtocolV2ResourceManifest(manifestValue);
1232
+ const selectedFiles = selectProtocolV2ResourceManifestFiles({
1233
+ manifest,
1234
+ targetsToUpdate: this.params.targetsToUpdate ?? [],
672
1235
  });
673
- const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1236
+ const expectedEntryNames = new Set([
1237
+ 'manifest.json',
1238
+ ...selectedFiles.map(file => file.archive_path),
1239
+ ]);
674
1240
  if (
675
- !pathInfoRes.message?.exist ||
676
- pathInfoRes.message?.directory ||
677
- fileSize === undefined ||
678
- fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
1241
+ entries.length !== expectedEntryNames.size ||
1242
+ entries.some(entry => !expectedEntryNames.has(entry.name))
679
1243
  ) {
680
- return null;
1244
+ throw ERRORS.TypedError(
1245
+ HardwareErrorCode.RuntimeError,
1246
+ 'Protocol V2 local resource ZIP contains an unexpected entry',
1247
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1248
+ );
681
1249
  }
682
1250
 
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,
1251
+ let totalSize = 0;
1252
+ const materializedEntries: FirmwareMemoryArtifactEntry[] = [
1253
+ { entryName: 'manifest.json', binary: manifestBinary },
1254
+ ];
1255
+ for (const file of selectedFiles) {
1256
+ const entry = zip.file(file.archive_path);
1257
+ if (!entry) {
1258
+ throw ERRORS.TypedError(
1259
+ HardwareErrorCode.RuntimeError,
1260
+ `Protocol V2 local resource ZIP is missing ${file.archive_path}`,
1261
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1262
+ );
1263
+ }
1264
+ const { uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
1265
+ totalSize += uncompressedSize;
1266
+ if (uncompressedSize !== file.size || totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1267
+ throw ERRORS.TypedError(
1268
+ HardwareErrorCode.RuntimeError,
1269
+ `Protocol V2 local resource file declared size is invalid: ${file.archive_path}`,
1270
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1271
+ );
1272
+ }
1273
+ const fileBinary = await entry.async('arraybuffer');
1274
+ const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
1275
+ if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
1276
+ throw ERRORS.TypedError(
1277
+ HardwareErrorCode.RuntimeError,
1278
+ `Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
1279
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1280
+ );
1281
+ }
1282
+ materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
1283
+ }
1284
+ return { binary, materializedEntries };
1285
+ }
1286
+
1287
+ private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
1288
+ const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
1289
+ if (!resourceRequested) {
1290
+ return [];
1291
+ }
1292
+ const archiveSources = await this.prepareProtocolV2ResourceArchiveSources();
1293
+ if (archiveSources) {
1294
+ return archiveSources;
1295
+ }
1296
+ throw ERRORS.TypedError(
1297
+ HardwareErrorCode.RuntimeError,
1298
+ 'Protocol V2 resource archive is not prepared',
1299
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1300
+ );
1301
+ }
1302
+
1303
+ private async prepareProtocolV2ResourceArchiveSources(): Promise<
1304
+ ProtocolV2ResourceBundleSource[] | undefined
1305
+ > {
1306
+ const archiveArtifacts =
1307
+ this.params.preparedPlan?.artifacts.filter(
1308
+ artifact =>
1309
+ artifact.role === 'resourceBundle' &&
1310
+ artifact.target === 'resource' &&
1311
+ artifact.container === 'zip'
1312
+ ) ?? [];
1313
+ if (archiveArtifacts.length === 0) {
1314
+ return undefined;
1315
+ }
1316
+ if (archiveArtifacts.length !== 1) {
1317
+ throw ERRORS.TypedError(
1318
+ HardwareErrorCode.RuntimeError,
1319
+ 'Protocol V2 prepared plan must contain exactly one resource archive',
1320
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1321
+ );
1322
+ }
1323
+
1324
+ const entries = archiveArtifacts[0].materializedEntries ?? [];
1325
+ const manifestEntry = entries.find(entry => entry.entryName === 'manifest.json');
1326
+ if (
1327
+ !manifestEntry ||
1328
+ manifestEntry.artifact.size <= 0 ||
1329
+ manifestEntry.artifact.size > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
1330
+ ) {
1331
+ throw ERRORS.TypedError(
1332
+ HardwareErrorCode.RuntimeError,
1333
+ 'Protocol V2 prepared resource archive has no valid manifest.json',
1334
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1335
+ );
1336
+ }
1337
+
1338
+ const manifestSource = await this.openProtocolV2PreparedSource(manifestEntry.artifact);
1339
+ let manifestValue: unknown;
1340
+ try {
1341
+ manifestValue = JSON.parse(
1342
+ new TextDecoder().decode(await readFirmwareByteSourceFully(manifestSource))
1343
+ );
1344
+ } catch (error) {
1345
+ throw ERRORS.TypedError(
1346
+ HardwareErrorCode.RuntimeError,
1347
+ `Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
1348
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1349
+ );
1350
+ }
1351
+ const manifest = parseProtocolV2ResourceManifest(manifestValue);
1352
+ const selectedFiles = selectProtocolV2ResourceManifestFiles({
1353
+ manifest,
1354
+ targetsToUpdate: this.params.targetsToUpdate ?? [],
1355
+ });
1356
+ if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
1357
+ throw ERRORS.TypedError(
1358
+ HardwareErrorCode.RuntimeError,
1359
+ 'Protocol V2 prepared resource archive contains too many files',
1360
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1361
+ );
1362
+ }
1363
+
1364
+ const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
1365
+ const expectedEntryNames = new Set([
1366
+ 'manifest.json',
1367
+ ...selectedFiles.map(file => file.archive_path),
1368
+ ]);
1369
+ if (entries.some(entry => !expectedEntryNames.has(entry.entryName))) {
1370
+ throw ERRORS.TypedError(
1371
+ HardwareErrorCode.RuntimeError,
1372
+ 'Protocol V2 prepared resource archive contains an unexpected entry',
1373
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1374
+ );
1375
+ }
1376
+
1377
+ let totalSize = 0;
1378
+ const sources: ProtocolV2ResourceBundleSource[] = [];
1379
+ for (const [index, file] of selectedFiles.entries()) {
1380
+ const entry = entriesByName.get(file.archive_path);
1381
+ if (
1382
+ !entry ||
1383
+ entry.artifact.size !== file.size ||
1384
+ entry.artifact.sha256.toLowerCase() !== file.sha256.toLowerCase()
1385
+ ) {
1386
+ throw ERRORS.TypedError(
1387
+ HardwareErrorCode.RuntimeError,
1388
+ `Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
1389
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1390
+ );
1391
+ }
1392
+ totalSize += entry.artifact.size;
1393
+ if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
1394
+ throw ERRORS.TypedError(
1395
+ HardwareErrorCode.RuntimeError,
1396
+ 'Protocol V2 prepared resource archive exceeds the total size limit',
1397
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
1398
+ );
1399
+ }
1400
+ const source = await this.openProtocolV2PreparedSource(entry.artifact);
1401
+ const header =
1402
+ source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
1403
+ ? parseProtocolV2OkppHeader(
1404
+ new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
1405
+ )
1406
+ : null;
1407
+ sources.push({
1408
+ name: file.original_name || `resource-${index}`,
1409
+ source,
1410
+ devicePath: file.device_path,
1411
+ ...(header
1412
+ ? {
1413
+ version: header.version,
1414
+ payloadHash: header.payloadHash,
1415
+ headerHash: header.headerHash,
1416
+ }
1417
+ : {}),
696
1418
  });
697
- const data = toProtocolV2Bytes(res.message?.data);
698
- if (data.byteLength === 0) return null;
699
- chunks.push(data);
700
- offset += data.byteLength;
701
1419
  }
1420
+ return sources;
1421
+ }
702
1422
 
703
- const headerBytes = new Uint8Array(offset);
704
- let cursor = 0;
705
- chunks.forEach(chunk => {
706
- headerBytes.set(chunk, cursor);
707
- cursor += chunk.byteLength;
1423
+ private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
1424
+ try {
1425
+ this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
1426
+ const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
1427
+ const resourceSources = await this.prepareProtocolV2ResourceSources();
1428
+ if (installSources.length === 0 && resourceSources.length === 0) {
1429
+ throw ERRORS.TypedError(
1430
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
1431
+ 'No firmware to update'
1432
+ );
1433
+ }
1434
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
1435
+
1436
+ return await this.executeProtocolV2SourceUpdate({
1437
+ installSources,
1438
+ resourceSources,
1439
+ });
1440
+ } finally {
1441
+ await this.closeProtocolV2PreparedSources();
1442
+ }
1443
+ }
1444
+
1445
+ private validateExpectedTargetVersions() {
1446
+ const expected = this.params.expectedTargetVersions;
1447
+ if (expected === undefined) return;
1448
+ if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
1449
+ throw ERRORS.TypedError(
1450
+ HardwareErrorCode.RuntimeError,
1451
+ 'Protocol V2 expected target versions are invalid'
1452
+ );
1453
+ }
1454
+ for (const [target, version] of Object.entries(expected)) {
1455
+ if (
1456
+ !Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
1457
+ target as Exclude<FirmwareUpdateV4Target, 'boot_resources'>
1458
+ ) ||
1459
+ typeof version !== 'string' ||
1460
+ !/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
1461
+ version.length > 64
1462
+ ) {
1463
+ throw ERRORS.TypedError(
1464
+ HardwareErrorCode.RuntimeError,
1465
+ 'Protocol V2 expected target version is invalid'
1466
+ );
1467
+ }
1468
+ }
1469
+ }
1470
+
1471
+ private getExpectedProtocolV2TargetVersion(targetId: number) {
1472
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
1473
+ const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
1474
+ if (!version) return undefined;
1475
+ const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
1476
+ if (
1477
+ parts.length !== 3 ||
1478
+ parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
1479
+ ) {
1480
+ return undefined;
1481
+ }
1482
+ return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
1483
+ }
1484
+
1485
+ private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
1486
+ const expected = this.params?.expectedTargetVersions;
1487
+ if (!expected) return;
1488
+ const features = this.protocolV2LatestFinalFeatures;
1489
+ const requestedTargets = this.getProtocolV2RequestedTargets();
1490
+ const applicationTargets = requestedTargets.filter(
1491
+ target => target === 'app_v1' || target === 'app_v2'
1492
+ );
1493
+ const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
1494
+ boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
1495
+ coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
1496
+ se01: features?.se01Version ?? undefined,
1497
+ se02: features?.se02Version ?? undefined,
1498
+ se03: features?.se03Version ?? undefined,
1499
+ se04: features?.se04Version ?? undefined,
1500
+ };
1501
+ if (features && applicationTargets.length === 1) {
1502
+ visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
1503
+ }
1504
+ const expectedEntries = (
1505
+ Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
1506
+ ).filter(([target]) => !targets || targets.includes(target));
1507
+ for (const [target, expectedVersion] of expectedEntries) {
1508
+ const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
1509
+ ([, mappedTarget]) => mappedTarget === target
1510
+ )?.[0];
1511
+ const statusVersion =
1512
+ targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
1513
+ const observedVersion =
1514
+ statusVersion === undefined
1515
+ ? visibleVersions[target]
1516
+ : `${Math.floor(statusVersion / 0x10000) % 0x100}.${
1517
+ Math.floor(statusVersion / 0x100) % 0x100
1518
+ }.${statusVersion % 0x100}`;
1519
+ if (!observedVersion) {
1520
+ if (this.protocolV2FinalStatusVerified) {
1521
+ Log.warn(
1522
+ `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
1523
+ );
1524
+ } else {
1525
+ throw ERRORS.TypedError(
1526
+ HardwareErrorCode.FirmwareVerificationFailed,
1527
+ `Protocol V2 target ${target} has no observable final version after status fallback`
1528
+ );
1529
+ }
1530
+ } else if (observedVersion !== expectedVersion) {
1531
+ throw ERRORS.TypedError(
1532
+ HardwareErrorCode.FirmwareVerificationFailed,
1533
+ `Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
1534
+ );
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
1540
+ if (this.params.targetsToUpdate?.length) {
1541
+ return [...new Set(this.params.targetsToUpdate)];
1542
+ }
1543
+ if (this.params.preparedPlan?.targetsToUpdate.length) {
1544
+ return [...new Set(this.params.preparedPlan.targetsToUpdate)] as FirmwareUpdateV4Target[];
1545
+ }
1546
+ const targets = new Set<FirmwareUpdateV4Target>();
1547
+ Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
1548
+ targets.add(target as FirmwareUpdateV4Target)
1549
+ );
1550
+ if (this.params.bootloaderBinary) targets.add('boot');
1551
+ if (this.params.applicationP1Binary) targets.add('app_v1');
1552
+ if (this.params.applicationP2Binary) targets.add('app_v2');
1553
+ if (this.params.coprocessorBinary) targets.add('coprocessor');
1554
+ if (this.params.se01Binary) targets.add('se01');
1555
+ if (this.params.se02Binary) targets.add('se02');
1556
+ if (this.params.se03Binary) targets.add('se03');
1557
+ if (this.params.se04Binary) targets.add('se04');
1558
+ return Array.from(targets);
1559
+ }
1560
+
1561
+ private async getProtocolV2DeviceFeatures(): Promise<Features> {
1562
+ if (this.device.features) {
1563
+ return this.device.features;
1564
+ }
1565
+ if (typeof this.device.getFeatures === 'function') {
1566
+ const features = await this.device.getFeatures();
1567
+ if (features) return features;
1568
+ }
1569
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
1570
+ }
1571
+
1572
+ private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
1573
+ const serialNumber = deviceInfo.hw?.serial_no?.trim();
1574
+ return serialNumber || undefined;
1575
+ }
1576
+
1577
+ private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
1578
+ assertProtocolV2ReconnectIdentity(
1579
+ this.protocolV2ExpectedSerialNumber,
1580
+ this.getProtocolV2SerialNumber(deviceInfo),
1581
+ this.protocolV2ExpectedPath,
1582
+ this.device.originalDescriptor.path
1583
+ );
1584
+ }
1585
+
1586
+ private getProtocolV2DeviceInfoTimeout() {
1587
+ return this.isBleReconnect()
1588
+ ? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
1589
+ : PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
1590
+ }
1591
+
1592
+ private async requestProtocolV2PhysicalIdentity() {
1593
+ return requestProtocolV2DeviceInfo({
1594
+ commands: this.device.getCommands(),
1595
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
708
1596
  });
709
- return parseProtocolV2OkppHeader(headerBytes);
710
1597
  }
711
1598
 
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
1599
+ private async captureProtocolV2PhysicalIdentity() {
1600
+ const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
1601
+ const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
1602
+ const path = this.device.originalDescriptor?.path?.trim() || undefined;
1603
+ if (this.params?.preparedPlan) {
1604
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
1605
+ preparedPlan: this.params.preparedPlan,
1606
+ deviceIdentity: serialNumber,
1607
+ });
1608
+ } else if (this.params?.expectedDeviceId) {
1609
+ assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
1610
+ } else {
1611
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
1612
+ }
1613
+ this.protocolV2ExpectedSerialNumber = serialNumber;
1614
+ this.protocolV2ExpectedPath = path;
1615
+ }
1616
+
1617
+ private async verifyProtocolV2ReconnectIdentity() {
1618
+ const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
1619
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1620
+ return deviceInfo;
1621
+ }
1622
+
1623
+ private prepareBootloaderBinary(): ArrayBuffer | null {
1624
+ return this.params.bootloaderBinary ?? null;
1625
+ }
1626
+
1627
+ private getMissingProtocolV2FirmwareTargets(installItems: ProtocolV2InstallItem[]) {
1628
+ const preparedTargets = new Set(
1629
+ installItems.flatMap(item => {
1630
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1631
+ return target ? [target] : [];
1632
+ })
724
1633
  );
725
- if (versionSatisfied) {
726
- Log.log(`[FirmwareUpdateV4] skip Protocol V2 component ${key}; version is up to date`);
1634
+ return (this.params.targetsToUpdate ?? [])
1635
+ .filter(
1636
+ (target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
1637
+ target !== 'resource' && target !== 'boot_resources'
1638
+ )
1639
+ .filter(target => !preparedTargets.has(target));
1640
+ }
1641
+
1642
+ private buildProtocolV2InstallItems({
1643
+ bootloaderBinary,
1644
+ fwBinaryMap,
1645
+ }: {
1646
+ bootloaderBinary: ArrayBuffer | null;
1647
+ fwBinaryMap: ProtocolV2TargetBinary[];
1648
+ }): ProtocolV2InstallItem[] {
1649
+ const installItems: ProtocolV2InstallItem[] = [];
1650
+
1651
+ if (bootloaderBinary) {
1652
+ installItems.push({
1653
+ fileName: 'bootloader.bin',
1654
+ binary: bootloaderBinary,
1655
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
1656
+ kind: 'bootloader',
1657
+ });
727
1658
  }
728
- return !versionSatisfied;
1659
+
1660
+ installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
1661
+ return installItems;
1662
+ }
1663
+
1664
+ private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
1665
+ const { components } = release;
1666
+ if (!components) return [];
1667
+
1668
+ const orderedKeys = [
1669
+ ...(release.installOrder ?? []),
1670
+ ...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
1671
+ ];
1672
+
1673
+ return orderedKeys
1674
+ .map(key => {
1675
+ const component = components[key];
1676
+ return component ? ([key, component] as const) : undefined;
1677
+ })
1678
+ .filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
1679
+ }
1680
+
1681
+ private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
1682
+ const targetName = component.target?.toUpperCase();
1683
+ if (targetName === 'ROMLOADER') {
1684
+ throw ERRORS.TypedError(
1685
+ HardwareErrorCode.RuntimeError,
1686
+ PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
1687
+ );
1688
+ }
1689
+ const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
1690
+ if (!target) {
1691
+ throw ERRORS.TypedError(
1692
+ HardwareErrorCode.RuntimeError,
1693
+ `Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
1694
+ );
1695
+ }
1696
+ return target;
729
1697
  }
730
1698
 
731
1699
  private async downloadRemoteProtocolV2Component(
@@ -739,15 +1707,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
739
1707
  `Missing Protocol V2 firmware component url: ${key}/${component.target}`
740
1708
  );
741
1709
  }
1710
+ const expectedFingerprint = normalizeProtocolV2Hex(component.fingerprint);
1711
+ if (
1712
+ !Number.isSafeInteger(component.expectedSize) ||
1713
+ Number(component.expectedSize) <= 0 ||
1714
+ !expectedFingerprint ||
1715
+ !/^[0-9a-f]{64}$/u.test(expectedFingerprint)
1716
+ ) {
1717
+ throw ERRORS.TypedError(
1718
+ HardwareErrorCode.RuntimeError,
1719
+ `Protocol V2 firmware component integrity metadata is invalid: ${key}/${component.target}`,
1720
+ { firmwareUpdateCode: 'FirmwarePlanInvalid' }
1721
+ );
1722
+ }
742
1723
 
743
1724
  const { binary } = await getSysResourceBinary(component.url);
1725
+ if (binary.byteLength !== component.expectedSize) {
1726
+ throw ERRORS.TypedError(
1727
+ HardwareErrorCode.RuntimeError,
1728
+ `Protocol V2 firmware size mismatch: ${key}/${component.target}`,
1729
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
1730
+ );
1731
+ }
1732
+ if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
1733
+ throw ERRORS.TypedError(
1734
+ HardwareErrorCode.RuntimeError,
1735
+ `Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
1736
+ );
1737
+ }
1738
+ const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
1739
+ if (expectedPayloadHash) {
1740
+ const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
1741
+ if (!header || header.payloadHash !== expectedPayloadHash) {
1742
+ throw ERRORS.TypedError(
1743
+ HardwareErrorCode.RuntimeError,
1744
+ `Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
1745
+ );
1746
+ }
1747
+ }
744
1748
  return {
745
1749
  ...target,
746
1750
  binary,
747
1751
  };
748
1752
  }
749
1753
 
750
- private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
1754
+ private async prepareRemoteProtocolV2Binaries(
1755
+ firmwareType: EFirmwareType,
1756
+ features: Features,
1757
+ explicitInstallItems: ProtocolV2InstallItem[] = []
1758
+ ) {
751
1759
  const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
752
1760
 
753
1761
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -755,6 +1763,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
755
1763
  const installItems: ProtocolV2InstallItem[] = [];
756
1764
 
757
1765
  if (!release) {
1766
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
1767
+ if (missingFirmwareTargets.length > 0) {
1768
+ throw ERRORS.TypedError(
1769
+ HardwareErrorCode.RuntimeError,
1770
+ `Protocol V2 firmware release is unavailable for requested targets: ${missingFirmwareTargets.join(
1771
+ ', '
1772
+ )}`
1773
+ );
1774
+ }
758
1775
  return {
759
1776
  bootloaderBinary,
760
1777
  fwBinaryMap,
@@ -763,38 +1780,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
763
1780
  }
764
1781
 
765
1782
  const entries = this.getRemoteComponentEntries(release);
1783
+ const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
1784
+ const explicitInstallItemByTargetId = new Map(
1785
+ explicitInstallItems.map(item => [item.targetId, item] as const)
1786
+ );
1787
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
766
1788
 
767
1789
  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
- });
1790
+ const targetName = component.target?.toUpperCase();
1791
+ const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
1792
+ const updateTarget = target
1793
+ ? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
1794
+ : undefined;
1795
+ if (updateTarget && targetsToUpdate.has(updateTarget)) {
1796
+ const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
1797
+ const installItem =
1798
+ explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
1799
+ if (installItem.kind === 'bootloader') {
1800
+ bootloaderBinary = installItem.binary;
786
1801
  } else {
787
1802
  const binaryEntry = {
788
- fileName: remoteBinary.fileName,
789
- binary: remoteBinary.binary,
790
- targetId: remoteBinary.targetId,
1803
+ fileName: installItem.fileName,
1804
+ binary: installItem.binary,
1805
+ targetId: installItem.targetId,
791
1806
  };
792
1807
  fwBinaryMap.push(binaryEntry);
793
- installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
794
1808
  }
1809
+ installItems.push({ ...installItem });
1810
+ preparedTargets.add(updateTarget);
795
1811
  }
796
1812
  }
797
1813
 
1814
+ const missingTarget = Array.from(targetsToUpdate).find(
1815
+ target => target !== 'resource' && !preparedTargets.has(target)
1816
+ );
1817
+ if (missingTarget) {
1818
+ throw ERRORS.TypedError(
1819
+ HardwareErrorCode.RuntimeError,
1820
+ `Protocol V2 release does not contain requested target ${missingTarget}`
1821
+ );
1822
+ }
1823
+
798
1824
  return {
799
1825
  bootloaderBinary,
800
1826
  fwBinaryMap,
@@ -802,140 +1828,83 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
802
1828
  };
803
1829
  }
804
1830
 
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[];
1831
+ private getProtocolV2ResourceFilePath(path: string) {
1832
+ if (path.startsWith('vol')) return path;
1833
+ if (path.startsWith('/')) return `vol0:${path}`;
1834
+ return `vol0:/${path}`;
843
1835
  }
844
1836
 
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 };
1837
+ private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
1838
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1839
+ const filePath = this.getProtocolV2ResourceFilePath(path);
1840
+ const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
1841
+ path: filePath,
1842
+ });
1843
+ const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
1844
+ if (
1845
+ !pathInfoRes.message?.exist ||
1846
+ pathInfoRes.message?.directory ||
1847
+ fileSize === undefined ||
1848
+ fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE ||
1849
+ (expectedSize !== undefined && fileSize !== expectedSize)
1850
+ ) {
1851
+ return null;
885
1852
  }
886
1853
 
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,
1854
+ const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
1855
+ const chunks: Uint8Array[] = [];
1856
+ let offset = 0;
1857
+ while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
1858
+ const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
1859
+ const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
1860
+ file: {
1861
+ path: filePath,
1862
+ offset,
1863
+ total_size: 0,
1864
+ },
1865
+ chunk_len: readLen,
1866
+ ui_percentage: undefined,
902
1867
  });
1868
+ const data = toProtocolV2Bytes(res.message?.data);
1869
+ if (data.byteLength === 0) return null;
1870
+ chunks.push(data);
1871
+ offset += data.byteLength;
903
1872
  }
904
1873
 
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 };
1874
+ const headerBytes = new Uint8Array(offset);
1875
+ let cursor = 0;
1876
+ chunks.forEach(chunk => {
1877
+ headerBytes.set(chunk, cursor);
1878
+ cursor += chunk.byteLength;
1879
+ });
1880
+ return parseProtocolV2OkppHeader(headerBytes);
912
1881
  }
913
1882
 
914
- /**
915
- * 比对设备上已有 okpkg 的 OKPP header(仅远端配置模式使用)。
916
- */
1883
+ /** Compare the downloaded and installed okpkg headers before transferring a resource. */
917
1884
  private async isProtocolV2ResourceBundleUpToDate(
918
- bundle: ProtocolV2ResourceBundleBinary
1885
+ bundle: Pick<
1886
+ ProtocolV2ResourceBundleSource,
1887
+ 'name' | 'source' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1888
+ >
919
1889
  ): Promise<boolean> {
920
- if (this.params.forcedUpdateRes) return false;
921
- if (!bundle.version && !bundle.payloadHash) return false;
1890
+ if (this.params?.forcedUpdateRes) return false;
1891
+ if (!bundle.payloadHash || !bundle.headerHash) return false;
922
1892
 
923
1893
  try {
924
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
1894
+ const header = await this.readProtocolV2DeviceFileHeader(
1895
+ bundle.devicePath,
1896
+ bundle.source.size
1897
+ );
925
1898
  if (!header) return false;
926
1899
 
927
1900
  if (bundle.version) {
928
1901
  const cmp = compareProtocolV2Versions(header.version, bundle.version);
929
1902
  if (cmp === undefined || cmp !== 0) return false;
930
1903
  }
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
- }
1904
+ const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
1905
+ const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
1906
+ if (!expectedPayloadHash || header.payloadHash !== expectedPayloadHash) return false;
1907
+ if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
939
1908
  return true;
940
1909
  } catch (error) {
941
1910
  Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
@@ -945,23 +1914,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
945
1914
 
946
1915
  private isProtocolV2BootloaderMode() {
947
1916
  if (typeof this.device.isBootloader === 'function') {
948
- return this.device.isBootloader();
1917
+ return !!this.device.isBootloader();
1918
+ }
1919
+ return (
1920
+ this.device.features?.mode === 'bootloader' ||
1921
+ (this.device.features?.mode == null && !!this.device.features?.bootloaderMode)
1922
+ );
1923
+ }
1924
+
1925
+ private isProtocolV2RomloaderMode() {
1926
+ const deviceType = this.device.getCurrentDeviceType();
1927
+ if (
1928
+ !this.device.isProtocolV2() ||
1929
+ (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
1930
+ ) {
1931
+ return false;
1932
+ }
1933
+ if (typeof this.device.isRomloader === 'function') {
1934
+ return this.device.isRomloader();
949
1935
  }
950
- return !!this.device.features?.bootloaderMode;
1936
+ return this.device.features?.mode === 'romloader';
951
1937
  }
952
1938
 
953
1939
  async enterProtocolV2BootloaderMode() {
1940
+ // romloader is the first update environment and forwards targets to bootloader.
1941
+ // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
1942
+ if (this.isProtocolV2RomloaderMode()) {
1943
+ Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
1944
+ this.protocolV2ExecutionInLoader = true;
1945
+ return false;
1946
+ }
954
1947
  if (this.isProtocolV2BootloaderMode()) {
955
- Log.debug('Protocol V2 device is already in bootloader mode, skip reboot to bootloader');
1948
+ Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
1949
+ this.protocolV2ExecutionInLoader = true;
1950
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
956
1951
  return false;
957
1952
  }
958
1953
 
959
1954
  try {
960
1955
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
961
1956
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
962
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
963
1957
  await wait(1000);
964
1958
  await this.waitForProtocolV2BootloaderMode();
1959
+ this.protocolV2ExecutionInLoader = true;
1960
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
965
1961
  return true;
966
1962
  } catch (error) {
967
1963
  if (error instanceof HardwareError) {
@@ -978,20 +1974,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
978
1974
  ) {
979
1975
  const startTime = Date.now();
980
1976
  let lastError: unknown;
1977
+ let shouldReconnect = true;
981
1978
 
982
1979
  while (Date.now() - startTime < timeout) {
983
1980
  try {
984
- await this.reconnectProtocolV2Device();
1981
+ if (shouldReconnect) {
1982
+ await this.reconnectProtocolV2Device();
1983
+ shouldReconnect = false;
1984
+ }
985
1985
  const deviceInfo = await requestProtocolV2DeviceInfo({
986
1986
  commands: this.device.getCommands(),
987
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1987
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
988
1988
  });
989
- const features = this.device.updateProtocolV2Features(deviceInfo);
990
- if (features?.bootloaderMode) {
1989
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
1990
+ const features = await this.device.probeProtocolV2RuntimeState(
1991
+ deviceInfo,
1992
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
1993
+ );
1994
+ if (features?.mode === 'bootloader') {
991
1995
  return features;
992
1996
  }
993
1997
  lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
994
1998
  } catch (error) {
1999
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2000
+ throw error;
2001
+ }
2002
+ shouldReconnect = true;
995
2003
  lastError = error;
996
2004
  Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
997
2005
  }
@@ -1007,8 +2015,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1007
2015
  }
1008
2016
 
1009
2017
  /**
1010
- * 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
1011
- * 文件名仅用于 staging 路径展示,target_id 已显式给定。
2018
+ * Collect explicit target binaries grouped by DeviceFirmwareTargetType.
2019
+ * Filenames are display-only staging paths because target_id is explicit.
1012
2020
  */
1013
2021
  private collectExplicitTargetBinaries() {
1014
2022
  const entries: ProtocolV2TargetBinary[] = [];
@@ -1044,115 +2052,277 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1044
2052
  return entries;
1045
2053
  }
1046
2054
 
2055
+ private buildProtocolV2ExecutionPhases({
2056
+ installSources,
2057
+ resourceSources,
2058
+ }: {
2059
+ installSources: ProtocolV2InstallSource[];
2060
+ resourceSources: ProtocolV2ResourceBundleSource[];
2061
+ }): ProtocolV2ExecutionPhase[] {
2062
+ const phases: ProtocolV2ExecutionPhase[] = [];
2063
+ const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
2064
+ if (bootloaderSources.length > 0) {
2065
+ phases.push(
2066
+ {
2067
+ kind: 'bootloader-install',
2068
+ installSources: bootloaderSources,
2069
+ resourceSources: [],
2070
+ },
2071
+ {
2072
+ kind: 'bootloader-verify',
2073
+ installSources: [],
2074
+ resourceSources: [],
2075
+ }
2076
+ );
2077
+ }
2078
+ if (resourceSources.length > 0) {
2079
+ phases.push({
2080
+ kind: 'resource-sync',
2081
+ installSources: [],
2082
+ resourceSources,
2083
+ });
2084
+ }
2085
+ const componentSources = installSources.filter(source => source.kind !== 'bootloader');
2086
+ if (componentSources.length > 0) {
2087
+ phases.push({
2088
+ kind: 'component-install',
2089
+ installSources: componentSources,
2090
+ resourceSources: [],
2091
+ });
2092
+ }
2093
+ phases.push({
2094
+ kind: 'final-verify',
2095
+ installSources: [],
2096
+ resourceSources: [],
2097
+ });
2098
+ return phases;
2099
+ }
2100
+
2101
+ private async executeProtocolV2Phases({
2102
+ installSources,
2103
+ resourceSources,
2104
+ }: {
2105
+ installSources: ProtocolV2InstallSource[];
2106
+ resourceSources: ProtocolV2ResourceBundleSource[];
2107
+ }) {
2108
+ const phases = this.buildProtocolV2ExecutionPhases({
2109
+ installSources,
2110
+ resourceSources,
2111
+ });
2112
+ for (const phase of phases) {
2113
+ if (phase.kind === 'final-verify') {
2114
+ return this.completeProtocolV2FinalVerification();
2115
+ }
2116
+ if (phase.kind === 'resource-sync') {
2117
+ await this.enterProtocolV2BootloaderMode();
2118
+ await this.executeProtocolV2TransferPhase(phase);
2119
+ } else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
2120
+ await this.enterProtocolV2BootloaderMode();
2121
+ await this.executeProtocolV2TransferPhase(phase);
2122
+ await this.exitProtocolV2BootloaderToNormal();
2123
+ } else if (phase.kind === 'bootloader-verify') {
2124
+ await this.waitForProtocolV2FinalFeatures();
2125
+ this.assertExpectedProtocolV2Versions(['boot']);
2126
+ }
2127
+ }
2128
+ throw ERRORS.TypedError(
2129
+ HardwareErrorCode.RuntimeError,
2130
+ 'Protocol V2 execution has no final verification phase'
2131
+ );
2132
+ }
2133
+
2134
+ private async executeProtocolV2SourceUpdate({
2135
+ installSources,
2136
+ resourceSources,
2137
+ }: {
2138
+ installSources: ProtocolV2InstallSource[];
2139
+ resourceSources: ProtocolV2ResourceBundleSource[];
2140
+ }) {
2141
+ return this.executeProtocolV2Phases({
2142
+ installSources,
2143
+ resourceSources,
2144
+ });
2145
+ }
2146
+
1047
2147
  private async executeProtocolV2Update({
1048
2148
  fwBinaryMap,
1049
2149
  bootloaderBinary,
1050
2150
  installItems,
1051
- resourceBundles,
2151
+ resourceBundles = [],
1052
2152
  }: {
1053
2153
  fwBinaryMap?: ProtocolV2TargetBinary[];
1054
2154
  bootloaderBinary?: ArrayBuffer | null;
1055
2155
  installItems?: ProtocolV2InstallItem[];
1056
2156
  resourceBundles?: ProtocolV2ResourceBundleBinary[];
1057
2157
  }) {
1058
- const orderedInstallItems =
2158
+ const memoryInstallItems =
1059
2159
  installItems ??
1060
2160
  this.buildProtocolV2InstallItems({
1061
- bootloaderBinary: bootloaderBinary ?? null,
1062
2161
  fwBinaryMap: fwBinaryMap ?? [],
2162
+ bootloaderBinary: bootloaderBinary ?? null,
1063
2163
  });
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
2164
+ try {
2165
+ const installSources = await Promise.all(
2166
+ memoryInstallItems.map(async item => ({
2167
+ fileName: item.fileName,
2168
+ source: await this.openProtocolV2MemorySource(item.binary),
2169
+ targetId: item.targetId,
2170
+ kind: item.kind,
2171
+ }))
1076
2172
  );
1077
- processedSize = resourceTransfer.processedSize;
1078
- totalSize = resourceTransfer.totalSize;
2173
+ const resourceSources = await Promise.all(
2174
+ resourceBundles.map(async resource => ({
2175
+ name: resource.name,
2176
+ source: await this.openProtocolV2MemorySource(resource.binary),
2177
+ devicePath: resource.devicePath,
2178
+ ...(resource.version ? { version: resource.version } : {}),
2179
+ ...(resource.payloadHash ? { payloadHash: resource.payloadHash } : {}),
2180
+ ...(resource.headerHash ? { headerHash: resource.headerHash } : {}),
2181
+ }))
2182
+ );
2183
+ return await this.executeProtocolV2Phases({
2184
+ installSources,
2185
+ resourceSources,
2186
+ });
2187
+ } finally {
2188
+ await this.closeProtocolV2PreparedSources();
1079
2189
  }
2190
+ }
1080
2191
 
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');
2192
+ private async executeProtocolV2TransferPhase({
2193
+ installSources,
2194
+ resourceSources,
2195
+ }: ProtocolV2ExecutionPhase) {
2196
+ let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
2197
+ const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
2198
+ for (const resource of resourceSources) {
2199
+ if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
2200
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
2201
+ } else {
2202
+ resourcesToSync.push(resource);
2203
+ totalSize += resource.source.size;
1086
2204
  }
1087
- return;
1088
2205
  }
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
2206
 
1118
- stagedInstallTargets.push({
1119
- ...item,
1120
- path: filePath,
1121
- });
1122
- }
2207
+ this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
2208
+ let processedSize = 0;
2209
+ for (const resource of resourcesToSync) {
2210
+ // The bootloader keeps its live resource package mounted. FatFs rejects
2211
+ // replacing an open file, so early boot promotes this staging file before mounting it.
2212
+ const writePath = resolveProtocolV2ResourceWritePath(resource.devicePath);
2213
+ processedSize = await this.protocolV2SourceUpdateProcess({
2214
+ source: resource.source,
2215
+ filePath: writePath,
2216
+ processedSize,
2217
+ totalSize,
2218
+ });
2219
+ await this.verifyProtocolV2StagedFile(writePath, resource.source.size);
2220
+ }
1123
2221
 
1124
- if (totalSize > 0) {
1125
- this.postProgressMessage(100, 'transferData');
1126
- }
2222
+ const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
2223
+ for (const item of installSources) {
2224
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
2225
+ processedSize = await this.protocolV2SourceUpdateProcess({
2226
+ source: item.source,
2227
+ filePath,
2228
+ processedSize,
2229
+ totalSize,
2230
+ });
2231
+ await this.verifyProtocolV2StagedFile(filePath, item.source.size);
2232
+ stagedInstallTargets.push({
2233
+ targetId: item.targetId,
2234
+ path: filePath,
2235
+ });
2236
+ }
1127
2237
 
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;
2238
+ if (totalSize > 0) {
2239
+ this.postProgressMessage(100, 'transferData');
2240
+ }
2241
+ if (stagedInstallTargets.length === 0) {
2242
+ return;
1142
2243
  }
1143
2244
 
1144
2245
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
1145
-
1146
- const allTargets = stagedInstallTargets.map(item => ({
2246
+ const targets = stagedInstallTargets.map(item => ({
1147
2247
  target_id: item.targetId,
1148
2248
  path: item.path,
1149
2249
  }));
1150
- Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1151
- const startResponse = await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1152
- await this.waitForProtocolV2FirmwareUpdateComplete(allTargets, startResponse);
2250
+ await this.protocolV2StartFirmwareUpdate({ targets });
2251
+ await this.waitForProtocolV2FirmwareUpdateComplete(targets);
2252
+ }
2253
+
2254
+ private async protocolV2SourceUpdateProcess({
2255
+ source,
2256
+ filePath,
2257
+ processedSize,
2258
+ totalSize,
2259
+ }: {
2260
+ source: FirmwareByteSource;
2261
+ filePath: string;
2262
+ processedSize: number;
2263
+ totalSize: number;
2264
+ }) {
2265
+ let lastError: unknown;
2266
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
2267
+ try {
2268
+ const transferStartedAt = Date.now();
2269
+ await writeFirmwareByteSource({
2270
+ source,
2271
+ chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
2272
+ write: async ({ data, sourceOffset, length, first }) => {
2273
+ const chunkEnd = sourceOffset + length;
2274
+ const deviceProgress = getProtocolV2DeviceTransferProgress(
2275
+ processedSize + sourceOffset,
2276
+ processedSize + chunkEnd,
2277
+ totalSize
2278
+ );
2279
+ const response = await this.fileWriteChunk(
2280
+ filePath,
2281
+ source.size,
2282
+ sourceOffset,
2283
+ data,
2284
+ first,
2285
+ deviceProgress
2286
+ );
2287
+ const rawProcessedByte = response.message.processed_byte;
2288
+ const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
2289
+ if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
2290
+ throw ERRORS.TypedError(
2291
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2292
+ `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
2293
+ );
2294
+ }
2295
+ const transferredBytes = processedSize + chunkEnd;
2296
+ const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
2297
+ this.postProgressMessage(
2298
+ Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
2299
+ 'transferData',
2300
+ {
2301
+ transferredBytes,
2302
+ totalBytes: totalSize,
2303
+ rateBytesPerSecond:
2304
+ elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
2305
+ elapsedMs,
2306
+ }
2307
+ );
2308
+ return length;
2309
+ },
2310
+ });
2311
+ return processedSize + source.size;
2312
+ } catch (error) {
2313
+ lastError = error;
2314
+ if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
2315
+ await this.recoverProtocolV2FileTransfer();
2316
+ }
2317
+ }
2318
+ }
2319
+ throw ERRORS.TypedError(
2320
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
2321
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
2322
+ );
1153
2323
  }
1154
2324
 
1155
- private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
2325
+ private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
1156
2326
  return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
1157
2327
  }
1158
2328
 
@@ -1169,168 +2339,315 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1169
2339
  }
1170
2340
  }
1171
2341
 
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
2342
  private assertProtocolV2TargetStatus(
1215
2343
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1216
- expectedTargetIds: Set<number>
2344
+ expectedTargetIds: Set<number>,
2345
+ expectedPaths = new Map<number, string>()
1217
2346
  ) {
2347
+ Log.log(
2348
+ `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
2349
+ );
1218
2350
  const failedTarget = statusTargets.find(
1219
2351
  target =>
1220
2352
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1221
2353
  isProtocolV2TargetStatusFailed(target.status)
1222
2354
  );
1223
2355
  if (failedTarget) {
2356
+ const failedTargetDetails = JSON.stringify({
2357
+ targetId: failedTarget.target_id,
2358
+ status: failedTarget.status,
2359
+ payloadVersion: failedTarget.payload_version,
2360
+ path: failedTarget.path,
2361
+ });
2362
+ Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
1224
2363
  throw ERRORS.TypedError(
1225
2364
  HardwareErrorCode.FirmwareError,
1226
- `Protocol V2 firmware target ${failedTarget.target_id} failed`
2365
+ `Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
2366
+ failedTarget.status ?? 'unknown'
2367
+ } payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
2368
+ failedTarget.path ?? 'unknown'
2369
+ }`
1227
2370
  );
1228
2371
  }
1229
2372
 
1230
- const completedTargets = statusTargets.filter(
1231
- target =>
1232
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1233
- isProtocolV2TargetStatusFinished(target.status)
2373
+ const matchingTargets = statusTargets.filter(target =>
2374
+ expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
2375
+ );
2376
+ const seenTargetIds = new Set<number>();
2377
+ for (const target of matchingTargets) {
2378
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2379
+ if (
2380
+ targetId === undefined ||
2381
+ seenTargetIds.has(targetId) ||
2382
+ (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
2383
+ ) {
2384
+ throw ERRORS.TypedError(
2385
+ HardwareErrorCode.RuntimeError,
2386
+ `Protocol V2 install status conflicts with target ${target.target_id}`,
2387
+ {
2388
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
2389
+ }
2390
+ );
2391
+ }
2392
+ seenTargetIds.add(targetId);
2393
+ }
2394
+ const completedTargets = matchingTargets.filter(target =>
2395
+ isProtocolV2TargetStatusFinished(target.status)
1234
2396
  );
1235
- if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
2397
+ const completedTargetIds = new Set<number>();
2398
+ completedTargets.forEach(target => {
2399
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2400
+ if (targetId !== undefined) {
2401
+ completedTargetIds.add(targetId);
2402
+ }
2403
+ });
2404
+ if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
2405
+ for (const target of completedTargets) {
2406
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2407
+ const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
2408
+ if (targetId === undefined) {
2409
+ throw ERRORS.TypedError(
2410
+ HardwareErrorCode.RuntimeError,
2411
+ 'Protocol V2 completed target identity is invalid'
2412
+ );
2413
+ }
2414
+ const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
2415
+ if (expectedVersion !== undefined && payloadVersion !== undefined) {
2416
+ if (payloadVersion !== expectedVersion) {
2417
+ throw ERRORS.TypedError(
2418
+ HardwareErrorCode.FirmwareVerificationFailed,
2419
+ `Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
2420
+ );
2421
+ }
2422
+ }
2423
+ if (payloadVersion !== undefined) {
2424
+ this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
2425
+ }
2426
+ }
2427
+ this.postProgressMessage(100, 'installingFirmware');
1236
2428
  return true;
1237
2429
  }
1238
2430
 
1239
- const inProgressTarget = statusTargets.find(
1240
- target =>
1241
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
2431
+ if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
2432
+ const hasInProgressTarget = matchingTargets.some(target =>
1242
2433
  isProtocolV2TargetStatusInProgress(target.status)
1243
- );
1244
- if (inProgressTarget) {
1245
- this.postProgressMessage(99, 'installingFirmware');
2434
+ );
2435
+ const completedProgress = Math.floor(
2436
+ (completedTargetIds.size / expectedTargetIds.size) * 100
2437
+ );
2438
+ // The protocol exposes no per-target percentage, so report coarse progress by
2439
+ // completed targets and use 1% once work starts to keep the UI responsive.
2440
+ const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
2441
+ this.postProgressMessage(progress, 'installingFirmware');
1246
2442
  }
1247
2443
 
1248
2444
  return false;
1249
2445
  }
1250
2446
 
1251
- private async waitForProtocolV2FirmwareUpdateComplete(
1252
- targets: Array<{ target_id: number; path: string }>,
1253
- startResponse?: ProtocolV2FirmwareUpdateStartResponse
2447
+ private getProtocolV2MissingTargetIds(
2448
+ statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
2449
+ expectedTargetIds: Set<number>
1254
2450
  ) {
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;
2451
+ const reportedTargetIds = new Set<number>();
2452
+ statusTargets.forEach(target => {
2453
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2454
+ if (targetId !== undefined) {
2455
+ reportedTargetIds.add(targetId);
1261
2456
  }
1262
- }
2457
+ });
2458
+ return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
2459
+ }
1263
2460
 
2461
+ private async waitForProtocolV2FirmwareUpdateComplete(
2462
+ targets: Array<{ target_id: number; path: string }>
2463
+ ) {
2464
+ this.protocolV2FinalStatusVerified = false;
2465
+ const expectedTargetIds = new Set(targets.map(target => target.target_id));
2466
+ const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
1264
2467
  const startTime = Date.now();
1265
2468
  let lastError: unknown;
2469
+ let shouldReconnect = true;
2470
+ let deviceInfo: ProtocolV2DeviceInfo | undefined;
2471
+ let missingTargetStatusSince: number | undefined;
2472
+ let missingTargetStatusKey: string | undefined;
2473
+ const resetMissingTargetStatusGrace = () => {
2474
+ missingTargetStatusSince = undefined;
2475
+ missingTargetStatusKey = undefined;
2476
+ };
1266
2477
 
1267
2478
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
1268
2479
  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;
2480
+ if (shouldReconnect) {
2481
+ await this.reconnectProtocolV2Device();
2482
+ deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
2483
+ shouldReconnect = false;
1279
2484
  }
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()) {
2485
+ const currentDeviceInfo = deviceInfo;
2486
+ try {
2487
+ const statusResponse = await this.device.getCommands().typedCall(
2488
+ 'DeviceFirmwareUpdateStatusGet',
2489
+ 'DeviceFirmwareUpdateStatus',
2490
+ {
2491
+ fields: {
2492
+ status: true,
2493
+ payload_version: true,
2494
+ path: true,
2495
+ },
2496
+ },
2497
+ { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
2498
+ );
2499
+ const statusTargets = (statusResponse.message.records ??
2500
+ []) as ProtocolV2FirmwareUpdateStatusTarget[];
2501
+ if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
2502
+ this.protocolV2FinalStatusVerified = true;
2503
+ return;
2504
+ }
2505
+
2506
+ if (
2507
+ statusTargets.length === 0 &&
2508
+ currentDeviceInfo &&
2509
+ (await this.probeProtocolV2NormalMode(currentDeviceInfo))
2510
+ ) {
2511
+ Log.log(
2512
+ '[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
2513
+ );
2514
+ this.postProgressMessage(100, 'installingFirmware');
2515
+ return;
2516
+ }
2517
+
2518
+ const missingTargetIds = this.getProtocolV2MissingTargetIds(
2519
+ statusTargets,
2520
+ expectedTargetIds
2521
+ );
2522
+ if (missingTargetIds.length > 0) {
2523
+ const now = Date.now();
2524
+ const missingKey = missingTargetIds.join(',');
2525
+ if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
2526
+ missingTargetStatusSince = now;
2527
+ missingTargetStatusKey = missingKey;
2528
+ } else if (
2529
+ now - missingTargetStatusSince >=
2530
+ PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
2531
+ ) {
2532
+ const reportedTargetIds = statusTargets
2533
+ .map(target => normalizeProtocolV2TargetId(target.target_id))
2534
+ .filter((targetId): targetId is number => targetId !== undefined);
2535
+ throw ERRORS.TypedError(
2536
+ HardwareErrorCode.FirmwareError,
2537
+ `Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
2538
+ ','
2539
+ )}`
2540
+ );
2541
+ }
2542
+ lastError = new Error(
2543
+ `Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
2544
+ );
2545
+ } else {
2546
+ resetMissingTargetStatusGrace();
2547
+ lastError = new Error('Protocol V2 firmware targets are still installing');
2548
+ }
2549
+ } catch (error) {
2550
+ if (isProtocolV2TerminalInstallStatusError(error)) {
2551
+ throw error;
2552
+ }
2553
+ // Only consecutive full status dumps can prove that a record is absent.
2554
+ // A reboot or transport interruption starts a fresh grace window.
2555
+ resetMissingTargetStatusGrace();
2556
+ // App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
2557
+ // missing endpoint as completion only after the runtime probe confirms App mode.
2558
+ if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
2559
+ if (!currentDeviceInfo) {
2560
+ throw ERRORS.TypedError(
2561
+ HardwareErrorCode.RuntimeError,
2562
+ 'Protocol V2 device identity is unavailable during install polling'
2563
+ );
2564
+ }
2565
+ if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
2566
+ Log.log(
2567
+ '[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
2568
+ );
2569
+ this.postProgressMessage(100, 'installingFirmware');
1285
2570
  return;
1286
2571
  }
1287
- } catch (reconnectError) {
1288
- lastError = reconnectError;
2572
+ lastError = new Error(
2573
+ 'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
2574
+ );
2575
+ } else {
2576
+ shouldReconnect = true;
2577
+ deviceInfo = undefined;
2578
+ lastError = error;
1289
2579
  Log.log(
1290
- 'Protocol V2 firmware install reconnect/normal-mode probe failed: ',
1291
- reconnectError
2580
+ '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
2581
+ error
1292
2582
  );
1293
2583
  }
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
2584
  }
2585
+ } catch (error) {
2586
+ lastError = error;
2587
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2588
+ throw error;
2589
+ }
2590
+ if (isProtocolV2TerminalInstallStatusError(error)) {
2591
+ throw error;
2592
+ }
2593
+ if (
2594
+ error instanceof HardwareError &&
2595
+ error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
2596
+ ) {
2597
+ throw error;
2598
+ }
2599
+ shouldReconnect = true;
2600
+ deviceInfo = undefined;
2601
+ resetMissingTargetStatusGrace();
2602
+ Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
1302
2603
  }
1303
2604
  await wait(1000);
1304
2605
  }
1305
2606
 
1306
2607
  throw ERRORS.TypedError(
1307
2608
  HardwareErrorCode.RuntimeError,
1308
- `Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
2609
+ `Protocol V2 firmware update status not complete within ${
2610
+ PROTOCOL_V2_INSTALL_TIMEOUT / 1000
2611
+ }s: ${this.normalizeErrorMessage(lastError)}`
1309
2612
  );
1310
2613
  }
1311
2614
 
1312
2615
  private async exitProtocolV2BootloaderToNormal() {
1313
- this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
2616
+ await this.reconnectProtocolV2Device();
2617
+ const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
1314
2618
  try {
1315
- await this.reconnectProtocolV2Device();
1316
- if (await this.probeProtocolV2NormalMode()) {
1317
- Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
2619
+ if (await this.probeProtocolV2NormalMode(deviceInfo)) {
2620
+ Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
1318
2621
  return;
1319
2622
  }
1320
2623
  } catch (error) {
1321
- Log.log('Protocol V2 normal-mode probe before reboot failed: ', error);
2624
+ Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
1322
2625
  }
1323
2626
  await this.protocolV2Reboot(DeviceRebootType.Normal);
2627
+ this.protocolV2ExecutionInLoader = false;
2628
+ }
2629
+
2630
+ private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
2631
+ const features = await this.device.probeProtocolV2RuntimeState(
2632
+ deviceInfo,
2633
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2634
+ );
2635
+ return features.mode === 'normal' && !features.bootloaderMode;
1324
2636
  }
1325
2637
 
1326
2638
  private async waitForProtocolV2FinalFeatures() {
1327
2639
  const features = await this.waitForProtocolV2ReconnectAndFeatures(
1328
- PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT
2640
+ PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1329
2641
  );
1330
2642
 
2643
+ return this.getProtocolV2VersionResult(features);
2644
+ }
2645
+
2646
+ private getProtocolV2VersionResult(features: Features) {
1331
2647
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1332
2648
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1333
2649
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
2650
+ this.protocolV2LatestFinalFeatures = features;
1334
2651
  if (firmwareVersion === '0.0.0') {
1335
2652
  Log.warn(
1336
2653
  '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 +2661,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1344
2661
  };
1345
2662
  }
1346
2663
 
2664
+ private async completeProtocolV2FinalVerification() {
2665
+ if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
2666
+ await this.exitProtocolV2BootloaderToNormal();
2667
+ }
2668
+ const versions = await this.waitForProtocolV2FinalFeatures();
2669
+ this.assertExpectedProtocolV2Versions();
2670
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
2671
+ DevicePool.resetState();
2672
+ return versions;
2673
+ }
2674
+
1347
2675
  private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
1348
2676
  const startTime = Date.now();
1349
2677
  let lastError: unknown;
2678
+ let shouldReconnect = true;
1350
2679
 
1351
2680
  while (Date.now() - startTime < timeout) {
1352
2681
  try {
1353
- await this.reconnectProtocolV2Device();
2682
+ if (shouldReconnect) {
2683
+ await this.reconnectProtocolV2Device();
2684
+ shouldReconnect = false;
2685
+ }
1354
2686
  const deviceInfo = await requestProtocolV2DeviceInfo({
1355
2687
  commands: this.device.getCommands(),
1356
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1357
- // 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
2688
+ timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
2689
+ // Completion needs target versions only; keep scope aligned with the request.
1358
2690
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
1359
2691
  });
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
- );
2692
+ this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2693
+ const features = await this.device.probeProtocolV2RuntimeState(
2694
+ deviceInfo,
2695
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2696
+ );
2697
+ if (features.mode === 'normal' && !features.bootloaderMode) {
2698
+ return features;
1366
2699
  }
1367
- return features;
2700
+ lastError = ERRORS.TypedError(
2701
+ HardwareErrorCode.DeviceNotFound,
2702
+ 'Protocol V2 device is still in bootloader mode'
2703
+ );
1368
2704
  } catch (error) {
2705
+ if (this.isProtocolV2ReconnectIdentityError(error)) {
2706
+ throw error;
2707
+ }
2708
+ shouldReconnect = true;
1369
2709
  lastError = error;
1370
2710
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
1371
- await wait(1000);
1372
2711
  }
2712
+ await wait(1000);
1373
2713
  }
1374
2714
 
1375
2715
  throw ERRORS.TypedError(
@@ -1393,130 +2733,71 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1393
2733
  DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
1394
2734
  devicesDescriptor.length === 1
1395
2735
  ) {
2736
+ const descriptor = devicesDescriptor[0];
2737
+ if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
2738
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
2739
+ }
1396
2740
  this.device.updateDescriptor(
1397
2741
  {
1398
- ...devicesDescriptor[0],
2742
+ ...descriptor,
1399
2743
  protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
1400
2744
  },
1401
2745
  true
1402
2746
  );
1403
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
2747
+ await this.ensureProtocolV2DeviceAcquired();
1404
2748
  this.device.commands.disposed = false;
1405
2749
  this.device.getCommands().mainId = this.device.mainId ?? '';
1406
- await this.device.initialize();
1407
2750
  return;
1408
2751
  }
1409
2752
 
1410
- // App bootloader 序列号暂时可能不一致。V4 升级重连阶段只接受唯一枚举设备,
1411
- // 避免继续按旧 app connectId 查缓存导致反复输出 path mismatch 日志。
2753
+ // Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
2754
+ // Enumeration order is not stable and must never decide the reconnect target.
1412
2755
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
1413
2756
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1414
2757
  });
1415
- if (deviceList.length !== 1) {
2758
+ const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
2759
+ const expectedPath = this.protocolV2ExpectedPath?.trim();
2760
+ const reconnectDevice = deviceList.find(candidate => {
2761
+ const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
2762
+ if (expectedSerialNumber && candidateSerialNumber) {
2763
+ return candidateSerialNumber === expectedSerialNumber;
2764
+ }
2765
+ return !!expectedPath && candidate.getConnectId() === expectedPath;
2766
+ });
2767
+ if (!reconnectDevice) {
1416
2768
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1417
2769
  }
1418
2770
 
1419
2771
  Log.debug(
1420
- 'Protocol V2 firmware reconnect using single enumerated device:',
1421
- deviceList[0].getConnectId()
2772
+ 'Protocol V2 firmware reconnect using matched device:',
2773
+ reconnectDevice.getConnectId()
1422
2774
  );
1423
- this.device.updateFromCache(deviceList[0]);
1424
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
2775
+ this.device.updateFromCache(reconnectDevice);
2776
+ await this.ensureProtocolV2DeviceAcquired();
1425
2777
  this.device.commands.disposed = false;
1426
2778
  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
2779
  }
1453
2780
 
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');
2781
+ /**
2782
+ * After acquiring a USB session, polling reuses its command channel. Reacquire only
2783
+ * after reboot or enumeration changes cause hasDeviceAcquire() to become false.
2784
+ */
2785
+ private async ensureProtocolV2DeviceAcquired() {
2786
+ const commands = this.device.getCommands();
2787
+ if (this.device.hasDeviceAcquire() && !commands.disposed) {
2788
+ return;
1503
2789
  }
1504
-
1505
- return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
2790
+ await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, {
2791
+ throwOnRunPromiseError: true,
2792
+ });
1506
2793
  }
1507
2794
 
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';
2795
+ private isProtocolV2ReconnectIdentityError(error: unknown) {
2796
+ // A serial can be temporarily unavailable while Loader starts, and BLE has no stable path.
2797
+ // Keep polling unless the device explicitly reports a different serial.
2798
+ return this.normalizeErrorMessage(error).includes(
2799
+ 'Protocol V2 reconnect physical identity mismatch'
2800
+ );
1520
2801
  }
1521
2802
 
1522
2803
  private async fileWriteChunk(
@@ -1528,17 +2809,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1528
2809
  progress: number | null
1529
2810
  ): Promise<TypedResponseMessage<'FilesystemFile'>> {
1530
2811
  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,
2812
+ const writeRes = await typedCall(
2813
+ 'FilesystemFileWrite',
2814
+ 'FilesystemFile',
2815
+ {
2816
+ file: {
2817
+ path: filePath,
2818
+ offset,
2819
+ total_size: totalFileSize,
2820
+ data: chunk,
2821
+ },
2822
+ overwrite,
2823
+ append: false,
2824
+ ui_percentage: progress ?? undefined,
1537
2825
  },
1538
- overwrite,
1539
- append: false,
1540
- ui_percentage: progress ?? undefined,
1541
- });
2826
+ {
2827
+ writeWithResponse: false,
2828
+ onWriteCompleted: () => undefined,
2829
+ }
2830
+ );
1542
2831
  if (writeRes.type !== 'FilesystemFile') {
1543
2832
  if ((writeRes as any).type === 'CallMethodError') {
1544
2833
  if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
@@ -1554,9 +2843,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1554
2843
  const env = DataManager.getSettings('env');
1555
2844
  if (DataManager.isBleConnect(env)) {
1556
2845
  await wait(3000);
1557
- await this.acquireProtocolV2BleDevice();
1558
- await this.device.initialize();
1559
2846
  }
2847
+ await this.reconnectProtocolV2Device();
2848
+ await this.verifyProtocolV2ReconnectIdentity();
2849
+ await this.device.initialize();
1560
2850
  await wait(2000);
1561
2851
  }
1562
2852
 
@@ -1575,35 +2865,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1575
2865
  targets: Array<{ target_id: number; path: string }>;
1576
2866
  }) {
1577
2867
  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
- }
2868
+ const response: ProtocolV2FirmwareUpdateStartResponse = await commands.typedCall(
2869
+ 'DeviceFirmwareUpdateRequest',
2870
+ 'Success',
2871
+ { targets },
2872
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
2873
+ );
2874
+ // Success acknowledges that the device accepted installation. End confirmation
2875
+ // and begin status polling only after receiving this ACK.
1606
2876
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
2877
+ this.postProgressMessage(0, 'installingFirmware');
1607
2878
  return response;
1608
2879
  }
1609
2880
 
@@ -1613,9 +2884,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1613
2884
  const res = await typedCall('DeviceReboot', 'Success', {
1614
2885
  reboot_type: rebootType,
1615
2886
  });
2887
+ this.device.markProtocolV2Reboot(rebootType);
1616
2888
  return res.message;
1617
2889
  } catch (error) {
1618
2890
  if (isProtocolV2DeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
2891
+ this.device.markProtocolV2Reboot(rebootType);
1619
2892
  return { message: 'Device rebooted successfully' };
1620
2893
  }
1621
2894
  throw error;