@onekeyfe/hd-core 1.2.0-alpha.14 → 1.2.0-alpha.141

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