@onekeyfe/hd-core 1.2.0-alpha.12 → 1.2.0-alpha.121

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