@onekeyfe/hd-core 1.2.0-alpha.13 → 1.2.0-alpha.130

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