@onekeyfe/hd-core 1.2.0-alpha.2 → 1.2.0-alpha.21

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 (406) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +75 -0
  2. package/__tests__/DeviceCommands.test.ts +285 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +43 -0
  4. package/__tests__/connectSettings.test.ts +19 -0
  5. package/__tests__/core-dispose.test.ts +34 -0
  6. package/__tests__/core-error-output.test.ts +39 -0
  7. package/__tests__/device-features-state.test.ts +78 -0
  8. package/__tests__/device-identity.test.ts +30 -0
  9. package/__tests__/device-lifecycle-events.test.ts +117 -0
  10. package/__tests__/device-pool-state.test.ts +132 -0
  11. package/__tests__/device-settings.test.ts +391 -0
  12. package/__tests__/device-state-contract.test.ts +27 -0
  13. package/__tests__/device-state-events.test.ts +162 -0
  14. package/__tests__/device-state-mapper.test.ts +241 -0
  15. package/__tests__/device-state-projector.test.ts +94 -0
  16. package/__tests__/device-state-store.test.ts +82 -0
  17. package/__tests__/device-utils.test.ts +106 -0
  18. package/__tests__/device-wallet-session-store.test.ts +213 -0
  19. package/__tests__/deviceFeaturesUtils.test.ts +39 -0
  20. package/__tests__/deviceSettings.test.ts +103 -0
  21. package/__tests__/evmLedgerLegacySafety.test.ts +10 -6
  22. package/__tests__/evmSignTypedData.test.ts +80 -0
  23. package/__tests__/filesystemValidation.test.ts +53 -0
  24. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +402 -0
  25. package/__tests__/get-device-state.test.ts +410 -0
  26. package/__tests__/homescreen.test.ts +24 -0
  27. package/__tests__/kaspaSignTransaction.test.ts +591 -0
  28. package/__tests__/logBlockEvent.test.ts +26 -0
  29. package/__tests__/networkUtils.test.ts +386 -0
  30. package/__tests__/open-wallet-session-error-response.test.ts +43 -0
  31. package/__tests__/open-wallet-session.test.ts +559 -0
  32. package/__tests__/preInitialize.test.ts +33 -0
  33. package/__tests__/pro2Wallpaper.test.ts +50 -0
  34. package/__tests__/protocol-v2-bootloader-mode.test.ts +37 -0
  35. package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
  36. package/__tests__/protocol-v2.test.ts +3954 -1191
  37. package/__tests__/protocolV2FileWrite.test.ts +92 -0
  38. package/__tests__/protocolV2UiInteraction.test.ts +266 -0
  39. package/__tests__/public-device-state-api.test.ts +257 -0
  40. package/__tests__/public-pro2-api-boundary.test.ts +110 -0
  41. package/__tests__/refresh-device-state.test.ts +101 -0
  42. package/__tests__/request-context-logging.test.ts +16 -0
  43. package/__tests__/search-devices.test.ts +136 -0
  44. package/dist/api/BaseMethod.d.ts +8 -0
  45. package/dist/api/BaseMethod.d.ts.map +1 -1
  46. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  47. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  48. package/dist/api/ClearSessionCache.d.ts +9 -0
  49. package/dist/api/ClearSessionCache.d.ts.map +1 -0
  50. package/dist/api/DirList.d.ts.map +1 -1
  51. package/dist/api/FileRead.d.ts.map +1 -1
  52. package/dist/api/FileWrite.d.ts +1 -0
  53. package/dist/api/FileWrite.d.ts.map +1 -1
  54. package/dist/api/FirmwareUpdateV2.d.ts +2 -2
  55. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  56. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  57. package/dist/api/FirmwareUpdateV4.d.ts +25 -5
  58. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  59. package/dist/api/GetDeviceState.d.ts +7 -0
  60. package/dist/api/GetDeviceState.d.ts.map +1 -0
  61. package/dist/api/GetFeatures.d.ts +1 -1
  62. package/dist/api/GetFeatures.d.ts.map +1 -1
  63. package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
  64. package/dist/api/GetPassphraseState.d.ts +1 -6
  65. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  66. package/dist/api/OpenWalletSession.d.ts +14 -0
  67. package/dist/api/OpenWalletSession.d.ts.map +1 -0
  68. package/dist/api/PathInfo.d.ts.map +1 -1
  69. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  70. package/dist/api/SearchDevices.d.ts +3 -0
  71. package/dist/api/SearchDevices.d.ts.map +1 -1
  72. package/dist/api/UploadPortfolio.d.ts +17 -0
  73. package/dist/api/UploadPortfolio.d.ts.map +1 -0
  74. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  75. package/dist/api/device/DeviceCancel.d.ts.map +1 -1
  76. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  77. package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
  78. package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
  79. package/dist/api/device/DeviceLock.d.ts.map +1 -1
  80. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  81. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  82. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  83. package/dist/api/device/DeviceUnlock.d.ts +1 -1
  84. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  85. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  86. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  87. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  88. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +1 -0
  89. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  90. package/dist/api/firmware/getBinary.d.ts +11 -3
  91. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  92. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  93. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  94. package/dist/api/helpers/filesystemValidation.d.ts +3 -0
  95. package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
  96. package/dist/api/helpers/paramsValidator.d.ts +1 -0
  97. package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
  98. package/dist/api/helpers/protocolV2FileWrite.d.ts +33 -0
  99. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -0
  100. package/dist/api/index.d.ts +12 -19
  101. package/dist/api/index.d.ts.map +1 -1
  102. package/dist/api/kaspa/KaspaSignTransaction.d.ts +5 -1
  103. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  104. package/dist/api/kaspa/helpers/TransferSerialize.d.ts.map +1 -1
  105. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +6 -0
  106. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -0
  107. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +7 -0
  108. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -0
  109. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -1
  110. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  111. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +3 -2
  112. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  113. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  114. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -1
  115. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +28 -0
  116. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -0
  117. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  118. package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
  119. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
  120. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +26 -0
  121. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -0
  122. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
  123. package/dist/api/protocol-v2/{FilesystemFixPermission.d.ts → FilesystemPermissionFix.d.ts} +2 -2
  124. package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -0
  125. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +6 -0
  126. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -0
  127. package/dist/api/protocol-v2/helpers.d.ts +27 -19
  128. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  129. package/dist/api/ton/TonSignData.d.ts +1 -5
  130. package/dist/api/ton/TonSignData.d.ts.map +1 -1
  131. package/dist/api/utils.d.ts +2 -1
  132. package/dist/api/utils.d.ts.map +1 -1
  133. package/dist/constants/index.d.ts +2 -1
  134. package/dist/constants/index.d.ts.map +1 -1
  135. package/dist/core/RequestQueue.d.ts.map +1 -1
  136. package/dist/core/deviceEventRegistration.d.ts +11 -0
  137. package/dist/core/deviceEventRegistration.d.ts.map +1 -0
  138. package/dist/core/index.d.ts +3 -1
  139. package/dist/core/index.d.ts.map +1 -1
  140. package/dist/data-manager/DataManager.d.ts +1 -1
  141. package/dist/data-manager/DataManager.d.ts.map +1 -1
  142. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  143. package/dist/device/Device.d.ts +35 -11
  144. package/dist/device/Device.d.ts.map +1 -1
  145. package/dist/device/DeviceCommands.d.ts +8 -6
  146. package/dist/device/DeviceCommands.d.ts.map +1 -1
  147. package/dist/device/DeviceFeaturesState.d.ts +3 -0
  148. package/dist/device/DeviceFeaturesState.d.ts.map +1 -0
  149. package/dist/device/DevicePool.d.ts +4 -2
  150. package/dist/device/DevicePool.d.ts.map +1 -1
  151. package/dist/device/DeviceStateMapper.d.ts +13 -0
  152. package/dist/device/DeviceStateMapper.d.ts.map +1 -0
  153. package/dist/device/DeviceStateProjector.d.ts +7 -0
  154. package/dist/device/DeviceStateProjector.d.ts.map +1 -0
  155. package/dist/device/DeviceStateStore.d.ts +21 -0
  156. package/dist/device/DeviceStateStore.d.ts.map +1 -0
  157. package/dist/device/DeviceWalletSessionStore.d.ts +20 -0
  158. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
  159. package/dist/device/cloneDeviceState.d.ts +2 -0
  160. package/dist/device/cloneDeviceState.d.ts.map +1 -0
  161. package/dist/device/deviceIdentity.d.ts +5 -0
  162. package/dist/device/deviceIdentity.d.ts.map +1 -0
  163. package/dist/deviceProfile/buildDeviceFeatures.d.ts +9 -3
  164. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  165. package/dist/deviceProfile/index.d.ts +0 -1
  166. package/dist/deviceProfile/index.d.ts.map +1 -1
  167. package/dist/events/device.d.ts +11 -2
  168. package/dist/events/device.d.ts.map +1 -1
  169. package/dist/events/logBlockEvent.d.ts.map +1 -1
  170. package/dist/events/ui-request.d.ts +18 -2
  171. package/dist/events/ui-request.d.ts.map +1 -1
  172. package/dist/index.d.ts +1361 -609
  173. package/dist/index.js +5898 -2860
  174. package/dist/inject.d.ts.map +1 -1
  175. package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
  176. package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
  177. package/dist/protocols/protocol-v2/features.d.ts +23 -28
  178. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  179. package/dist/protocols/protocol-v2/firmware.d.ts +11 -9
  180. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  181. package/dist/protocols/protocol-v2/index.d.ts +3 -2
  182. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  183. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  184. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  185. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +16 -0
  186. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
  187. package/dist/protocols/protocol-v2/uiInteraction.d.ts +39 -0
  188. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -0
  189. package/dist/protocols/protocol-v2/unlockRetry.d.ts +11 -0
  190. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  191. package/dist/protocols/protocol-v2/walletSession.d.ts +19 -0
  192. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
  193. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -2
  194. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  195. package/dist/types/api/deviceSettings.d.ts +11 -2
  196. package/dist/types/api/deviceSettings.d.ts.map +1 -1
  197. package/dist/types/api/deviceUnlock.d.ts +2 -2
  198. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  199. package/dist/types/api/firmwareUpdate.d.ts +6 -6
  200. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  201. package/dist/types/api/getDeviceState.d.ts +12 -0
  202. package/dist/types/api/getDeviceState.d.ts.map +1 -0
  203. package/dist/types/api/getFeatures.d.ts.map +1 -1
  204. package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
  205. package/dist/types/api/getPassphraseState.d.ts +2 -10
  206. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  207. package/dist/types/api/index.d.ts +22 -25
  208. package/dist/types/api/index.d.ts.map +1 -1
  209. package/dist/types/api/kaspaSignTransaction.d.ts +27 -2
  210. package/dist/types/api/kaspaSignTransaction.d.ts.map +1 -1
  211. package/dist/types/api/openWalletSession.d.ts +41 -0
  212. package/dist/types/api/openWalletSession.d.ts.map +1 -0
  213. package/dist/types/api/protocolV2.d.ts +23 -28
  214. package/dist/types/api/protocolV2.d.ts.map +1 -1
  215. package/dist/types/api/sessionCache.d.ts +10 -0
  216. package/dist/types/api/sessionCache.d.ts.map +1 -0
  217. package/dist/types/device.d.ts +121 -5
  218. package/dist/types/device.d.ts.map +1 -1
  219. package/dist/types/params.d.ts +1 -0
  220. package/dist/types/params.d.ts.map +1 -1
  221. package/dist/types/settings.d.ts +21 -1
  222. package/dist/types/settings.d.ts.map +1 -1
  223. package/dist/utils/assets.d.ts +2 -1
  224. package/dist/utils/assets.d.ts.map +1 -1
  225. package/dist/utils/deviceFeaturesCompat.d.ts +13 -0
  226. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
  227. package/dist/utils/deviceFeaturesUtils.d.ts +13 -3
  228. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  229. package/dist/utils/deviceInfoUtils.d.ts +9 -8
  230. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  231. package/dist/utils/deviceSettings.d.ts +38 -7
  232. package/dist/utils/deviceSettings.d.ts.map +1 -1
  233. package/dist/utils/deviceVersionUtils.d.ts +6 -5
  234. package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
  235. package/dist/utils/homescreen.d.ts.map +1 -1
  236. package/dist/utils/index.d.ts +5 -2
  237. package/dist/utils/index.d.ts.map +1 -1
  238. package/dist/utils/networkUtils.d.ts +9 -1
  239. package/dist/utils/networkUtils.d.ts.map +1 -1
  240. package/dist/utils/patch.d.ts +1 -1
  241. package/dist/utils/patch.d.ts.map +1 -1
  242. package/dist/utils/pro2Wallpaper.d.ts +12 -0
  243. package/dist/utils/pro2Wallpaper.d.ts.map +1 -0
  244. package/dist/utils/tracing.d.ts.map +1 -1
  245. package/package.json +4 -4
  246. package/src/api/BaseMethod.ts +36 -9
  247. package/src/api/CheckBootloaderRelease.ts +1 -2
  248. package/src/api/CheckFirmwareRelease.ts +1 -2
  249. package/src/api/ClearSessionCache.ts +28 -0
  250. package/src/api/DirList.ts +5 -3
  251. package/src/api/DirMake.ts +3 -3
  252. package/src/api/DirRemove.ts +3 -3
  253. package/src/api/FileDelete.ts +3 -3
  254. package/src/api/FileRead.ts +23 -7
  255. package/src/api/FileWrite.ts +23 -168
  256. package/src/api/FirmwareUpdate.ts +4 -4
  257. package/src/api/FirmwareUpdateV2.ts +133 -45
  258. package/src/api/FirmwareUpdateV3.ts +3 -5
  259. package/src/api/FirmwareUpdateV4.ts +1138 -357
  260. package/src/api/GetDeviceState.ts +55 -0
  261. package/src/api/GetFeatures.ts +8 -3
  262. package/src/api/GetOnekeyFeatures.ts +3 -72
  263. package/src/api/GetPassphraseState.ts +11 -18
  264. package/src/api/OpenWalletSession.ts +221 -0
  265. package/src/api/PathInfo.ts +5 -3
  266. package/src/api/PromptWebDeviceAccess.ts +2 -3
  267. package/src/api/SearchDevices.ts +29 -4
  268. package/src/api/UploadPortfolio.ts +40 -0
  269. package/src/api/alephium/AlephiumSignTransaction.ts +2 -2
  270. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +4 -8
  271. package/src/api/benfen/BenfenGetAddress.ts +1 -1
  272. package/src/api/btc/BTCGetPublicKey.ts +1 -1
  273. package/src/api/cardano/CardanoSignMessage.ts +1 -1
  274. package/src/api/cardano/CardanoSignTransaction.ts +1 -1
  275. package/src/api/device/DeviceCancel.ts +1 -0
  276. package/src/api/device/DeviceChangePin.ts +24 -0
  277. package/src/api/device/DeviceFullyUploadResource.ts +4 -5
  278. package/src/api/device/DeviceLock.ts +1 -0
  279. package/src/api/device/DeviceRebootToBoardloader.ts +5 -4
  280. package/src/api/device/DeviceRebootToBootloader.ts +5 -4
  281. package/src/api/device/DeviceSettings.ts +183 -1
  282. package/src/api/device/DeviceUnlock.ts +11 -1
  283. package/src/api/device/DeviceUpdateBootloader.ts +1 -2
  284. package/src/api/device/DeviceWipe.ts +19 -0
  285. package/src/api/evm/EVMSignTypedData.ts +3 -5
  286. package/src/api/evm/legacyV1/getAddress.ts +2 -2
  287. package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
  288. package/src/api/evm/legacyV1/signMessage.ts +2 -2
  289. package/src/api/evm/legacyV1/signTypedData.ts +2 -2
  290. package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
  291. package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
  292. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +21 -7
  293. package/src/api/firmware/getBinary.ts +8 -3
  294. package/src/api/firmware/progressThrottle.ts +44 -0
  295. package/src/api/helpers/batchGetPublickeys.ts +2 -2
  296. package/src/api/helpers/filesystemValidation.ts +61 -0
  297. package/src/api/helpers/paramsValidator.ts +1 -1
  298. package/src/api/helpers/protocolV2FileWrite.ts +193 -0
  299. package/src/api/index.ts +14 -20
  300. package/src/api/kaspa/KaspaSignTransaction.ts +341 -22
  301. package/src/api/kaspa/helpers/TransferSerialize.ts +14 -0
  302. package/src/api/nervos/NervosSignTransaction.ts +2 -2
  303. package/src/api/nexa/NexaSignTransaction.ts +1 -1
  304. package/src/api/protocol-v2/{FactoryGetDeviceInfo.ts → DeviceFactoryInfoGet.ts} +4 -4
  305. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +32 -0
  306. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +7 -9
  307. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +41 -6
  308. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +6 -4
  309. package/src/api/protocol-v2/DeviceInfoGet.ts +145 -0
  310. package/src/api/protocol-v2/DeviceReboot.ts +5 -3
  311. package/src/api/protocol-v2/DeviceStatusGet.ts +16 -0
  312. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +131 -0
  313. package/src/api/protocol-v2/FilesystemFormat.ts +5 -2
  314. package/src/api/protocol-v2/{FilesystemFixPermission.ts → FilesystemPermissionFix.ts} +3 -3
  315. package/src/api/protocol-v2/Ping.ts +1 -1
  316. package/src/api/protocol-v2/{GetProtoVersion.ts → ProtocolInfoRequest.ts} +3 -3
  317. package/src/api/protocol-v2/helpers.ts +58 -62
  318. package/src/api/ton/TonSignData.ts +1 -5
  319. package/src/api/utils.ts +7 -3
  320. package/src/constants/index.ts +7 -1
  321. package/src/core/RequestQueue.ts +1 -0
  322. package/src/core/deviceEventRegistration.ts +23 -0
  323. package/src/core/index.ts +140 -35
  324. package/src/data/messages/messages-protocol-v2.json +825 -709
  325. package/src/data/messages/messages.json +286 -1
  326. package/src/data-manager/DataManager.ts +13 -6
  327. package/src/data-manager/connectSettings.ts +4 -0
  328. package/src/device/Device.ts +480 -192
  329. package/src/device/DeviceCommands.ts +81 -25
  330. package/src/device/DeviceFeaturesState.ts +25 -0
  331. package/src/device/DevicePool.ts +67 -19
  332. package/src/device/DeviceStateMapper.ts +349 -0
  333. package/src/device/DeviceStateProjector.ts +95 -0
  334. package/src/device/DeviceStateStore.ts +201 -0
  335. package/src/device/DeviceWalletSessionStore.ts +105 -0
  336. package/src/device/cloneDeviceState.ts +39 -0
  337. package/src/device/deviceIdentity.ts +18 -0
  338. package/src/deviceProfile/buildDeviceFeatures.ts +235 -210
  339. package/src/deviceProfile/index.ts +0 -1
  340. package/src/events/device.ts +17 -1
  341. package/src/events/logBlockEvent.ts +8 -1
  342. package/src/events/ui-request.ts +24 -2
  343. package/src/inject.ts +23 -34
  344. package/src/protocols/protocol-v2/deviceSession.ts +24 -0
  345. package/src/protocols/protocol-v2/features.ts +68 -82
  346. package/src/protocols/protocol-v2/firmware.ts +12 -36
  347. package/src/protocols/protocol-v2/index.ts +3 -1
  348. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  349. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +61 -0
  350. package/src/protocols/protocol-v2/uiInteraction.ts +210 -0
  351. package/src/protocols/protocol-v2/unlockRetry.ts +76 -0
  352. package/src/protocols/protocol-v2/walletSession.ts +107 -0
  353. package/src/types/api/checkAllFirmwareRelease.ts +1 -2
  354. package/src/types/api/deviceSettings.ts +26 -2
  355. package/src/types/api/deviceUnlock.ts +5 -2
  356. package/src/types/api/firmwareUpdate.ts +28 -20
  357. package/src/types/api/getDeviceState.ts +19 -0
  358. package/src/types/api/getFeatures.ts +3 -0
  359. package/src/types/api/getOnekeyFeatures.ts +3 -0
  360. package/src/types/api/getPassphraseState.ts +8 -11
  361. package/src/types/api/index.ts +30 -53
  362. package/src/types/api/kaspaSignTransaction.ts +40 -2
  363. package/src/types/api/openWalletSession.ts +64 -0
  364. package/src/types/api/protocolV2.ts +58 -55
  365. package/src/types/api/sessionCache.ts +14 -0
  366. package/src/types/device.ts +170 -4
  367. package/src/types/params.ts +4 -0
  368. package/src/types/settings.ts +42 -30
  369. package/src/utils/assets.ts +4 -1
  370. package/src/utils/deviceFeaturesCompat.ts +173 -0
  371. package/src/utils/deviceFeaturesUtils.ts +68 -24
  372. package/src/utils/deviceInfoUtils.ts +26 -64
  373. package/src/utils/deviceSettings.ts +161 -39
  374. package/src/utils/deviceVersionUtils.ts +25 -44
  375. package/src/utils/findDefectiveBatchDevice.ts +3 -3
  376. package/src/utils/homescreen.ts +10 -0
  377. package/src/utils/index.ts +21 -1
  378. package/src/utils/networkUtils.ts +270 -14
  379. package/src/utils/pro2Wallpaper.ts +109 -0
  380. package/src/utils/tracing.ts +3 -1
  381. package/dist/api/GetDeviceInfo.d.ts +0 -9
  382. package/dist/api/GetDeviceInfo.d.ts.map +0 -1
  383. package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
  384. package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
  385. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts +0 -29
  386. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +0 -1
  387. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +0 -7
  388. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +0 -1
  389. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +0 -6
  390. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +0 -1
  391. package/dist/api/protocol-v2/FilesystemDiskControl.d.ts +0 -13
  392. package/dist/api/protocol-v2/FilesystemDiskControl.d.ts.map +0 -1
  393. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +0 -1
  394. package/dist/api/protocol-v2/GetProtoVersion.d.ts +0 -6
  395. package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +0 -1
  396. package/dist/deviceProfile/buildDeviceProfile.d.ts +0 -21
  397. package/dist/deviceProfile/buildDeviceProfile.d.ts.map +0 -1
  398. package/dist/types/api/getDeviceInfo.d.ts +0 -85
  399. package/dist/types/api/getDeviceInfo.d.ts.map +0 -1
  400. package/src/api/GetDeviceInfo.ts +0 -145
  401. package/src/api/protocol-v2/DevReboot.ts +0 -24
  402. package/src/api/protocol-v2/DeviceGetDeviceInfo.ts +0 -118
  403. package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +0 -29
  404. package/src/api/protocol-v2/FilesystemDiskControl.ts +0 -50
  405. package/src/deviceProfile/buildDeviceProfile.ts +0 -334
  406. package/src/types/api/getDeviceInfo.ts +0 -99
@@ -1,6 +1,6 @@
1
1
  import { ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
2
2
  import {
3
- DevRebootType,
3
+ DeviceRebootType,
4
4
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
5
5
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
6
6
  } from '@onekeyfe/hd-transport';
@@ -15,50 +15,208 @@ import {
15
15
  getFirmwareType,
16
16
  getLogger,
17
17
  } from '../utils';
18
- import { getBinary, getSysResourceBinary } from './firmware/getBinary';
18
+ import { getSysResourceBinary } from './firmware/getBinary';
19
19
  import { DataManager } from '../data-manager';
20
20
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
21
21
  import { DevicePool } from '../device/DevicePool';
22
22
  import {
23
23
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
24
24
  ProtocolV2FirmwareTargetType,
25
- protocolV2FileNameToTargetId,
26
25
  } from '../protocols/protocol-v2';
27
26
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
28
27
  import {
29
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
30
28
  getProtocolV2UnknownErrorText,
31
29
  isProtocolV2DeviceDisconnectedError,
32
30
  } from './protocol-v2/helpers';
33
31
 
34
- import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
32
+ import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
35
33
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
36
34
  import type { PROTO } from '../constants';
37
35
  import type { TypedResponseMessage } from '../device/DeviceCommands';
38
- import type { Features } from '../types';
36
+ import type {
37
+ Features,
38
+ IFirmwareReleaseInfo,
39
+ IProtocolV2FirmwareComponent,
40
+ IVersionArray,
41
+ } from '../types';
39
42
 
40
43
  const Log = getLogger(LoggerNames.Method);
41
44
 
42
45
  const SESSION_ERROR = 'session not found';
43
46
  const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
47
+ const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
44
48
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
49
+ const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
45
50
  const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
46
- const PROTOCOL_V2_TARGET_STATUS_FINISHED = 0;
51
+ const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
47
52
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
48
- const PROTOCOL_V2_TARGET_STATUS_FAILED = 2;
53
+ const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
54
+ const PROTOCOL_V2_TARGET_STATUS_FAILED_MIN = 3;
49
55
  const PROTOCOL_V2_CONNECT_PROTOCOL = 'V2';
50
- const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = 'vol1:';
56
+ const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = 'vol0:/';
51
57
  const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
58
+ const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
59
+ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
60
+ const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
61
+ const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
62
+ const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
63
+ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
64
+ const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
65
+ const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
66
+ const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
67
+
68
+ const getProtocolV2DeviceTransferProgress = (
69
+ bytesBeforeChunk: number,
70
+ bytesAfterChunk: number,
71
+ totalBytes: number
72
+ ) => {
73
+ if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
74
+ return 100;
75
+ }
76
+ if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
77
+ return 0;
78
+ }
79
+ if (bytesAfterChunk >= totalBytes) {
80
+ return 100;
81
+ }
82
+ return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
83
+ };
84
+
85
+ const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
86
+ const safeElapsedMs = Math.max(elapsedMs, 1);
87
+ return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
88
+ };
52
89
 
53
90
  type ProtocolV2FirmwareUpdateStatusTarget = {
54
- target_id: number;
55
- status: number;
91
+ target_id: number | string;
92
+ status?: number | string;
93
+ payload_version?: number;
94
+ path?: string;
56
95
  };
57
96
 
58
- type ProtocolV2FirmwareUpdateStartResponse =
59
- | TypedResponseMessage<'Success'>
60
- | TypedResponseMessage<'DevFirmwareUpdateStatus'>
61
- | undefined;
97
+ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
98
+
99
+ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
100
+ type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
101
+ kind: ProtocolV2RemoteComponentTarget['kind'];
102
+ };
103
+ type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
104
+ path: string;
105
+ };
106
+
107
+ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
108
+ filePath: string;
109
+ processedSize?: number;
110
+ totalSize?: number;
111
+ onTransferredBytes?: (transferredBytes: number) => void;
112
+ };
113
+
114
+ /** RESC bundle okpkg written independently to devicePath through FileWrite. */
115
+ type ProtocolV2ResourceBundleBinary = {
116
+ name: string;
117
+ binary: ArrayBuffer;
118
+ devicePath: string;
119
+ /** Download URL for remote-config mode; omitted in manual mode. */
120
+ url?: string;
121
+ version?: IVersionArray;
122
+ payloadHash?: string;
123
+ headerHash?: string;
124
+ };
125
+
126
+ type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
127
+ binary: ArrayBuffer;
128
+ };
129
+
130
+ type ProtocolV2RemoteComponentTarget = {
131
+ fileName: string;
132
+ targetId: number;
133
+ kind: 'bootloader' | 'firmware';
134
+ };
135
+
136
+ type ProtocolV2OkppHeader = {
137
+ type: string;
138
+ version: IVersionArray;
139
+ payloadHash: string;
140
+ headerHash: string;
141
+ };
142
+
143
+ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
144
+ Record<string, ProtocolV2RemoteComponentTarget>
145
+ > = {
146
+ BOOTLOADER: {
147
+ fileName: 'bootloader.bin',
148
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
149
+ kind: 'bootloader',
150
+ },
151
+ APPLICATION_P1: {
152
+ fileName: 'application_p1.bin',
153
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1,
154
+ kind: 'firmware',
155
+ },
156
+ APPLICATION_P2: {
157
+ fileName: 'application_p2.bin',
158
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2,
159
+ kind: 'firmware',
160
+ },
161
+ COPROCESSOR: {
162
+ fileName: 'coprocessor.bin',
163
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
164
+ kind: 'firmware',
165
+ },
166
+ SE01: {
167
+ fileName: 'se01.bin',
168
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01,
169
+ kind: 'firmware',
170
+ },
171
+ SE02: {
172
+ fileName: 'se02.bin',
173
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02,
174
+ kind: 'firmware',
175
+ },
176
+ SE03: {
177
+ fileName: 'se03.bin',
178
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03,
179
+ kind: 'firmware',
180
+ },
181
+ SE04: {
182
+ fileName: 'se04.bin',
183
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04,
184
+ kind: 'firmware',
185
+ },
186
+ };
187
+
188
+ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4Target>([
189
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
190
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
191
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
192
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR, 'coprocessor'],
193
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, 'se01'],
194
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, 'se02'],
195
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, 'se03'],
196
+ [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
197
+ ]);
198
+
199
+ const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
200
+ 'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
201
+
202
+ // hd-transport historically decodes scalar enums as enum-name strings.
203
+ // Map them back to firmware protocol values before internal comparisons.
204
+ const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
205
+ Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
206
+ );
207
+ const PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME = new Map<string, number>([
208
+ ['FW_MGMT_UPDATER_TASK_STATUS_PENDING', PROTOCOL_V2_TARGET_STATUS_PENDING],
209
+ ['FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS', PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS],
210
+ ['FW_MGMT_UPDATER_TASK_STATUS_FINISHED', PROTOCOL_V2_TARGET_STATUS_FINISHED],
211
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_NOT_FOUND', 3],
212
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_READ', 4],
213
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_WRITE', 5],
214
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY', 6],
215
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_INSTALL', 7],
216
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ABORT', 8],
217
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_BUSY', 9],
218
+ ['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS', 10],
219
+ ]);
62
220
 
63
221
  const isProtocolV2ReconnectProbeError = (error: unknown) => {
64
222
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
@@ -68,43 +226,200 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
68
226
  );
69
227
  };
70
228
 
71
- const isProtocolV2PollingTransientError = (error: unknown) => {
229
+ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
72
230
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
73
231
  return (
74
- isProtocolV2DeviceDisconnectedError(error) ||
75
- isProtocolV2ReconnectProbeError(error) ||
76
- (message.includes('response timeout') && message.includes('devicegetfirmwareupdatestatus')) ||
77
- message.includes('device not found') ||
78
- message.includes('transportnotfound')
232
+ message.includes('handler not registered') ||
233
+ message.includes('message handler not found') ||
234
+ message.includes('unsupported message')
235
+ );
236
+ };
237
+
238
+ const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
239
+ normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
240
+
241
+ const isProtocolV2TargetStatusInProgress = (
242
+ status: ProtocolV2FirmwareUpdateStatusTarget['status']
243
+ ) =>
244
+ normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_PENDING ||
245
+ normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS;
246
+
247
+ const isProtocolV2TargetStatusFailed = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) => {
248
+ const normalizedStatus = normalizeProtocolV2TargetStatus(status);
249
+ return (
250
+ typeof normalizedStatus === 'number' && normalizedStatus >= PROTOCOL_V2_TARGET_STATUS_FAILED_MIN
79
251
  );
80
252
  };
81
253
 
254
+ const normalizeProtocolV2TargetId = (targetId: number | string) => {
255
+ if (typeof targetId === 'number') {
256
+ return targetId;
257
+ }
258
+ return PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME.get(targetId);
259
+ };
260
+
261
+ const normalizeProtocolV2TargetStatus = (
262
+ status: ProtocolV2FirmwareUpdateStatusTarget['status']
263
+ ) => {
264
+ if (typeof status === 'number') {
265
+ return status;
266
+ }
267
+ if (typeof status === 'string') {
268
+ return PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME.get(status);
269
+ }
270
+ return undefined;
271
+ };
272
+
273
+ const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
274
+
275
+ const versionArrayToNumber = (version?: IVersionArray) => {
276
+ if (!version) return undefined;
277
+ return version[0] * 0x10000 + version[1] * 0x100 + version[2];
278
+ };
279
+
280
+ const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
281
+ const currentNumber = versionArrayToNumber(current);
282
+ const targetNumber = versionArrayToNumber(target);
283
+ if (currentNumber === undefined || targetNumber === undefined) return undefined;
284
+ return currentNumber - targetNumber;
285
+ };
286
+
287
+ const bytesToHex = (bytes: Uint8Array) =>
288
+ Array.from(bytes)
289
+ .map(byte => byte.toString(16).padStart(2, '0'))
290
+ .join('');
291
+
292
+ const hexToProtocolV2Bytes = (hex: string) => {
293
+ const normalized = hex.replace(/^0x/i, '');
294
+ if (!normalized || normalized.length % 2 !== 0 || /[^0-9a-f]/i.test(normalized)) {
295
+ return new Uint8Array(0);
296
+ }
297
+ const bytes = new Uint8Array(normalized.length / 2);
298
+ for (let i = 0; i < bytes.length; i++) {
299
+ bytes[i] = Number.parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
300
+ }
301
+ return bytes;
302
+ };
303
+
304
+ const toProtocolV2Bytes = (value: unknown): Uint8Array => {
305
+ if (value instanceof Uint8Array) return value;
306
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
307
+ if (ArrayBuffer.isView(value)) {
308
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
309
+ }
310
+ if (typeof value === 'string') return hexToProtocolV2Bytes(value);
311
+ return new Uint8Array(0);
312
+ };
313
+
314
+ const toProtocolV2FiniteNumber = (value: unknown) => {
315
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
316
+ if (typeof value === 'string') {
317
+ const numeric = Number(value);
318
+ return Number.isFinite(numeric) ? numeric : undefined;
319
+ }
320
+ if (value && typeof value === 'object') {
321
+ const longLike = value as { toNumber?: () => number };
322
+ if (typeof longLike.toNumber === 'function') {
323
+ const numeric = longLike.toNumber();
324
+ return Number.isFinite(numeric) ? numeric : undefined;
325
+ }
326
+ }
327
+ return undefined;
328
+ };
329
+
330
+ const readProtocolV2Ascii = (bytes: Uint8Array, offset: number, length: number) =>
331
+ Array.from(bytes.slice(offset, offset + length))
332
+ .map(byte => String.fromCharCode(byte))
333
+ .join('');
334
+
335
+ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | null => {
336
+ if (bytes.byteLength < PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
337
+ if (readProtocolV2Ascii(bytes, 0, 4) !== 'OKPP') return null;
338
+
339
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
340
+ const headerLen = view.getUint32(0x0c, true);
341
+ if (headerLen !== PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
342
+
343
+ const packedVersion = view.getUint32(0x10, true);
344
+ return {
345
+ type: readProtocolV2Ascii(bytes, 0x08, 4),
346
+ version: [
347
+ Math.floor(packedVersion / 0x10000) % 0x100,
348
+ Math.floor(packedVersion / 0x100) % 0x100,
349
+ packedVersion % 0x100,
350
+ ],
351
+ payloadHash: bytesToHex(
352
+ bytes.slice(
353
+ PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET,
354
+ PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
355
+ )
356
+ ),
357
+ headerHash: bytesToHex(
358
+ bytes.slice(
359
+ PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
360
+ PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
361
+ )
362
+ ),
363
+ };
364
+ };
365
+
366
+ export const assertProtocolV2ReconnectIdentity = (
367
+ expectedDeviceId?: string,
368
+ actualDeviceId?: string,
369
+ options: { allowMissingActual?: boolean } = {}
370
+ ) => {
371
+ if (!expectedDeviceId) return;
372
+ if (!actualDeviceId) {
373
+ if (options.allowMissingActual) return;
374
+ throw ERRORS.TypedError(
375
+ HardwareErrorCode.DeviceNotFound,
376
+ 'Protocol V2 reconnect identity unavailable'
377
+ );
378
+ }
379
+ if (actualDeviceId !== expectedDeviceId) {
380
+ throw ERRORS.TypedError(
381
+ HardwareErrorCode.DeviceNotFound,
382
+ `Protocol V2 reconnect identity mismatch: expected ${expectedDeviceId}, received ${actualDeviceId}`
383
+ );
384
+ }
385
+ };
386
+
82
387
  /**
83
388
  * FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
84
389
  *
85
390
  * It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
86
391
  * - upload uses FilesystemFileWrite
87
- * - install uses DevFirmwareUpdate
88
- * - completion reboots to normal, then polls Ping
392
+ * - install uses DeviceFirmwareUpdateRequest
393
+ * - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
89
394
  */
90
395
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
396
+ private protocolV2ExpectedDeviceId?: string;
397
+
91
398
  init() {
92
399
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
93
400
  this.requireDeviceMode = [];
401
+ this.unlockPolicy = 'retry-on-locked';
94
402
  this.useDevicePassphraseState = false;
95
403
  this.skipForceUpdateCheck = true;
96
404
 
97
405
  const { payload } = this;
98
406
 
407
+ if (typeof payload.retryCount !== 'number') {
408
+ payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
409
+ }
410
+ if (typeof payload.pollIntervalTime !== 'number') {
411
+ payload.pollIntervalTime = PROTOCOL_V2_CONNECT_POLL_INTERVAL;
412
+ }
413
+ if (typeof payload.timeout !== 'number') {
414
+ payload.timeout = PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT;
415
+ }
416
+ if (typeof payload.protocolV2DeviceInfoTimeoutMs !== 'number') {
417
+ payload.protocolV2DeviceInfoTimeoutMs = PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT;
418
+ }
419
+
99
420
  validateParams(payload, [
100
- { name: 'bleVersion', type: 'array' },
101
- { name: 'bleBinary', type: 'buffer' },
102
421
  { name: 'chunkSize', type: 'number' },
103
- { name: 'firmwareVersion', type: 'array' },
104
- { name: 'firmwareBinary', type: 'buffer' },
105
- { name: 'resourceBinary', type: 'buffer' },
106
422
  { name: 'forcedUpdateRes', type: 'boolean' },
107
- { name: 'bootloaderVersion', type: 'array' },
108
423
  { name: 'bootloaderBinary', type: 'buffer' },
109
424
  { name: 'romloaderBinary', type: 'buffer' },
110
425
  { name: 'applicationP1Binary', type: 'buffer' },
@@ -115,16 +430,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
115
430
  { name: 'se03Binary', type: 'buffer' },
116
431
  { name: 'se04Binary', type: 'buffer' },
117
432
  { name: 'firmwareType', type: 'string' },
433
+ { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
118
434
  { name: 'platform', type: 'string' },
435
+ { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
119
436
  ]);
120
437
 
121
438
  this.params = {
122
- bleBinary: payload.bleBinary,
123
439
  chunkSize: payload.chunkSize,
124
- firmwareBinary: payload.firmwareBinary,
125
440
  forcedUpdateRes: payload.forcedUpdateRes,
126
- bleVersion: payload.bleVersion,
127
- bootloaderVersion: payload.bootloaderVersion,
128
441
  bootloaderBinary: payload.bootloaderBinary,
129
442
  romloaderBinary: payload.romloaderBinary,
130
443
  applicationP1Binary: payload.applicationP1Binary,
@@ -134,9 +447,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
134
447
  se02Binary: payload.se02Binary,
135
448
  se03Binary: payload.se03Binary,
136
449
  se04Binary: payload.se04Binary,
137
- firmwareVersion: payload.firmwareVersion,
138
- resourceBinary: payload.resourceBinary,
450
+ resourceBundleFiles: payload.resourceBundleFiles,
139
451
  firmwareType: payload.firmwareType,
452
+ targetsToUpdate: payload.targetsToUpdate,
140
453
  platform: payload.platform,
141
454
  };
142
455
  }
@@ -171,38 +484,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
171
484
 
172
485
  private async runProtocolV2() {
173
486
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
487
+ this.protocolV2ExpectedDeviceId = deviceFeatures.deviceId ?? undefined;
174
488
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
175
489
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
176
490
 
177
- let resourceBinary: ArrayBuffer | null = null;
178
- let fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[] = [];
491
+ let fwBinaryMap: ProtocolV2TargetBinary[] = [];
179
492
  let bootloaderBinary: ArrayBuffer | null = null;
493
+ let installItems: ProtocolV2InstallItem[] | undefined;
494
+ let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
180
495
  try {
181
496
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
182
- resourceBinary = await this.prepareResourceBinary(firmwareType, deviceFeatures);
183
- fwBinaryMap = await this.prepareFirmwareAndBleBinary(firmwareType, deviceFeatures);
184
- bootloaderBinary = await this.prepareBootloaderBinary(firmwareType, deviceFeatures);
185
- // DevFirmwareTargetType 拆分的目标二进制(显式 target_id,不走文件名推断)
186
- fwBinaryMap.push(...this.collectExplicitTargetBinaries());
497
+ fwBinaryMap = this.collectExplicitTargetBinaries();
498
+ bootloaderBinary = this.prepareBootloaderBinary();
499
+ if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
500
+ const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
501
+ firmwareType,
502
+ deviceFeatures
503
+ );
504
+ bootloaderBinary = remoteBinaries.bootloaderBinary;
505
+ fwBinaryMap = remoteBinaries.fwBinaryMap;
506
+ installItems = remoteBinaries.installItems;
507
+ }
508
+ resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
509
+ if (resourceBundles?.length) {
510
+ resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
511
+ }
187
512
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
188
513
  } catch (err) {
189
514
  throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
190
515
  }
191
516
 
192
- if (!resourceBinary && !bootloaderBinary && fwBinaryMap.length === 0) {
517
+ if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
193
518
  throw ERRORS.TypedError(
194
519
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
195
520
  'No firmware to update'
196
521
  );
197
522
  }
198
523
 
199
- // TODO: 当前 firmware-pro2 子模块的 reboot/bootloader 流程还未稳定,先暂停自动进 bootloader。
200
- // await this.enterProtocolV2BootloaderMode();
524
+ await this.enterProtocolV2BootloaderMode();
201
525
 
202
526
  await this.executeProtocolV2Update({
203
- resourceBinary,
204
527
  fwBinaryMap,
205
528
  bootloaderBinary,
529
+ ...(installItems ? { installItems } : undefined),
530
+ ...(resourceBundles?.length ? { resourceBundles } : undefined),
206
531
  });
207
532
 
208
533
  await this.exitProtocolV2BootloaderToNormal();
@@ -215,50 +540,365 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
215
540
  }
216
541
 
217
542
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
543
+ if (this.device.features) {
544
+ return this.device.features;
545
+ }
218
546
  if (typeof this.device.getFeatures === 'function') {
219
547
  const features = await this.device.getFeatures();
220
548
  if (features) return features;
221
549
  }
222
- if (this.device.features) {
223
- return this.device.features;
224
- }
225
550
  throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
226
551
  }
227
552
 
228
- private async prepareResourceBinary(firmwareType: EFirmwareType, features: Features) {
229
- if (this.params.resourceBinary) {
230
- return this.params.resourceBinary;
553
+ private prepareBootloaderBinary(): ArrayBuffer | null {
554
+ return this.params.bootloaderBinary ?? null;
555
+ }
556
+
557
+ private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
558
+ return (
559
+ !!this.params.resourceBundleFiles?.length ||
560
+ !!this.params.bootloaderBinary ||
561
+ fwBinaryMap.length > 0
562
+ );
563
+ }
564
+
565
+ private buildProtocolV2InstallItems({
566
+ bootloaderBinary,
567
+ fwBinaryMap,
568
+ }: {
569
+ bootloaderBinary: ArrayBuffer | null;
570
+ fwBinaryMap: ProtocolV2TargetBinary[];
571
+ }): ProtocolV2InstallItem[] {
572
+ const installItems: ProtocolV2InstallItem[] = [];
573
+
574
+ if (bootloaderBinary) {
575
+ installItems.push({
576
+ fileName: 'bootloader.bin',
577
+ binary: bootloaderBinary,
578
+ targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
579
+ kind: 'bootloader',
580
+ });
581
+ }
582
+
583
+ installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
584
+ return installItems;
585
+ }
586
+
587
+ private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
588
+ const { components } = release;
589
+ if (!components) return [];
590
+
591
+ const orderedKeys = [
592
+ ...(release.installOrder ?? []),
593
+ ...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
594
+ ];
595
+
596
+ return orderedKeys
597
+ .map(key => {
598
+ const component = components[key];
599
+ return component ? ([key, component] as const) : undefined;
600
+ })
601
+ .filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
602
+ }
603
+
604
+ private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
605
+ const targetName = component.target?.toUpperCase();
606
+ if (targetName === 'ROMLOADER') {
607
+ throw ERRORS.TypedError(
608
+ HardwareErrorCode.RuntimeError,
609
+ PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
610
+ );
611
+ }
612
+ const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
613
+ if (!target) {
614
+ throw ERRORS.TypedError(
615
+ HardwareErrorCode.RuntimeError,
616
+ `Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
617
+ );
231
618
  }
232
- const resourceUrl = DataManager.getSysResourcesLatestRelease({
233
- features,
234
- forcedUpdateRes: this.params.forcedUpdateRes,
235
- firmwareType,
619
+ return target;
620
+ }
621
+
622
+ private getProtocolV2ResourceFilePath(path: string) {
623
+ if (path.startsWith('vol')) return path;
624
+ if (path.startsWith('/')) return `vol0:${path}`;
625
+ return `vol0:/${path}`;
626
+ }
627
+
628
+ private async readProtocolV2DeviceFileHeader(path: string) {
629
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
630
+ const filePath = this.getProtocolV2ResourceFilePath(path);
631
+ const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
632
+ path: filePath,
236
633
  });
634
+ const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
635
+ if (
636
+ !pathInfoRes.message?.exist ||
637
+ pathInfoRes.message?.directory ||
638
+ fileSize === undefined ||
639
+ fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
640
+ ) {
641
+ return null;
642
+ }
643
+
644
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
645
+ const chunks: Uint8Array[] = [];
646
+ let offset = 0;
647
+ while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
648
+ const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
649
+ const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
650
+ file: {
651
+ path: filePath,
652
+ offset,
653
+ total_size: 0,
654
+ },
655
+ chunk_len: readLen,
656
+ ui_percentage: undefined,
657
+ });
658
+ const data = toProtocolV2Bytes(res.message?.data);
659
+ if (data.byteLength === 0) return null;
660
+ chunks.push(data);
661
+ offset += data.byteLength;
662
+ }
663
+
664
+ const headerBytes = new Uint8Array(offset);
665
+ let cursor = 0;
666
+ chunks.forEach(chunk => {
667
+ headerBytes.set(chunk, cursor);
668
+ cursor += chunk.byteLength;
669
+ });
670
+ return parseProtocolV2OkppHeader(headerBytes);
671
+ }
237
672
 
238
- if (resourceUrl) {
239
- const resource = (await getSysResourceBinary(resourceUrl)).binary;
240
- return resource;
673
+ private async downloadRemoteProtocolV2Component(
674
+ key: string,
675
+ component: IProtocolV2FirmwareComponent
676
+ ): Promise<ProtocolV2RemoteComponentBinary> {
677
+ const target = this.getRemoteComponentTarget(key, component);
678
+ if (!component.url) {
679
+ throw ERRORS.TypedError(
680
+ HardwareErrorCode.RuntimeError,
681
+ `Missing Protocol V2 firmware component url: ${key}/${component.target}`
682
+ );
241
683
  }
242
- Log.warn('No resource url found');
243
- return null;
684
+
685
+ const { binary } = await getSysResourceBinary(component.url);
686
+ return {
687
+ ...target,
688
+ binary,
689
+ };
244
690
  }
245
691
 
246
- private async prepareBootloaderBinary(
692
+ private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
693
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
694
+
695
+ let bootloaderBinary: ArrayBuffer | null = null;
696
+ const fwBinaryMap: ProtocolV2TargetBinary[] = [];
697
+ const installItems: ProtocolV2InstallItem[] = [];
698
+
699
+ if (!release) {
700
+ return {
701
+ bootloaderBinary,
702
+ fwBinaryMap,
703
+ installItems,
704
+ };
705
+ }
706
+
707
+ const entries = this.getRemoteComponentEntries(release);
708
+ const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
709
+
710
+ for (const [key, component] of entries) {
711
+ const target = this.getRemoteComponentTarget(key, component);
712
+ const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
713
+ const shouldInstall = updateTarget ? targetsToUpdate.has(updateTarget) : false;
714
+ if (shouldInstall) {
715
+ const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
716
+ if (remoteBinary.kind === 'bootloader') {
717
+ bootloaderBinary = remoteBinary.binary;
718
+ installItems.push({
719
+ fileName: remoteBinary.fileName,
720
+ binary: remoteBinary.binary,
721
+ targetId: remoteBinary.targetId,
722
+ kind: remoteBinary.kind,
723
+ });
724
+ } else {
725
+ const binaryEntry = {
726
+ fileName: remoteBinary.fileName,
727
+ binary: remoteBinary.binary,
728
+ targetId: remoteBinary.targetId,
729
+ };
730
+ fwBinaryMap.push(binaryEntry);
731
+ installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
732
+ }
733
+ }
734
+ }
735
+
736
+ return {
737
+ bootloaderBinary,
738
+ fwBinaryMap,
739
+ installItems,
740
+ };
741
+ }
742
+
743
+ // ============================================================
744
+ // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
745
+ // ============================================================
746
+
747
+ /**
748
+ * Prepare the RESC bundle list.
749
+ *
750
+ * Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
751
+ * - resourceBundleFiles supplied: use binaries directly without version checks.
752
+ * - Otherwise load release.resourceBundles from remote config.json; the binaries
753
+ * are prefetched before reboot and synchronization skips matching device headers.
754
+ */
755
+ private prepareProtocolV2ResourceBundles(
247
756
  firmwareType: EFirmwareType,
248
757
  features: Features
249
- ): Promise<ArrayBuffer | null> {
250
- if (this.params.bootloaderBinary) {
251
- return this.params.bootloaderBinary;
758
+ ): ProtocolV2ResourceBundleBinary[] | undefined {
759
+ // Manual binaries are installed directly without comparison.
760
+ if (this.params.resourceBundleFiles?.length) {
761
+ return this.params.resourceBundleFiles.map(file => ({
762
+ name: file.devicePath.split('/').pop() ?? file.devicePath,
763
+ binary: file.binary,
764
+ devicePath: file.devicePath,
765
+ }));
766
+ }
767
+
768
+ if (!this.params.targetsToUpdate?.includes('resource')) {
769
+ return undefined;
252
770
  }
253
771
 
254
- if (this.params.bootloaderVersion) {
255
- const bootResourceUrl = DataManager.getBootloaderResource(features, firmwareType);
256
- if (bootResourceUrl) {
257
- const bootBinary = (await getSysResourceBinary(bootResourceUrl)).binary;
258
- return bootBinary;
772
+ // Remote-config bundles are downloaded and compared later.
773
+ const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
774
+ if (!release?.resourceBundles?.length) return undefined;
775
+
776
+ return release.resourceBundles.map(bundle => ({
777
+ name: bundle.name,
778
+ binary: new ArrayBuffer(0),
779
+ devicePath: bundle.devicePath,
780
+ url: bundle.url,
781
+ version: bundle.version,
782
+ payloadHash: bundle.payloadHash,
783
+ headerHash: bundle.headerHash,
784
+ })) as ProtocolV2ResourceBundleBinary[];
785
+ }
786
+
787
+ private async prefetchProtocolV2ResourceBundles(
788
+ bundles: ProtocolV2ResourceBundleBinary[]
789
+ ): Promise<ProtocolV2ResourceBundleBinary[]> {
790
+ const prefetched: ProtocolV2ResourceBundleBinary[] = [];
791
+ for (const bundle of bundles) {
792
+ let { binary } = bundle;
793
+ if (binary.byteLength === 0) {
794
+ if (!bundle.url) {
795
+ throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
796
+ }
797
+ Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
798
+ ({ binary } = await getSysResourceBinary(bundle.url));
799
+ }
800
+ if (binary.byteLength === 0) {
801
+ throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
259
802
  }
803
+ prefetched.push({ ...bundle, binary });
804
+ }
805
+ return prefetched;
806
+ }
807
+
808
+ /**
809
+ * Synchronize RESC bundles to the device.
810
+ *
811
+ * Manual mode writes supplied binaries directly.
812
+ * Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
813
+ */
814
+ private async syncProtocolV2ResourceBundles(
815
+ bundles: ProtocolV2ResourceBundleBinary[],
816
+ firmwareSize: number
817
+ ): Promise<{ processedSize: number; totalSize: number }> {
818
+ const transferStartTime = Date.now();
819
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
820
+
821
+ const isRemoteMode = bundles.some(bundle => !!bundle.url);
822
+ let bundlesToSync = bundles;
823
+
824
+ // Remote binaries were prefetched before bootloader entry. Only device-local
825
+ // header comparison and transfer are allowed in this phase.
826
+ if (isRemoteMode) {
827
+ const filtered: ProtocolV2ResourceBundleBinary[] = [];
828
+ for (const bundle of bundles) {
829
+ // Compare the existing device header.
830
+ const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
831
+ if (upToDate) {
832
+ Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
833
+ } else {
834
+ filtered.push(bundle);
835
+ }
836
+ }
837
+ bundlesToSync = filtered;
838
+ }
839
+
840
+ if (bundlesToSync.length === 0) {
841
+ Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
842
+ return { processedSize: 0, totalSize: firmwareSize };
843
+ }
844
+
845
+ // Write directly to the device through FileWrite.
846
+ let totalSize = 0;
847
+ for (const b of bundlesToSync) totalSize += b.binary.byteLength;
848
+
849
+ const transferTotalSize = totalSize + firmwareSize;
850
+ let processedSize = 0;
851
+ for (const bundle of bundlesToSync) {
852
+ Log.log(
853
+ `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
854
+ );
855
+ processedSize = await this.protocolV2CommonUpdateProcess({
856
+ payload: bundle.binary,
857
+ filePath: bundle.devicePath,
858
+ processedSize,
859
+ totalSize: transferTotalSize,
860
+ });
861
+ }
862
+
863
+ const elapsedMs = Date.now() - transferStartTime;
864
+ Log.log(
865
+ `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
866
+ elapsedMs / 1000
867
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
868
+ );
869
+ return { processedSize, totalSize: transferTotalSize };
870
+ }
871
+
872
+ /**
873
+ * Compare the existing okpkg OKPP header in remote-config mode.
874
+ */
875
+ private async isProtocolV2ResourceBundleUpToDate(
876
+ bundle: ProtocolV2ResourceBundleBinary
877
+ ): Promise<boolean> {
878
+ if (this.params.forcedUpdateRes) return false;
879
+ if (!bundle.version && !bundle.payloadHash) return false;
880
+
881
+ try {
882
+ const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
883
+ if (!header) return false;
884
+
885
+ if (bundle.version) {
886
+ const cmp = compareProtocolV2Versions(header.version, bundle.version);
887
+ if (cmp === undefined || cmp !== 0) return false;
888
+ }
889
+ if (bundle.payloadHash) {
890
+ const expected = normalizeProtocolV2Hex(bundle.payloadHash);
891
+ if (expected && header.payloadHash !== expected) return false;
892
+ }
893
+ if (bundle.headerHash) {
894
+ const expected = normalizeProtocolV2Hex(bundle.headerHash);
895
+ if (expected && header.headerHash !== expected) return false;
896
+ }
897
+ return true;
898
+ } catch (error) {
899
+ Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
900
+ return false;
260
901
  }
261
- return null;
262
902
  }
263
903
 
264
904
  private isProtocolV2BootloaderMode() {
@@ -269,19 +909,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
269
909
  }
270
910
 
271
911
  async enterProtocolV2BootloaderMode() {
272
- if (this.isProtocolV2BootloaderMode()) {
912
+ // romloader is the first update environment and forwards targets to bootloader.
913
+ // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
914
+ if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
915
+ Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
273
916
  return false;
274
917
  }
275
918
 
276
919
  try {
277
920
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
278
- await this.protocolV2Reboot(DevRebootType.Bootloader);
921
+ await this.protocolV2Reboot(DeviceRebootType.Bootloader);
279
922
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
280
- this.checkDeviceToBootloader(this.payload.connectId);
281
- await this.checkPromise?.promise;
282
- this.checkPromise = null;
283
- await wait(1500);
284
- await this.device.acquire?.();
923
+ await wait(1000);
924
+ await this.waitForProtocolV2BootloaderMode();
285
925
  return true;
286
926
  } catch (error) {
287
927
  if (error instanceof HardwareError) {
@@ -292,268 +932,327 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
292
932
  }
293
933
  }
294
934
 
935
+ private async waitForProtocolV2BootloaderMode(
936
+ timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
937
+ retryInterval = 1000
938
+ ) {
939
+ const startTime = Date.now();
940
+ let lastError: unknown;
941
+
942
+ while (Date.now() - startTime < timeout) {
943
+ try {
944
+ await this.reconnectProtocolV2Device();
945
+ const deviceInfo = await requestProtocolV2DeviceInfo({
946
+ commands: this.device.getCommands(),
947
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
948
+ });
949
+ // This is a reconnect probe after a Bootloader reboot. Bootloader does not
950
+ // support DeviceStatusGet, so the generic runtime-mode probe is invalid here.
951
+ const features = this.device.updateProtocolV2Features(deviceInfo, null, 'bootloader');
952
+ assertProtocolV2ReconnectIdentity(
953
+ this.protocolV2ExpectedDeviceId,
954
+ features.deviceId ?? undefined,
955
+ { allowMissingActual: !!features.bootloaderMode }
956
+ );
957
+ if (features?.bootloaderMode) {
958
+ return features;
959
+ }
960
+ lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
961
+ } catch (error) {
962
+ lastError = error;
963
+ Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
964
+ }
965
+ await wait(retryInterval);
966
+ }
967
+
968
+ throw ERRORS.TypedError(
969
+ HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure,
970
+ `Protocol V2 bootloader not ready within ${timeout / 1000}s: ${this.normalizeErrorMessage(
971
+ lastError
972
+ )}`
973
+ );
974
+ }
975
+
295
976
  /**
296
- * 收集按 DevFirmwareTargetType 拆分的显式目标二进制。
297
- * 文件名仅用于 staging 路径展示,target_id 已显式给定。
977
+ * Collect explicit target binaries grouped by DeviceFirmwareTargetType.
978
+ * Filenames are display-only staging paths because target_id is explicit.
298
979
  */
299
980
  private collectExplicitTargetBinaries() {
300
- const entries: { fileName: string; binary: ArrayBuffer; targetId: number }[] = [];
981
+ const entries: ProtocolV2TargetBinary[] = [];
301
982
  const push = (binary: ArrayBuffer | undefined, fileName: string, targetId: number) => {
302
983
  if (binary) entries.push({ fileName, binary, targetId });
303
984
  };
304
985
 
305
- push(
306
- this.params.romloaderBinary,
307
- 'romloader.bin',
308
- ProtocolV2FirmwareTargetType.TARGET_MAIN_BOOT
309
- );
986
+ if (this.params.romloaderBinary) {
987
+ throw ERRORS.TypedError(
988
+ HardwareErrorCode.RuntimeError,
989
+ PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
990
+ );
991
+ }
310
992
  push(
311
993
  this.params.applicationP1Binary,
312
994
  'application_p1.bin',
313
- ProtocolV2FirmwareTargetType.TARGET_MAIN_APP
995
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1
314
996
  );
315
997
  push(
316
998
  this.params.applicationP2Binary,
317
999
  'application_p2.bin',
318
- ProtocolV2FirmwareTargetType.TARGET_MAIN_APP
1000
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
1001
+ );
1002
+ push(
1003
+ this.params.coprocessorBinary,
1004
+ 'coprocessor.bin',
1005
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR
319
1006
  );
320
- push(this.params.coprocessorBinary, 'coprocessor.bin', ProtocolV2FirmwareTargetType.TARGET_BT);
321
- push(this.params.se01Binary, 'se01.bin', ProtocolV2FirmwareTargetType.TARGET_SE1);
322
- push(this.params.se02Binary, 'se02.bin', ProtocolV2FirmwareTargetType.TARGET_SE2);
323
- push(this.params.se03Binary, 'se03.bin', ProtocolV2FirmwareTargetType.TARGET_SE3);
324
- push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.TARGET_SE4);
1007
+ push(this.params.se01Binary, 'se01.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01);
1008
+ push(this.params.se02Binary, 'se02.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02);
1009
+ push(this.params.se03Binary, 'se03.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03);
1010
+ push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04);
325
1011
  return entries;
326
1012
  }
327
1013
 
328
- private async prepareFirmwareAndBleBinary(firmwareType: EFirmwareType, features: Features) {
329
- const fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[] = [];
330
- if (this.params.firmwareBinary) {
331
- fwBinaryMap.push({
332
- fileName: 'firmware.bin',
333
- binary: this.params.firmwareBinary,
334
- });
335
- } else if (this.params.firmwareVersion) {
336
- const firmwareBinary = (
337
- await getBinary({
338
- features,
339
- version: this.params.firmwareVersion,
340
- updateType: 'firmware',
341
- isUpdateBootloader: false,
342
- firmwareType,
343
- })
344
- ).binary;
345
- fwBinaryMap.push({
346
- fileName: 'firmware.bin',
347
- binary: firmwareBinary,
348
- });
349
- }
350
-
351
- if (this.params.bleBinary) {
352
- fwBinaryMap.push({
353
- fileName: 'ble-firmware.bin',
354
- binary: this.params.bleBinary,
355
- });
356
- } else if (this.params.bleVersion) {
357
- const bleBinary = await getBinary({
358
- features,
359
- version: this.params.bleVersion,
360
- updateType: 'ble',
361
- firmwareType,
362
- });
363
- fwBinaryMap.push({
364
- fileName: 'ble-firmware.bin',
365
- binary: bleBinary.binary,
366
- });
367
- }
368
-
369
- return fwBinaryMap;
370
- }
371
-
372
1014
  private async executeProtocolV2Update({
373
- resourceBinary,
374
1015
  fwBinaryMap,
375
1016
  bootloaderBinary,
1017
+ installItems,
1018
+ resourceBundles,
376
1019
  }: {
377
- resourceBinary: ArrayBuffer | null;
378
- fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[];
379
- bootloaderBinary: ArrayBuffer | null;
1020
+ fwBinaryMap?: ProtocolV2TargetBinary[];
1021
+ bootloaderBinary?: ArrayBuffer | null;
1022
+ installItems?: ProtocolV2InstallItem[];
1023
+ resourceBundles?: ProtocolV2ResourceBundleBinary[];
380
1024
  }) {
381
- let totalSize = 0;
382
- let processedSize = 0;
1025
+ const orderedInstallItems =
1026
+ installItems ??
1027
+ this.buildProtocolV2InstallItems({
1028
+ bootloaderBinary: bootloaderBinary ?? null,
1029
+ fwBinaryMap: fwBinaryMap ?? [],
1030
+ });
383
1031
 
384
- if (resourceBinary) totalSize += resourceBinary.byteLength;
385
- for (const fwbinary of fwBinaryMap) totalSize += fwbinary.binary.byteLength;
386
- if (bootloaderBinary) totalSize += bootloaderBinary.byteLength;
1032
+ let firmwareSize = 0;
1033
+ for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
387
1034
 
388
1035
  this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
389
1036
 
390
- const targets: Array<{ target_id: number; path: string }> = [];
391
-
392
- if (resourceBinary) {
393
- // resource 仅支持单文件 .bin:整文件一次上传,target path 指向该文件
394
- const resourceFilePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}resource.bin`;
395
- processedSize = await this.protocolV2CommonUpdateProcess({
396
- payload: resourceBinary,
397
- filePath: resourceFilePath,
398
- processedSize,
399
- totalSize,
400
- });
401
- targets.push({
402
- target_id: ProtocolV2FirmwareTargetType.TARGET_RESOURCE,
403
- path: resourceFilePath,
404
- });
1037
+ let processedSize = 0;
1038
+ let totalSize = firmwareSize;
1039
+ if (resourceBundles?.length) {
1040
+ const resourceTransfer = await this.syncProtocolV2ResourceBundles(
1041
+ resourceBundles,
1042
+ firmwareSize
1043
+ );
1044
+ processedSize = resourceTransfer.processedSize;
1045
+ totalSize = resourceTransfer.totalSize;
405
1046
  }
406
1047
 
407
- if (bootloaderBinary) {
408
- const bootloaderPath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}bootloader.bin`;
409
- processedSize = await this.protocolV2CommonUpdateProcess({
410
- payload: bootloaderBinary,
411
- filePath: bootloaderPath,
412
- processedSize,
413
- totalSize,
414
- });
415
- targets.push({
416
- target_id: ProtocolV2FirmwareTargetType.TARGET_MAIN_BOOT,
417
- path: bootloaderPath,
418
- });
1048
+ // Skip staging and installation when the update contains RESC bundles only.
1049
+ if (orderedInstallItems.length === 0) {
1050
+ Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1051
+ if (totalSize > 0) {
1052
+ this.postProgressMessage(100, 'transferData');
1053
+ }
1054
+ return;
419
1055
  }
1056
+ let transferredSize = processedSize;
1057
+ const transferStartTime = Date.now();
1058
+ const transferTransport = this.getProtocolV2FirmwareTransferTransport();
1059
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1060
+ const onTransferredBytes = (bytes: number) => {
1061
+ transferredSize = bytes;
1062
+ };
1063
+ Log.log(
1064
+ `[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
1065
+ );
420
1066
 
421
- for (const fwbinary of fwBinaryMap) {
422
- const firmwarePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${fwbinary.fileName}`;
423
- processedSize = await this.protocolV2CommonUpdateProcess({
424
- payload: fwbinary.binary,
425
- filePath: firmwarePath,
426
- processedSize,
427
- totalSize,
428
- });
429
- targets.push({
430
- target_id: fwbinary.targetId ?? protocolV2FileNameToTargetId(fwbinary.fileName),
431
- path: firmwarePath,
432
- });
433
- }
1067
+ const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
434
1068
 
435
- if (totalSize > 0) {
436
- this.postProgressMessage(100, 'transferData');
1069
+ try {
1070
+ for (const item of orderedInstallItems) {
1071
+ const filePath = this.getProtocolV2InstallItemStagingPath(item);
1072
+ Log.log(
1073
+ `[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
1074
+ );
1075
+ processedSize = await this.protocolV2CommonUpdateProcess({
1076
+ payload: item.binary,
1077
+ filePath,
1078
+ processedSize,
1079
+ totalSize,
1080
+ onTransferredBytes,
1081
+ });
1082
+ transferredSize = processedSize;
1083
+ await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
1084
+
1085
+ stagedInstallTargets.push({
1086
+ ...item,
1087
+ path: filePath,
1088
+ });
1089
+ }
1090
+
1091
+ if (totalSize > 0) {
1092
+ this.postProgressMessage(100, 'transferData');
1093
+ }
1094
+
1095
+ const elapsedMs = Date.now() - transferStartTime;
1096
+ Log.log(
1097
+ `[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
1098
+ elapsedMs / 1000
1099
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
1100
+ );
1101
+ } catch (error) {
1102
+ const elapsedMs = Date.now() - transferStartTime;
1103
+ Log.warn(
1104
+ `[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
1105
+ elapsedMs / 1000
1106
+ ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
1107
+ );
1108
+ throw error;
437
1109
  }
438
1110
 
439
1111
  this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
440
- const startResponse = await this.protocolV2StartFirmwareUpdate({ targets });
441
- await this.waitForProtocolV2FirmwareUpdateComplete(targets, startResponse);
1112
+
1113
+ const allTargets = stagedInstallTargets.map(item => ({
1114
+ target_id: item.targetId,
1115
+ path: item.path,
1116
+ }));
1117
+ Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1118
+ await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1119
+ await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
442
1120
  }
443
1121
 
444
- private async queryProtocolV2FirmwareUpdateStatus() {
445
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
446
- return typedCall(
447
- 'DevGetFirmwareUpdateStatus',
448
- 'DevFirmwareUpdateStatus',
449
- {},
450
- {
451
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
452
- }
453
- );
1122
+ private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
1123
+ return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
454
1124
  }
455
1125
 
456
- private async pingProtocolV2Device() {
1126
+ private async verifyProtocolV2StagedFile(path: string, expectedSize: number) {
457
1127
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
458
- await typedCall(
459
- 'Ping',
460
- 'Success',
461
- { message: 'firmware-update' },
462
- {
463
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
464
- }
465
- );
1128
+ const response = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', { path });
1129
+ const actualSize = toProtocolV2FiniteNumber(response.message?.size);
1130
+ if (!response.message?.exist || response.message?.directory || actualSize !== expectedSize) {
1131
+ throw ERRORS.TypedError(
1132
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1133
+ `staged file verification failed: path=${path} exist=${!!response.message
1134
+ ?.exist} expected=${expectedSize} actual=${actualSize ?? 'unknown'}`
1135
+ );
1136
+ }
466
1137
  }
467
1138
 
468
1139
  private assertProtocolV2TargetStatus(
469
1140
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
470
1141
  expectedTargetIds: Set<number>
471
1142
  ) {
1143
+ Log.log(
1144
+ `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
1145
+ );
472
1146
  const failedTarget = statusTargets.find(
473
1147
  target =>
474
- expectedTargetIds.has(target.target_id) &&
475
- target.status === PROTOCOL_V2_TARGET_STATUS_FAILED
1148
+ expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1149
+ isProtocolV2TargetStatusFailed(target.status)
476
1150
  );
477
1151
  if (failedTarget) {
1152
+ const failedTargetDetails = JSON.stringify({
1153
+ targetId: failedTarget.target_id,
1154
+ status: failedTarget.status,
1155
+ payloadVersion: failedTarget.payload_version,
1156
+ path: failedTarget.path,
1157
+ });
1158
+ Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
478
1159
  throw ERRORS.TypedError(
479
1160
  HardwareErrorCode.FirmwareError,
480
- `Protocol V2 firmware target ${failedTarget.target_id} failed`
1161
+ `Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
1162
+ failedTarget.status ?? 'unknown'
1163
+ } payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
1164
+ failedTarget.path ?? 'unknown'
1165
+ }`
481
1166
  );
482
1167
  }
483
1168
 
484
- const completedTargets = statusTargets.filter(
485
- target =>
486
- expectedTargetIds.has(target.target_id) &&
487
- target.status === PROTOCOL_V2_TARGET_STATUS_FINISHED
1169
+ const matchingTargets = statusTargets.filter(target =>
1170
+ expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1171
+ );
1172
+ const completedTargets = matchingTargets.filter(target =>
1173
+ isProtocolV2TargetStatusFinished(target.status)
488
1174
  );
489
1175
  if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
1176
+ this.postProgressMessage(100, 'installingFirmware');
490
1177
  return true;
491
1178
  }
492
1179
 
493
- const inProgressTarget = statusTargets.find(
494
- target =>
495
- expectedTargetIds.has(target.target_id) &&
496
- target.status === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS
497
- );
498
- if (inProgressTarget) {
499
- this.postProgressMessage(99, 'installingFirmware');
1180
+ if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
1181
+ const hasInProgressTarget = matchingTargets.some(target =>
1182
+ isProtocolV2TargetStatusInProgress(target.status)
1183
+ );
1184
+ const completedProgress = Math.floor(
1185
+ (completedTargets.length / expectedTargetIds.size) * 100
1186
+ );
1187
+ // The protocol exposes no per-target percentage, so report coarse progress by
1188
+ // completed targets and use 1% once work starts to keep the UI responsive.
1189
+ const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
1190
+ this.postProgressMessage(progress, 'installingFirmware');
500
1191
  }
501
1192
 
502
1193
  return false;
503
1194
  }
504
1195
 
505
1196
  private async waitForProtocolV2FirmwareUpdateComplete(
506
- targets: Array<{ target_id: number; path: string }>,
507
- startResponse?: ProtocolV2FirmwareUpdateStartResponse
1197
+ targets: Array<{ target_id: number; path: string }>
508
1198
  ) {
509
1199
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
510
- if (startResponse?.type === 'Success') {
511
- return;
512
- }
513
- if (startResponse?.type === 'DevFirmwareUpdateStatus') {
514
- const statusTargets = (startResponse.message.targets ??
515
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
516
- if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
517
- return;
518
- }
519
- }
520
-
521
1200
  const startTime = Date.now();
522
1201
  let lastError: unknown;
523
1202
 
524
1203
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
525
1204
  try {
526
- const statusRes = await this.queryProtocolV2FirmwareUpdateStatus();
527
- const statusTargets = (statusRes.message.targets ??
528
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
529
- if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
530
- return;
1205
+ await this.reconnectProtocolV2Device();
1206
+ try {
1207
+ const statusResponse = await this.device.getCommands().typedCall(
1208
+ 'DeviceFirmwareUpdateStatusGet',
1209
+ 'DeviceFirmwareUpdateStatus',
1210
+ {
1211
+ fields: {
1212
+ status: true,
1213
+ payload_version: true,
1214
+ path: true,
1215
+ },
1216
+ },
1217
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1218
+ );
1219
+ if (
1220
+ this.assertProtocolV2TargetStatus(
1221
+ (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1222
+ expectedTargetIds
1223
+ )
1224
+ ) {
1225
+ return;
1226
+ }
1227
+ lastError = new Error('Protocol V2 firmware targets are still installing');
1228
+ } catch (error) {
1229
+ if (
1230
+ error instanceof HardwareError &&
1231
+ error.errorCode === HardwareErrorCode.FirmwareError
1232
+ ) {
1233
+ throw error;
1234
+ }
1235
+ // App firmware does not register DeviceFirmwareUpdateStatusGet. If the
1236
+ // device already rebooted into App, this endpoint error signals that the
1237
+ // install phase ended; the later phase performs the final readiness check.
1238
+ if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
1239
+ Log.log(
1240
+ '[FirmwareUpdateV4] firmware status endpoint unavailable after reboot; continue with normal-mode verification'
1241
+ );
1242
+ return;
1243
+ }
1244
+ lastError = error;
1245
+ Log.log(
1246
+ '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
1247
+ error
1248
+ );
531
1249
  }
532
1250
  } catch (error) {
533
1251
  lastError = error;
534
1252
  if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
535
1253
  throw error;
536
1254
  }
537
- Log.log('Protocol V2 firmware install status polling failed: ', error);
538
- if (isProtocolV2PollingTransientError(error)) {
539
- try {
540
- await this.reconnectProtocolV2Device();
541
- } catch (reconnectError) {
542
- lastError = reconnectError;
543
- Log.log(
544
- 'Protocol V2 firmware install reconnect/status polling failed: ',
545
- reconnectError
546
- );
547
- }
548
- try {
549
- await this.pingProtocolV2Device();
550
- Log.log('Protocol V2 firmware status unavailable, Ping is ready');
551
- return;
552
- } catch (pingError) {
553
- lastError = pingError;
554
- Log.log('Protocol V2 firmware install Ping polling failed: ', pingError);
555
- }
556
- }
1255
+ Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
557
1256
  }
558
1257
  await wait(1000);
559
1258
  }
@@ -565,13 +1264,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
565
1264
  }
566
1265
 
567
1266
  private async exitProtocolV2BootloaderToNormal() {
568
- this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
569
- await this.protocolV2Reboot(DevRebootType.Normal);
1267
+ await this.reconnectProtocolV2Device();
1268
+ // The connection may still be in bootloader. Request a Normal reboot directly;
1269
+ // repeating it after an automatic App reboot is idempotent.
1270
+ await this.protocolV2Reboot(DeviceRebootType.Normal);
570
1271
  }
571
1272
 
572
1273
  private async waitForProtocolV2FinalFeatures() {
573
1274
  const features = await this.waitForProtocolV2ReconnectAndFeatures(
574
- PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT
1275
+ PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
575
1276
  );
576
1277
 
577
1278
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
@@ -600,11 +1301,30 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
600
1301
  const deviceInfo = await requestProtocolV2DeviceInfo({
601
1302
  commands: this.device.getCommands(),
602
1303
  timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
603
- // 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
1304
+ // Completion needs target versions only; keep scope aligned with the request.
604
1305
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
605
1306
  });
606
- return this.device.updateProtocolV2Features(deviceInfo);
1307
+ const features = await this.device.probeProtocolV2RuntimeState(
1308
+ deviceInfo,
1309
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
1310
+ );
1311
+ assertProtocolV2ReconnectIdentity(
1312
+ this.protocolV2ExpectedDeviceId,
1313
+ features.deviceId ?? undefined
1314
+ );
1315
+ if (features.mode !== 'normal' || features.bootloaderMode) {
1316
+ throw ERRORS.TypedError(
1317
+ HardwareErrorCode.DeviceNotFound,
1318
+ 'Protocol V2 device is still in bootloader mode'
1319
+ );
1320
+ }
1321
+ return features;
607
1322
  } catch (error) {
1323
+ // A confirmed deviceId change is a firmware contract violation and cannot be
1324
+ // fixed by polling. A temporarily missing identity may still become ready.
1325
+ if (this.isProtocolV2ReconnectIdentityMismatch(error)) {
1326
+ throw error;
1327
+ }
608
1328
  lastError = error;
609
1329
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
610
1330
  await wait(1000);
@@ -628,27 +1348,105 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
628
1348
  const deviceDiff = await this.device.deviceConnector?.enumerate();
629
1349
  const devicesDescriptor = deviceDiff?.descriptors ?? [];
630
1350
 
631
- const { deviceList } = await DevicePool.getDevices(devicesDescriptor, this.connectId);
1351
+ if (
1352
+ DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
1353
+ devicesDescriptor.length === 1
1354
+ ) {
1355
+ this.device.updateDescriptor(
1356
+ {
1357
+ ...devicesDescriptor[0],
1358
+ protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
1359
+ },
1360
+ true
1361
+ );
1362
+ await this.ensureProtocolV2DeviceAcquired();
1363
+ this.device.commands.disposed = false;
1364
+ this.device.getCommands().mainId = this.device.mainId ?? '';
1365
+ assertProtocolV2ReconnectIdentity(
1366
+ this.protocolV2ExpectedDeviceId,
1367
+ this.device.getCurrentDeviceId(),
1368
+ // Cached identity may be absent immediately after acquire. Reject only an
1369
+ // existing mismatch; the final refresh performs strict identity validation.
1370
+ { allowMissingActual: true }
1371
+ );
1372
+ return;
1373
+ }
1374
+
1375
+ // App and bootloader serials may differ temporarily. During V4 reconnect, accept
1376
+ // only a uniquely enumerated device instead of repeatedly using the old App path.
1377
+ const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
1378
+ connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1379
+ });
632
1380
  if (deviceList.length !== 1) {
633
1381
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
634
1382
  }
635
1383
 
1384
+ Log.debug(
1385
+ 'Protocol V2 firmware reconnect using single enumerated device:',
1386
+ deviceList[0].getConnectId()
1387
+ );
636
1388
  this.device.updateFromCache(deviceList[0]);
637
- await this.device.acquire();
1389
+ await this.ensureProtocolV2DeviceAcquired();
638
1390
  this.device.commands.disposed = false;
639
1391
  this.device.getCommands().mainId = this.device.mainId ?? '';
1392
+ assertProtocolV2ReconnectIdentity(
1393
+ this.protocolV2ExpectedDeviceId,
1394
+ this.device.getCurrentDeviceId(),
1395
+ // Cached identity may be absent immediately after acquire. Reject only an
1396
+ // existing mismatch; the final refresh performs strict identity validation.
1397
+ { allowMissingActual: true }
1398
+ );
640
1399
  }
641
1400
 
642
- private async protocolV2CommonUpdateProcess({
1401
+ /**
1402
+ * After acquiring a USB session, polling reuses its command channel. Reacquire only
1403
+ * after reboot or enumeration changes cause hasDeviceAcquire() to become false.
1404
+ */
1405
+ private async ensureProtocolV2DeviceAcquired() {
1406
+ const commands = this.device.getCommands();
1407
+ if (this.device.hasDeviceAcquire() && !commands.disposed) {
1408
+ return;
1409
+ }
1410
+ await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, {
1411
+ throwOnRunPromiseError: true,
1412
+ });
1413
+ }
1414
+
1415
+ private isProtocolV2ReconnectIdentityMismatch(error: unknown) {
1416
+ return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect identity mismatch');
1417
+ }
1418
+
1419
+ private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1420
+ let lastError: unknown;
1421
+ for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
1422
+ try {
1423
+ return await this.protocolV2WriteWholeFile(params);
1424
+ } catch (error) {
1425
+ lastError = error;
1426
+ Log.error(
1427
+ `Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
1428
+ error
1429
+ );
1430
+ if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
1431
+ break;
1432
+ }
1433
+ await this.recoverProtocolV2FileTransfer();
1434
+ }
1435
+ }
1436
+
1437
+ throw ERRORS.TypedError(
1438
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
1439
+ `transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
1440
+ );
1441
+ }
1442
+
1443
+ private async protocolV2WriteWholeFile({
643
1444
  payload,
644
1445
  filePath,
645
1446
  processedSize,
646
1447
  totalSize,
647
- }: PROTO.FirmwareUpload & {
648
- filePath: string;
649
- processedSize?: number;
650
- totalSize?: number;
651
- }) {
1448
+ onTransferredBytes,
1449
+ }: ProtocolV2FileTransferParams) {
652
1450
  const chunkSize = this.getProtocolV2FirmwareChunkSize();
653
1451
  let offset = 0;
654
1452
  const getUploadProgress = (fileOffset: number) => {
@@ -663,9 +1461,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
663
1461
  const chunkLength = chunkEnd - offset;
664
1462
  const chunk = payload.slice(offset, chunkEnd);
665
1463
  const overwrite = offset === 0;
666
- const progress = getUploadProgress(chunkEnd);
1464
+ const progress = getProtocolV2DeviceTransferProgress(
1465
+ (processedSize ?? 0) + offset,
1466
+ (processedSize ?? 0) + chunkEnd,
1467
+ totalSize ?? payload.byteLength
1468
+ );
667
1469
 
668
- const writeRes = await this.fileWriteWithRetry(
1470
+ const writeRes = await this.fileWriteChunk(
669
1471
  filePath,
670
1472
  payload.byteLength,
671
1473
  offset,
@@ -673,25 +1475,38 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
673
1475
  overwrite,
674
1476
  progress
675
1477
  );
676
- const processedByte = Number(writeRes.message.processed_byte);
677
- const nextOffset =
678
- Number.isFinite(processedByte) && processedByte > offset
679
- ? processedByte
680
- : offset + chunkLength;
681
- if (nextOffset <= offset || nextOffset > payload.byteLength) {
1478
+ const rawProcessedByte = writeRes.message.processed_byte;
1479
+ const processedByte = Number(rawProcessedByte);
1480
+ const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
1481
+ if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
682
1482
  throw ERRORS.TypedError(
683
1483
  HardwareErrorCode.EmmcFileWriteFirmwareError,
684
1484
  `invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
685
1485
  );
686
1486
  }
687
1487
  offset = nextOffset;
1488
+ onTransferredBytes?.((processedSize ?? 0) + offset);
688
1489
  this.postProgressMessage(getUploadProgress(offset), 'transferData');
689
1490
  }
690
1491
 
691
1492
  return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
692
1493
  }
693
1494
 
694
- private async fileWriteWithRetry(
1495
+ private getProtocolV2FirmwareTransferTransport() {
1496
+ const env = DataManager.getSettings('env');
1497
+ if (env && DataManager.isBleConnect(env)) {
1498
+ return 'BLE';
1499
+ }
1500
+ if (
1501
+ env &&
1502
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
1503
+ ) {
1504
+ return 'WebUSB';
1505
+ }
1506
+ return env ?? 'unknown';
1507
+ }
1508
+
1509
+ private async fileWriteChunk(
695
1510
  filePath: string,
696
1511
  totalFileSize: number,
697
1512
  offset: number,
@@ -699,57 +1514,37 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
699
1514
  overwrite: boolean,
700
1515
  progress: number | null
701
1516
  ): Promise<TypedResponseMessage<'FilesystemFile'>> {
702
- const writeFunc = async () => {
703
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
704
- const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
705
- file: {
706
- path: filePath,
707
- offset,
708
- total_size: totalFileSize,
709
- data: chunk,
710
- },
711
- overwrite,
712
- append: false,
713
- ui_percentage: progress ?? undefined,
714
- });
715
- if (writeRes.type !== 'FilesystemFile') {
716
- if ((writeRes as any).type === 'CallMethodError') {
717
- if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
718
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
719
- }
1517
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1518
+ const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
1519
+ file: {
1520
+ path: filePath,
1521
+ offset,
1522
+ total_size: totalFileSize,
1523
+ data: chunk,
1524
+ },
1525
+ overwrite,
1526
+ append: false,
1527
+ ui_percentage: progress ?? undefined,
1528
+ });
1529
+ if (writeRes.type !== 'FilesystemFile') {
1530
+ if ((writeRes as any).type === 'CallMethodError') {
1531
+ if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
1532
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
720
1533
  }
721
- throw ERRORS.TypedError(
722
- HardwareErrorCode.EmmcFileWriteFirmwareError,
723
- 'transfer data error'
724
- );
725
1534
  }
726
- return writeRes;
727
- };
1535
+ throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
1536
+ }
1537
+ return writeRes;
1538
+ }
728
1539
 
729
- let retryCount = 10;
730
- while (retryCount > 0) {
731
- try {
732
- const result = await writeFunc();
733
- return result;
734
- } catch (error) {
735
- Log.error(`fileWrite error: `, error);
736
- retryCount--;
737
- if (retryCount === 0) {
738
- throw ERRORS.TypedError(
739
- HardwareErrorCode.EmmcFileWriteFirmwareError,
740
- 'transfer data error'
741
- );
742
- }
743
- const env = DataManager.getSettings('env');
744
- if (DataManager.isBleConnect(env)) {
745
- await wait(3000);
746
- await this.acquireProtocolV2BleDevice();
747
- await this.device.initialize();
748
- }
749
- await wait(2000);
750
- }
1540
+ private async recoverProtocolV2FileTransfer() {
1541
+ const env = DataManager.getSettings('env');
1542
+ if (DataManager.isBleConnect(env)) {
1543
+ await wait(3000);
1544
+ await this.acquireProtocolV2BleDevice();
1545
+ await this.device.initialize();
751
1546
  }
752
- throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
1547
+ await wait(2000);
753
1548
  }
754
1549
 
755
1550
  private async acquireProtocolV2BleDevice() {
@@ -767,44 +1562,30 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
767
1562
  targets: Array<{ target_id: number; path: string }>;
768
1563
  }) {
769
1564
  const commands = this.device.getCommands();
770
- let response: ProtocolV2FirmwareUpdateStartResponse;
771
- try {
772
- response = await commands.typedCall(
773
- 'DevFirmwareUpdate',
774
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
775
- {
776
- targets,
777
- },
778
- {
779
- intermediateTypes: ['DevFirmwareInstallProgress'],
780
- onIntermediateResponse: (response: { message?: { progress?: number } }) => {
781
- const progress = Number(response.message?.progress);
782
- if (Number.isFinite(progress)) {
783
- this.postProgressMessage(Math.min(progress, 99), 'installingFirmware');
784
- }
785
- },
786
- }
787
- );
788
- } catch (error) {
789
- if (isProtocolV2DeviceDisconnectedError(error)) {
790
- Log.log('Rebooting device');
791
- } else {
792
- throw error;
793
- }
794
- }
1565
+ const response: ProtocolV2FirmwareUpdateStartResponse = await commands.typedCall(
1566
+ 'DeviceFirmwareUpdateRequest',
1567
+ 'Success',
1568
+ { targets },
1569
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
1570
+ );
1571
+ // Success acknowledges that the device accepted installation. End confirmation
1572
+ // and begin status polling only after receiving this ACK.
795
1573
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
1574
+ this.postProgressMessage(0, 'installingFirmware');
796
1575
  return response;
797
1576
  }
798
1577
 
799
- private async protocolV2Reboot(rebootType: DevRebootType) {
1578
+ private async protocolV2Reboot(rebootType: DeviceRebootType) {
800
1579
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
801
1580
  try {
802
- const res = await typedCall('DevReboot', 'Success', {
1581
+ const res = await typedCall('DeviceReboot', 'Success', {
803
1582
  reboot_type: rebootType,
804
1583
  });
1584
+ this.device.markProtocolV2Reboot(rebootType);
805
1585
  return res.message;
806
1586
  } catch (error) {
807
1587
  if (isProtocolV2DeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
1588
+ this.device.markProtocolV2Reboot(rebootType);
808
1589
  return { message: 'Device rebooted successfully' };
809
1590
  }
810
1591
  throw error;