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

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