@onekeyfe/hd-core 1.2.0-alpha.11 → 1.2.0-alpha.111

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