@onekeyfe/hd-core 1.2.0-alpha.66 → 1.2.0-alpha.67
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.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -313
- package/__tests__/DeviceCommands.test.ts +13 -59
- package/__tests__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -198
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/device-lifecycle-events.test.ts +3 -105
- package/__tests__/device-pool-state.test.ts +0 -22
- package/__tests__/device-settings.test.ts +18 -39
- package/__tests__/device-state-contract.test.ts +0 -1
- package/__tests__/device-state-mapper.test.ts +1 -13
- package/__tests__/device-utils.test.ts +1 -48
- package/__tests__/deviceSettings.test.ts +1 -11
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +12 -132
- package/__tests__/homescreen.test.ts +1 -41
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +26 -3
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +0 -19
- package/__tests__/open-wallet-session.test.ts +37 -568
- package/__tests__/protocol-v2-unlock-policy.test.ts +37 -300
- package/__tests__/protocol-v2.test.ts +1153 -1458
- package/__tests__/protocolV2FileWrite.test.ts +0 -152
- package/__tests__/protocolV2UiInteraction.test.ts +0 -63
- package/__tests__/public-pro2-api-boundary.test.ts +0 -1
- package/__tests__/search-devices.test.ts +3 -12
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/dist/api/BaseMethod.d.ts +1 -3
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +27 -13
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +2 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +11 -4
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -9
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +0 -2
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -9
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +6 -29
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -2
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/events/device.d.ts +0 -4
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +2 -4
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -31
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +2 -10
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +247 -243
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3773 -3054
- package/dist/inject.d.ts +1 -6
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +4 -0
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -6
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +5 -3
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +1 -5
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -2
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +70 -4
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +10 -7
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +0 -3
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +0 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +21 -38
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -4
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +0 -4
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts +0 -10
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +10 -8
- package/src/api/CheckAllFirmwareRelease.ts +60 -59
- package/src/api/FirmwareUpdate.ts +15 -7
- package/src/api/FirmwareUpdateV2.ts +298 -126
- package/src/api/FirmwareUpdateV3.ts +265 -63
- package/src/api/FirmwareUpdateV4.ts +1017 -602
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -71
- package/src/api/SearchDevices.ts +1 -3
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -36
- package/src/api/device/DeviceSettings.ts +4 -5
- package/src/api/device/DeviceUnlock.ts +3 -8
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -27
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +144 -39
- package/src/api/helpers/protocolV2FileWrite.ts +63 -202
- package/src/api/index.ts +0 -2
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +13 -2
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
- package/src/api/protocol-v2/helpers.ts +1 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/core/deviceEventRegistration.ts +0 -4
- package/src/core/index.ts +111 -213
- package/src/data/messages/messages-protocol-v2.json +0 -25
- package/src/data-manager/DataManager.ts +41 -113
- package/src/data-manager/TransportManager.ts +0 -6
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +49 -274
- package/src/device/DeviceCommands.ts +4 -31
- package/src/device/DeviceConnector.ts +13 -33
- package/src/device/DevicePool.ts +7 -21
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +0 -3
- package/src/events/device.ts +0 -4
- package/src/events/logBlockEvent.ts +0 -1
- package/src/events/ui-promise.ts +2 -4
- package/src/events/ui-request.ts +2 -36
- package/src/events/ui-response.ts +2 -12
- package/src/index.ts +5 -0
- package/src/inject.ts +24 -60
- package/src/lowLevelInject.ts +8 -7
- package/src/protocols/protocol-v2/features.ts +7 -10
- package/src/protocols/protocol-v2/firmware.ts +0 -2
- package/src/protocols/protocol-v2/lockedError.ts +10 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
- package/src/protocols/protocol-v2/walletSession.ts +36 -214
- package/src/topLevelInject.ts +8 -7
- package/src/types/api/checkAllFirmwareRelease.ts +5 -3
- package/src/types/api/deviceUnlock.ts +1 -12
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -1
- package/src/types/api/firmwareUpdate.ts +78 -6
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -13
- package/src/types/api/protocolV2.ts +0 -13
- package/src/types/device.ts +3 -15
- package/src/types/params.ts +1 -7
- package/src/types/settings.ts +22 -60
- package/src/utils/deviceFeaturesCompat.ts +6 -5
- package/src/utils/deviceFeaturesUtils.ts +3 -14
- package/src/utils/deviceInfoUtils.ts +0 -2
- package/src/utils/deviceSettings.ts +0 -3
- package/src/utils/homescreen.ts +6 -32
- package/src/utils/index.ts +1 -6
- package/src/utils/pro2Wallpaper.ts +8 -35
- package/__tests__/core-initialization.test.ts +0 -23
- package/__tests__/device-connector-protocol.test.ts +0 -81
- package/__tests__/deviceUploadNft.test.ts +0 -316
- package/__tests__/pro2Nft.test.ts +0 -108
- package/__tests__/protocol-binding.test.ts +0 -89
- package/__tests__/protocol-v2-resources.test.ts +0 -340
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
- package/__tests__/uiProgressThrottle.test.ts +0 -74
- package/__tests__/uiPromiseRegistry.test.ts +0 -115
- package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
- package/dist/core/uiPromiseRegistry.d.ts +0 -6
- package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -30
- package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
- package/dist/utils/pro2Nft.d.ts +0 -31
- package/dist/utils/pro2Nft.d.ts.map +0 -1
- package/dist/utils/uiProgressThrottle.d.ts +0 -3
- package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
- package/src/api/DetectDeviceConnectProtocol.ts +0 -18
- package/src/api/protocol-v2/DeviceUploadNft.ts +0 -197
- package/src/core/uiPromiseRegistry.ts +0 -63
- package/src/protocols/protocol-v2/resources.ts +0 -390
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -131
- package/src/types/api/detectDeviceConnectProtocol.ts +0 -6
- package/src/utils/pro2Nft.ts +0 -142
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -2,13 +2,13 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
|
+
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
5
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
6
7
|
} from '@onekeyfe/hd-transport';
|
|
7
8
|
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
|
|
9
10
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
10
11
|
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
11
|
-
import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
|
|
12
12
|
import { validateParams } from './helpers/paramsValidator';
|
|
13
13
|
import {
|
|
14
14
|
LoggerNames,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getLogger,
|
|
20
20
|
} from '../utils';
|
|
21
21
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
22
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
22
23
|
import { DataManager } from '../data-manager';
|
|
23
24
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
24
25
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -27,40 +28,41 @@ import {
|
|
|
27
28
|
ProtocolV2FirmwareTargetType,
|
|
28
29
|
} from '../protocols/protocol-v2';
|
|
29
30
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
30
|
-
import {
|
|
31
|
-
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
32
|
-
buildProtocolV2ResourceUpdatePlan,
|
|
33
|
-
isProtocolV2ResourceFileValid,
|
|
34
|
-
readProtocolV2ResourceInventory,
|
|
35
|
-
} from '../protocols/protocol-v2/resources';
|
|
36
31
|
import {
|
|
37
32
|
getProtocolV2UnknownErrorText,
|
|
38
33
|
isProtocolV2DeviceDisconnectedError,
|
|
39
34
|
} from './protocol-v2/helpers';
|
|
35
|
+
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
36
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
37
|
+
import {
|
|
38
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
39
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
40
|
+
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
40
41
|
|
|
41
|
-
import type {
|
|
42
|
+
import type {
|
|
43
|
+
FirmwareArtifactReference,
|
|
44
|
+
FirmwareUpdateV4Params,
|
|
45
|
+
FirmwareUpdateV4Target,
|
|
46
|
+
} from '../types/api/firmwareUpdate';
|
|
42
47
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
43
48
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
44
|
-
import type { PROTO } from '../constants';
|
|
45
49
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
46
50
|
import type {
|
|
47
51
|
Features,
|
|
48
52
|
IFirmwareReleaseInfo,
|
|
49
53
|
IProtocolV2FirmwareComponent,
|
|
50
|
-
IProtocolV2Resource,
|
|
51
|
-
IProtocolV2ResourceFile,
|
|
52
54
|
IVersionArray,
|
|
53
55
|
} from '../types';
|
|
56
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
54
57
|
|
|
55
58
|
const Log = getLogger(LoggerNames.Method);
|
|
56
59
|
|
|
57
|
-
const
|
|
58
|
-
const
|
|
60
|
+
const SESSION_ERROR = 'session not found';
|
|
61
|
+
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
|
|
62
|
+
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
|
|
59
63
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
60
|
-
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
61
64
|
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
62
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
63
|
-
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
65
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
64
66
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
65
67
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
66
68
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -71,8 +73,9 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
|
71
73
|
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
72
74
|
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
73
75
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
74
|
-
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT =
|
|
76
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
|
|
75
77
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
78
|
+
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
76
79
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
77
80
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
78
81
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -95,11 +98,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
95
98
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
96
99
|
};
|
|
97
100
|
|
|
98
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
99
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
100
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
101
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
104
102
|
target_id: number | string;
|
|
105
103
|
status?: number | string;
|
|
@@ -113,22 +111,16 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
113
111
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
114
112
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
115
113
|
};
|
|
116
|
-
|
|
117
|
-
path: string;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
121
|
-
filePath: string;
|
|
122
|
-
processedSize?: number;
|
|
123
|
-
totalSize?: number;
|
|
124
|
-
onTransferredBytes?: (transferredBytes: number) => void;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
114
|
+
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
128
115
|
type ProtocolV2ResourceBundleBinary = {
|
|
129
116
|
name: string;
|
|
130
117
|
binary: ArrayBuffer;
|
|
131
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;
|
|
132
124
|
};
|
|
133
125
|
|
|
134
126
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -141,6 +133,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
141
133
|
kind: 'bootloader' | 'firmware';
|
|
142
134
|
};
|
|
143
135
|
|
|
136
|
+
type ProtocolV2InstallSource = {
|
|
137
|
+
fileName: string;
|
|
138
|
+
source: FirmwareByteSource;
|
|
139
|
+
targetId: number;
|
|
140
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
type ProtocolV2ResourceBundleSource = {
|
|
144
|
+
name: string;
|
|
145
|
+
source: FirmwareByteSource;
|
|
146
|
+
devicePath: string;
|
|
147
|
+
version?: IVersionArray;
|
|
148
|
+
payloadHash?: string;
|
|
149
|
+
headerHash?: string;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
type ProtocolV2ExecutionPhaseKind =
|
|
153
|
+
| 'resource-sync'
|
|
154
|
+
| 'bootloader-install'
|
|
155
|
+
| 'bootloader-verify'
|
|
156
|
+
| 'component-install'
|
|
157
|
+
| 'final-verify';
|
|
158
|
+
|
|
159
|
+
type ProtocolV2ExecutionPhase = {
|
|
160
|
+
kind: ProtocolV2ExecutionPhaseKind;
|
|
161
|
+
installSources: ProtocolV2InstallSource[];
|
|
162
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
163
|
+
};
|
|
164
|
+
|
|
144
165
|
type ProtocolV2OkppHeader = {
|
|
145
166
|
type: string;
|
|
146
167
|
version: IVersionArray;
|
|
@@ -243,6 +264,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
243
264
|
);
|
|
244
265
|
};
|
|
245
266
|
|
|
267
|
+
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
268
|
+
error instanceof HardwareError &&
|
|
269
|
+
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
270
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
271
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
272
|
+
|
|
246
273
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
247
274
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
248
275
|
|
|
@@ -280,6 +307,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
280
307
|
|
|
281
308
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
282
309
|
|
|
310
|
+
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
311
|
+
if (!version) return undefined;
|
|
312
|
+
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
316
|
+
const currentNumber = versionArrayToNumber(current);
|
|
317
|
+
const targetNumber = versionArrayToNumber(target);
|
|
318
|
+
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
319
|
+
return currentNumber - targetNumber;
|
|
320
|
+
};
|
|
321
|
+
|
|
283
322
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
284
323
|
Array.from(bytes)
|
|
285
324
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -370,30 +409,20 @@ export const isProtocolV2FirmwareFingerprintValid = (
|
|
|
370
409
|
|
|
371
410
|
export const assertProtocolV2ReconnectIdentity = (
|
|
372
411
|
expectedSerialNumber?: string,
|
|
373
|
-
actualSerialNumber?: string
|
|
374
|
-
expectedPath?: string,
|
|
375
|
-
actualPath?: string
|
|
412
|
+
actualSerialNumber?: string
|
|
376
413
|
) => {
|
|
377
|
-
if (expectedSerialNumber
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
return;
|
|
414
|
+
if (!expectedSerialNumber || !actualSerialNumber) {
|
|
415
|
+
throw ERRORS.TypedError(
|
|
416
|
+
HardwareErrorCode.DeviceNotFound,
|
|
417
|
+
'Protocol V2 reconnect physical identity is unavailable'
|
|
418
|
+
);
|
|
385
419
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
420
|
+
if (actualSerialNumber !== expectedSerialNumber) {
|
|
421
|
+
throw ERRORS.TypedError(
|
|
422
|
+
HardwareErrorCode.DeviceNotFound,
|
|
423
|
+
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
424
|
+
);
|
|
389
425
|
}
|
|
390
|
-
|
|
391
|
-
throw ERRORS.TypedError(
|
|
392
|
-
HardwareErrorCode.DeviceNotFound,
|
|
393
|
-
`Protocol V2 reconnect physical identity unavailable: expected path ${
|
|
394
|
-
expectedPath ?? 'unknown'
|
|
395
|
-
}, received ${actualPath ?? 'unknown'}`
|
|
396
|
-
);
|
|
397
426
|
};
|
|
398
427
|
|
|
399
428
|
/**
|
|
@@ -407,12 +436,20 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
407
436
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
408
437
|
private protocolV2ExpectedSerialNumber?: string;
|
|
409
438
|
|
|
410
|
-
private protocolV2ExpectedPath?: string;
|
|
411
|
-
|
|
412
439
|
getSupportedProtocols() {
|
|
413
440
|
return ['V2'] as const;
|
|
414
441
|
}
|
|
415
442
|
|
|
443
|
+
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
444
|
+
|
|
445
|
+
private protocolV2ExecutionInLoader = false;
|
|
446
|
+
|
|
447
|
+
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
448
|
+
|
|
449
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
450
|
+
|
|
451
|
+
private protocolV2FinalStatusVerified = false;
|
|
452
|
+
|
|
416
453
|
init() {
|
|
417
454
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
418
455
|
this.requireDeviceMode = [];
|
|
@@ -450,10 +487,62 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
450
487
|
{ name: 'firmwareType', type: 'string' },
|
|
451
488
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
452
489
|
{ name: 'platform', type: 'string' },
|
|
453
|
-
{ name: '
|
|
490
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
491
|
+
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
454
492
|
]);
|
|
493
|
+
if (
|
|
494
|
+
payload.expectedDeviceId !== undefined &&
|
|
495
|
+
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
496
|
+
) {
|
|
497
|
+
throw ERRORS.TypedError(
|
|
498
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
499
|
+
'Protocol V2 expected device identity is invalid'
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
const hostBinding =
|
|
503
|
+
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
504
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
505
|
+
: undefined;
|
|
506
|
+
if (hostBinding) {
|
|
507
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
508
|
+
preparedPlan: payload.preparedPlan,
|
|
509
|
+
executor: 'v4',
|
|
510
|
+
platform: payload.platform,
|
|
511
|
+
scopeTargets: [
|
|
512
|
+
'boot',
|
|
513
|
+
'app_v1',
|
|
514
|
+
'app_v2',
|
|
515
|
+
'coprocessor',
|
|
516
|
+
'resource',
|
|
517
|
+
'se01',
|
|
518
|
+
'se02',
|
|
519
|
+
'se03',
|
|
520
|
+
'se04',
|
|
521
|
+
],
|
|
522
|
+
bindings: [
|
|
523
|
+
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
524
|
+
artifact
|
|
525
|
+
? [
|
|
526
|
+
{
|
|
527
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
528
|
+
artifact,
|
|
529
|
+
},
|
|
530
|
+
]
|
|
531
|
+
: []
|
|
532
|
+
),
|
|
533
|
+
...(payload.resourceBundleArtifacts ?? []).map(
|
|
534
|
+
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
535
|
+
target: 'resource' as const,
|
|
536
|
+
logicalName: entry.name,
|
|
537
|
+
artifact: entry.artifact,
|
|
538
|
+
})
|
|
539
|
+
),
|
|
540
|
+
],
|
|
541
|
+
});
|
|
542
|
+
}
|
|
455
543
|
|
|
456
544
|
this.params = {
|
|
545
|
+
preparedPlan: payload.preparedPlan,
|
|
457
546
|
chunkSize: payload.chunkSize,
|
|
458
547
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
459
548
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -465,20 +554,28 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
465
554
|
se02Binary: payload.se02Binary,
|
|
466
555
|
se03Binary: payload.se03Binary,
|
|
467
556
|
se04Binary: payload.se04Binary,
|
|
468
|
-
|
|
557
|
+
resourceBundleFiles: payload.resourceBundleFiles,
|
|
469
558
|
firmwareType: payload.firmwareType,
|
|
470
559
|
targetsToUpdate: payload.targetsToUpdate,
|
|
560
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
471
561
|
platform: payload.platform,
|
|
562
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
563
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
564
|
+
componentArtifacts: payload.componentArtifacts,
|
|
565
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
472
566
|
};
|
|
473
567
|
}
|
|
474
568
|
|
|
475
|
-
private getProtocolV2FirmwareChunkSize() {
|
|
569
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
|
|
476
570
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
477
571
|
const env = DataManager.getSettings('env');
|
|
478
572
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
479
573
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
480
574
|
if (isBle) {
|
|
481
|
-
maxChunkSize =
|
|
575
|
+
maxChunkSize =
|
|
576
|
+
direction === 'read'
|
|
577
|
+
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
578
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
482
579
|
}
|
|
483
580
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
484
581
|
return maxChunkSize;
|
|
@@ -499,8 +596,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
499
596
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
500
597
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
501
598
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
502
|
-
|
|
503
|
-
|
|
599
|
+
this.validateExpectedTargetVersions();
|
|
600
|
+
|
|
601
|
+
if (
|
|
602
|
+
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
603
|
+
this.params.resourceBundleArtifacts?.length
|
|
604
|
+
) {
|
|
605
|
+
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
606
|
+
}
|
|
504
607
|
|
|
505
608
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
506
609
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -508,18 +611,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
508
611
|
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
509
612
|
try {
|
|
510
613
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
511
|
-
resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
|
|
512
614
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
513
615
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
616
|
+
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
617
|
+
if (
|
|
618
|
+
this.params.artifactReader ||
|
|
619
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
620
|
+
) {
|
|
621
|
+
throw ERRORS.TypedError(
|
|
622
|
+
HardwareErrorCode.RuntimeError,
|
|
623
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
624
|
+
{
|
|
625
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
}
|
|
523
629
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
524
630
|
firmwareType,
|
|
525
631
|
deviceFeatures
|
|
@@ -528,70 +634,308 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
528
634
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
529
635
|
installItems = remoteBinaries.installItems;
|
|
530
636
|
}
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
this.
|
|
537
|
-
|
|
637
|
+
const resourceDescriptors = this.prepareProtocolV2ResourceBundles(
|
|
638
|
+
firmwareType,
|
|
639
|
+
deviceFeatures
|
|
640
|
+
);
|
|
641
|
+
resourceBundles = resourceDescriptors?.length
|
|
642
|
+
? await this.materializeProtocolV2ResourceBundles(resourceDescriptors)
|
|
643
|
+
: undefined;
|
|
644
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
538
645
|
} catch (err) {
|
|
539
|
-
|
|
540
|
-
throw err;
|
|
541
|
-
}
|
|
542
|
-
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
646
|
+
throw normalizeFirmwarePreparationError(err);
|
|
543
647
|
}
|
|
544
648
|
|
|
545
|
-
if (
|
|
546
|
-
!bootloaderBinary &&
|
|
547
|
-
fwBinaryMap.length === 0 &&
|
|
548
|
-
!installItems?.length &&
|
|
549
|
-
!resourceBundles?.length &&
|
|
550
|
-
!needsRemoteResources
|
|
551
|
-
) {
|
|
649
|
+
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
552
650
|
throw ERRORS.TypedError(
|
|
553
651
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
554
652
|
'No firmware to update'
|
|
555
653
|
);
|
|
556
654
|
}
|
|
557
655
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (needsRemoteResources) {
|
|
561
|
-
try {
|
|
562
|
-
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
563
|
-
resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
|
|
564
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
565
|
-
} catch (err) {
|
|
566
|
-
if (enteredBootloader) {
|
|
567
|
-
try {
|
|
568
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
569
|
-
} catch (restoreError) {
|
|
570
|
-
Log.warn(
|
|
571
|
-
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
572
|
-
restoreError
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
await this.executeProtocolV2Update({
|
|
656
|
+
return this.executeProtocolV2Update({
|
|
581
657
|
fwBinaryMap,
|
|
582
658
|
bootloaderBinary,
|
|
583
659
|
...(installItems ? { installItems } : undefined),
|
|
584
660
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
585
661
|
});
|
|
662
|
+
}
|
|
586
663
|
|
|
587
|
-
|
|
588
|
-
await
|
|
664
|
+
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
665
|
+
const source = await openFirmwareByteSource({
|
|
666
|
+
artifact,
|
|
667
|
+
reader: this.params.artifactReader,
|
|
668
|
+
});
|
|
669
|
+
if (!source) {
|
|
670
|
+
throw ERRORS.TypedError(
|
|
671
|
+
HardwareErrorCode.RuntimeError,
|
|
672
|
+
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
673
|
+
{
|
|
674
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
this.protocolV2PreparedSources.push(source);
|
|
679
|
+
return source;
|
|
680
|
+
}
|
|
589
681
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
682
|
+
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
683
|
+
const source = await openFirmwareByteSource({ binary });
|
|
684
|
+
if (!source) {
|
|
685
|
+
throw ERRORS.TypedError(
|
|
686
|
+
HardwareErrorCode.RuntimeError,
|
|
687
|
+
'Protocol V2 firmware binary is empty'
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
this.protocolV2PreparedSources.push(source);
|
|
691
|
+
return source;
|
|
692
|
+
}
|
|
593
693
|
|
|
594
|
-
|
|
694
|
+
private async closeProtocolV2PreparedSources() {
|
|
695
|
+
const sources = this.protocolV2PreparedSources.splice(0);
|
|
696
|
+
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
private async prepareProtocolV2InstallSources(
|
|
700
|
+
firmwareType: EFirmwareType,
|
|
701
|
+
features: Features
|
|
702
|
+
): Promise<ProtocolV2InstallSource[]> {
|
|
703
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
704
|
+
if (!release) {
|
|
705
|
+
throw ERRORS.TypedError(
|
|
706
|
+
HardwareErrorCode.RuntimeError,
|
|
707
|
+
'Protocol V2 firmware release is unavailable'
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
711
|
+
const requestedTargets = new Set(
|
|
712
|
+
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
713
|
+
);
|
|
714
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
715
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
716
|
+
|
|
717
|
+
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
718
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
719
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
720
|
+
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
721
|
+
const artifact = componentArtifacts[updateTarget];
|
|
722
|
+
if (!artifact) {
|
|
723
|
+
throw ERRORS.TypedError(
|
|
724
|
+
HardwareErrorCode.RuntimeError,
|
|
725
|
+
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
726
|
+
{
|
|
727
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
728
|
+
artifactName: updateTarget,
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
installSources.push({
|
|
733
|
+
...target,
|
|
734
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
735
|
+
});
|
|
736
|
+
preparedTargets.add(updateTarget);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
741
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
742
|
+
);
|
|
743
|
+
if (unknownTarget) {
|
|
744
|
+
throw ERRORS.TypedError(
|
|
745
|
+
HardwareErrorCode.RuntimeError,
|
|
746
|
+
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return installSources;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private async prepareProtocolV2ResourceSources(
|
|
753
|
+
firmwareType: EFirmwareType,
|
|
754
|
+
features: Features
|
|
755
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
756
|
+
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
757
|
+
const resourceRequested =
|
|
758
|
+
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
759
|
+
if (!resourceRequested) {
|
|
760
|
+
return [];
|
|
761
|
+
}
|
|
762
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
763
|
+
const descriptors = release?.resourceBundles ?? [];
|
|
764
|
+
const artifactByName = new Map(
|
|
765
|
+
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
766
|
+
);
|
|
767
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
768
|
+
for (const descriptor of descriptors) {
|
|
769
|
+
const artifact = artifactByName.get(descriptor.name);
|
|
770
|
+
if (!artifact) {
|
|
771
|
+
throw ERRORS.TypedError(
|
|
772
|
+
HardwareErrorCode.RuntimeError,
|
|
773
|
+
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
774
|
+
{
|
|
775
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
776
|
+
artifactName: descriptor.name,
|
|
777
|
+
}
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
781
|
+
descriptor.devicePath,
|
|
782
|
+
'resourceBundles[].devicePath'
|
|
783
|
+
);
|
|
784
|
+
sources.push({
|
|
785
|
+
name: descriptor.name,
|
|
786
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
787
|
+
devicePath,
|
|
788
|
+
version: descriptor.version,
|
|
789
|
+
payloadHash: descriptor.payloadHash,
|
|
790
|
+
headerHash: descriptor.headerHash,
|
|
791
|
+
});
|
|
792
|
+
artifactByName.delete(descriptor.name);
|
|
793
|
+
}
|
|
794
|
+
if (artifactByName.size > 0) {
|
|
795
|
+
throw ERRORS.TypedError(
|
|
796
|
+
HardwareErrorCode.RuntimeError,
|
|
797
|
+
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return sources;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
804
|
+
try {
|
|
805
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
806
|
+
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
807
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
808
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
809
|
+
throw ERRORS.TypedError(
|
|
810
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
811
|
+
'No firmware to update'
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
815
|
+
|
|
816
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
817
|
+
installSources,
|
|
818
|
+
resourceSources,
|
|
819
|
+
});
|
|
820
|
+
} finally {
|
|
821
|
+
await this.closeProtocolV2PreparedSources();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
private validateExpectedTargetVersions() {
|
|
826
|
+
const expected = this.params.expectedTargetVersions;
|
|
827
|
+
if (expected === undefined) return;
|
|
828
|
+
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
829
|
+
throw ERRORS.TypedError(
|
|
830
|
+
HardwareErrorCode.RuntimeError,
|
|
831
|
+
'Protocol V2 expected target versions are invalid'
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
for (const [target, version] of Object.entries(expected)) {
|
|
835
|
+
if (
|
|
836
|
+
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
837
|
+
target as FirmwareUpdateV4Target
|
|
838
|
+
) ||
|
|
839
|
+
typeof version !== 'string' ||
|
|
840
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
841
|
+
version.length > 64
|
|
842
|
+
) {
|
|
843
|
+
throw ERRORS.TypedError(
|
|
844
|
+
HardwareErrorCode.RuntimeError,
|
|
845
|
+
'Protocol V2 expected target version is invalid'
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
852
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
853
|
+
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
854
|
+
if (!version) return undefined;
|
|
855
|
+
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
856
|
+
if (
|
|
857
|
+
parts.length !== 3 ||
|
|
858
|
+
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
859
|
+
) {
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
866
|
+
const expected = this.params?.expectedTargetVersions;
|
|
867
|
+
if (!expected) return;
|
|
868
|
+
const features = this.protocolV2LatestFinalFeatures;
|
|
869
|
+
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
870
|
+
const applicationTargets = requestedTargets.filter(
|
|
871
|
+
target => target === 'app_v1' || target === 'app_v2'
|
|
872
|
+
);
|
|
873
|
+
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
874
|
+
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
875
|
+
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
876
|
+
se01: features?.se01Version ?? undefined,
|
|
877
|
+
se02: features?.se02Version ?? undefined,
|
|
878
|
+
se03: features?.se03Version ?? undefined,
|
|
879
|
+
se04: features?.se04Version ?? undefined,
|
|
880
|
+
};
|
|
881
|
+
if (features && applicationTargets.length === 1) {
|
|
882
|
+
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
883
|
+
}
|
|
884
|
+
const expectedEntries = (
|
|
885
|
+
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
886
|
+
).filter(([target]) => !targets || targets.includes(target));
|
|
887
|
+
for (const [target, expectedVersion] of expectedEntries) {
|
|
888
|
+
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
889
|
+
([, mappedTarget]) => mappedTarget === target
|
|
890
|
+
)?.[0];
|
|
891
|
+
const statusVersion =
|
|
892
|
+
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
893
|
+
const observedVersion =
|
|
894
|
+
statusVersion === undefined
|
|
895
|
+
? visibleVersions[target]
|
|
896
|
+
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
897
|
+
Math.floor(statusVersion / 0x100) % 0x100
|
|
898
|
+
}.${statusVersion % 0x100}`;
|
|
899
|
+
if (!observedVersion) {
|
|
900
|
+
if (this.protocolV2FinalStatusVerified) {
|
|
901
|
+
Log.warn(
|
|
902
|
+
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
903
|
+
);
|
|
904
|
+
} else {
|
|
905
|
+
throw ERRORS.TypedError(
|
|
906
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
907
|
+
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
} else if (observedVersion !== expectedVersion) {
|
|
911
|
+
throw ERRORS.TypedError(
|
|
912
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
913
|
+
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
920
|
+
if (this.params.targetsToUpdate?.length) {
|
|
921
|
+
return [...new Set(this.params.targetsToUpdate)];
|
|
922
|
+
}
|
|
923
|
+
const targets = new Set<FirmwareUpdateV4Target>();
|
|
924
|
+
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
925
|
+
targets.add(target as FirmwareUpdateV4Target)
|
|
926
|
+
);
|
|
927
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
928
|
+
targets.add('resource');
|
|
929
|
+
}
|
|
930
|
+
if (this.params.bootloaderBinary) targets.add('boot');
|
|
931
|
+
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
932
|
+
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
933
|
+
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
934
|
+
if (this.params.se01Binary) targets.add('se01');
|
|
935
|
+
if (this.params.se02Binary) targets.add('se02');
|
|
936
|
+
if (this.params.se03Binary) targets.add('se03');
|
|
937
|
+
if (this.params.se04Binary) targets.add('se04');
|
|
938
|
+
return Array.from(targets);
|
|
595
939
|
}
|
|
596
940
|
|
|
597
941
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -613,32 +957,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
613
957
|
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
614
958
|
assertProtocolV2ReconnectIdentity(
|
|
615
959
|
this.protocolV2ExpectedSerialNumber,
|
|
616
|
-
this.getProtocolV2SerialNumber(deviceInfo)
|
|
617
|
-
this.protocolV2ExpectedPath,
|
|
618
|
-
this.device.originalDescriptor.path
|
|
960
|
+
this.getProtocolV2SerialNumber(deviceInfo)
|
|
619
961
|
);
|
|
620
962
|
}
|
|
621
963
|
|
|
622
|
-
private getProtocolV2DeviceInfoTimeout() {
|
|
623
|
-
return this.isBleReconnect()
|
|
624
|
-
? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
|
|
625
|
-
: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
964
|
private async requestProtocolV2PhysicalIdentity() {
|
|
629
965
|
return requestProtocolV2DeviceInfo({
|
|
630
966
|
commands: this.device.getCommands(),
|
|
631
|
-
timeoutMs:
|
|
967
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
632
968
|
});
|
|
633
969
|
}
|
|
634
970
|
|
|
635
971
|
private async captureProtocolV2PhysicalIdentity() {
|
|
636
972
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
637
973
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
638
|
-
|
|
639
|
-
|
|
974
|
+
if (this.params?.preparedPlan) {
|
|
975
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
976
|
+
preparedPlan: this.params.preparedPlan,
|
|
977
|
+
deviceIdentity: serialNumber,
|
|
978
|
+
});
|
|
979
|
+
} else if (this.params?.expectedDeviceId) {
|
|
980
|
+
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
981
|
+
} else {
|
|
982
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
983
|
+
}
|
|
640
984
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
641
|
-
this.protocolV2ExpectedPath = path;
|
|
642
985
|
}
|
|
643
986
|
|
|
644
987
|
private async verifyProtocolV2ReconnectIdentity() {
|
|
@@ -653,7 +996,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
653
996
|
|
|
654
997
|
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
655
998
|
return (
|
|
656
|
-
!!this.params.
|
|
999
|
+
!!this.params.resourceBundleFiles?.length ||
|
|
657
1000
|
!!this.params.bootloaderBinary ||
|
|
658
1001
|
fwBinaryMap.length > 0
|
|
659
1002
|
);
|
|
@@ -716,6 +1059,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
716
1059
|
return target;
|
|
717
1060
|
}
|
|
718
1061
|
|
|
1062
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
1063
|
+
if (path.startsWith('vol')) return path;
|
|
1064
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1065
|
+
return `vol0:/${path}`;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1069
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1070
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1071
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1072
|
+
path: filePath,
|
|
1073
|
+
});
|
|
1074
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1075
|
+
if (
|
|
1076
|
+
!pathInfoRes.message?.exist ||
|
|
1077
|
+
pathInfoRes.message?.directory ||
|
|
1078
|
+
fileSize === undefined ||
|
|
1079
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1080
|
+
) {
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1085
|
+
const chunks: Uint8Array[] = [];
|
|
1086
|
+
let offset = 0;
|
|
1087
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1088
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1089
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1090
|
+
file: {
|
|
1091
|
+
path: filePath,
|
|
1092
|
+
offset,
|
|
1093
|
+
total_size: 0,
|
|
1094
|
+
},
|
|
1095
|
+
chunk_len: readLen,
|
|
1096
|
+
ui_percentage: undefined,
|
|
1097
|
+
});
|
|
1098
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
1099
|
+
if (data.byteLength === 0) return null;
|
|
1100
|
+
chunks.push(data);
|
|
1101
|
+
offset += data.byteLength;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const headerBytes = new Uint8Array(offset);
|
|
1105
|
+
let cursor = 0;
|
|
1106
|
+
chunks.forEach(chunk => {
|
|
1107
|
+
headerBytes.set(chunk, cursor);
|
|
1108
|
+
cursor += chunk.byteLength;
|
|
1109
|
+
});
|
|
1110
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
719
1113
|
private async downloadRemoteProtocolV2Component(
|
|
720
1114
|
key: string,
|
|
721
1115
|
component: IProtocolV2FirmwareComponent
|
|
@@ -804,159 +1198,155 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
804
1198
|
};
|
|
805
1199
|
}
|
|
806
1200
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
private async prepareProtocolV2BootResources(): Promise<
|
|
814
|
-
ProtocolV2ResourceBundleBinary[] | undefined
|
|
815
|
-
> {
|
|
816
|
-
if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
|
|
817
|
-
const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
|
|
818
|
-
if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
|
|
1201
|
+
// ============================================================
|
|
1202
|
+
// Incremental RESC bundle sync through FileRead and FilesystemFileWrite
|
|
1203
|
+
// ============================================================
|
|
819
1204
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1205
|
+
/**
|
|
1206
|
+
* Prepare the RESC bundle list.
|
|
1207
|
+
*
|
|
1208
|
+
* Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
|
|
1209
|
+
* - resourceBundleFiles supplied: use binaries directly without version checks.
|
|
1210
|
+
* - Otherwise load release.resourceBundles from remote config.json; the binaries
|
|
1211
|
+
* are prefetched before reboot and synchronization skips matching device headers.
|
|
1212
|
+
*/
|
|
1213
|
+
private prepareProtocolV2ResourceBundles(
|
|
1214
|
+
firmwareType: EFirmwareType,
|
|
1215
|
+
features: Features
|
|
1216
|
+
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
1217
|
+
// Manual binaries are installed directly without comparison.
|
|
1218
|
+
if (this.params.resourceBundleFiles?.length) {
|
|
1219
|
+
return this.params.resourceBundleFiles.map((file, index) => {
|
|
1220
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
1221
|
+
file.devicePath,
|
|
1222
|
+
`resourceBundleFiles[${index}].devicePath`
|
|
1223
|
+
);
|
|
1224
|
+
return {
|
|
1225
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
1226
|
+
binary: file.binary,
|
|
1227
|
+
devicePath,
|
|
1228
|
+
};
|
|
831
1229
|
});
|
|
832
1230
|
}
|
|
833
|
-
return files;
|
|
834
|
-
}
|
|
835
1231
|
|
|
836
|
-
private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
837
|
-
const files = this.params.resourceFiles ?? [];
|
|
838
|
-
if (!files?.length) return undefined;
|
|
839
|
-
|
|
840
|
-
const prepared = files.map((file, index) => {
|
|
841
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
842
|
-
file.devicePath,
|
|
843
|
-
`resourceFiles[${index}].devicePath`
|
|
844
|
-
);
|
|
845
|
-
const descriptor = file as typeof file &
|
|
846
|
-
Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
|
|
847
|
-
if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
|
|
848
|
-
throw new Error(`resourceFiles[${index}] size mismatch`);
|
|
849
|
-
}
|
|
850
|
-
if (
|
|
851
|
-
descriptor.fileHash &&
|
|
852
|
-
!isProtocolV2ResourceFileValid(file.binary, {
|
|
853
|
-
size: file.binary.byteLength,
|
|
854
|
-
fileHash: descriptor.fileHash,
|
|
855
|
-
})
|
|
856
|
-
) {
|
|
857
|
-
throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
|
|
858
|
-
}
|
|
859
|
-
return {
|
|
860
|
-
name: devicePath.split('/').pop() ?? devicePath,
|
|
861
|
-
binary: file.binary,
|
|
862
|
-
devicePath,
|
|
863
|
-
};
|
|
864
|
-
});
|
|
865
|
-
if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
|
|
866
|
-
throw new Error('resourceFiles contain duplicate devicePath values');
|
|
867
|
-
}
|
|
868
|
-
return prepared;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
872
|
-
ProtocolV2ResourceBundleBinary[] | undefined
|
|
873
|
-
> {
|
|
874
1232
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
875
1233
|
return undefined;
|
|
876
1234
|
}
|
|
877
1235
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
)
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1236
|
+
// Remote-config bundles are downloaded and compared later.
|
|
1237
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
1238
|
+
if (!release?.resourceBundles?.length) return undefined;
|
|
1239
|
+
|
|
1240
|
+
return release.resourceBundles.map((bundle, index) => ({
|
|
1241
|
+
name: bundle.name,
|
|
1242
|
+
binary: new ArrayBuffer(0),
|
|
1243
|
+
devicePath: validateProtocolV2FilesystemPath(
|
|
1244
|
+
bundle.devicePath,
|
|
1245
|
+
`resourceBundles[${index}].devicePath`
|
|
1246
|
+
),
|
|
1247
|
+
url: bundle.url,
|
|
1248
|
+
version: bundle.version,
|
|
1249
|
+
payloadHash: bundle.payloadHash,
|
|
1250
|
+
headerHash: bundle.headerHash,
|
|
1251
|
+
})) as ProtocolV2ResourceBundleBinary[];
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
private async materializeProtocolV2ResourceBundles(
|
|
1255
|
+
bundles: ProtocolV2ResourceBundleBinary[]
|
|
1256
|
+
): Promise<ProtocolV2ResourceBundleBinary[]> {
|
|
1257
|
+
for (const bundle of bundles) {
|
|
1258
|
+
let { binary } = bundle;
|
|
1259
|
+
let downloadedFromRemote = false;
|
|
1260
|
+
if (binary.byteLength === 0) {
|
|
1261
|
+
if (!bundle.url) {
|
|
1262
|
+
throw ERRORS.TypedError(
|
|
1263
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1264
|
+
`Missing Protocol V2 RESC bundle binary: ${bundle.name}`
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
if (
|
|
1268
|
+
this.params.artifactReader ||
|
|
1269
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
1270
|
+
) {
|
|
1271
|
+
throw ERRORS.TypedError(
|
|
1272
|
+
HardwareErrorCode.RuntimeError,
|
|
1273
|
+
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1274
|
+
{
|
|
1275
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1276
|
+
artifactName: bundle.name,
|
|
1277
|
+
}
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
1281
|
+
({ binary } = await getSysResourceBinary(bundle.url));
|
|
1282
|
+
bundle.binary = binary;
|
|
1283
|
+
downloadedFromRemote = true;
|
|
1284
|
+
}
|
|
1285
|
+
if (bundle.binary.byteLength === 0) {
|
|
1286
|
+
throw ERRORS.TypedError(
|
|
1287
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1288
|
+
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
if (downloadedFromRemote) {
|
|
1292
|
+
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1293
|
+
if (!header || header.type !== 'RESC') {
|
|
1294
|
+
throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
|
|
1295
|
+
}
|
|
1296
|
+
if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
|
|
1297
|
+
throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
|
|
1298
|
+
}
|
|
1299
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1300
|
+
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
|
|
1301
|
+
throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
|
|
1302
|
+
}
|
|
1303
|
+
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1304
|
+
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
|
|
1305
|
+
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
904
1308
|
}
|
|
905
1309
|
return bundles;
|
|
906
1310
|
}
|
|
907
1311
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
private async syncProtocolV2ResourceBundles(
|
|
924
|
-
bundles: ProtocolV2ResourceBundleBinary[],
|
|
925
|
-
firmwareSize: number
|
|
926
|
-
): Promise<{ processedSize: number; totalSize: number }> {
|
|
927
|
-
const transferStartTime = Date.now();
|
|
928
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1312
|
+
/**
|
|
1313
|
+
* Compare the existing okpkg OKPP header in remote-config mode.
|
|
1314
|
+
*/
|
|
1315
|
+
private async isProtocolV2ResourceBundleUpToDate(
|
|
1316
|
+
bundle: Pick<
|
|
1317
|
+
ProtocolV2ResourceBundleBinary,
|
|
1318
|
+
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1319
|
+
>
|
|
1320
|
+
): Promise<boolean> {
|
|
1321
|
+
if (this.params?.forcedUpdateRes) return false;
|
|
1322
|
+
if (!bundle.version && !bundle.payloadHash) return false;
|
|
929
1323
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1324
|
+
try {
|
|
1325
|
+
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1326
|
+
if (!header) return false;
|
|
933
1327
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
1328
|
+
if (bundle.version) {
|
|
1329
|
+
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
1330
|
+
if (cmp === undefined || cmp !== 0) return false;
|
|
1331
|
+
}
|
|
1332
|
+
if (bundle.payloadHash) {
|
|
1333
|
+
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1334
|
+
if (expected && header.payloadHash !== expected) return false;
|
|
1335
|
+
}
|
|
1336
|
+
if (bundle.headerHash) {
|
|
1337
|
+
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1338
|
+
if (expected && header.headerHash !== expected) return false;
|
|
1339
|
+
}
|
|
1340
|
+
return true;
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1343
|
+
return false;
|
|
946
1344
|
}
|
|
947
|
-
|
|
948
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
949
|
-
Log.log(
|
|
950
|
-
`[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
951
|
-
elapsedMs / 1000
|
|
952
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
953
|
-
);
|
|
954
|
-
return { processedSize, totalSize: transferTotalSize };
|
|
955
1345
|
}
|
|
956
1346
|
|
|
957
1347
|
private isProtocolV2BootloaderMode() {
|
|
958
1348
|
if (typeof this.device.isBootloader === 'function') {
|
|
959
|
-
return
|
|
1349
|
+
return this.device.isBootloader();
|
|
960
1350
|
}
|
|
961
1351
|
return (
|
|
962
1352
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -965,11 +1355,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
965
1355
|
}
|
|
966
1356
|
|
|
967
1357
|
private isProtocolV2RomloaderMode() {
|
|
968
|
-
|
|
969
|
-
if (
|
|
970
|
-
!this.device.isProtocolV2() ||
|
|
971
|
-
(deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
|
|
972
|
-
) {
|
|
1358
|
+
if (!this.device.isProtocolV2() || this.device.getCurrentDeviceType() !== EDeviceType.Pro2) {
|
|
973
1359
|
return false;
|
|
974
1360
|
}
|
|
975
1361
|
if (typeof this.device.isRomloader === 'function') {
|
|
@@ -983,20 +1369,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
983
1369
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
984
1370
|
if (this.isProtocolV2RomloaderMode()) {
|
|
985
1371
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1372
|
+
this.protocolV2ExecutionInLoader = true;
|
|
986
1373
|
return false;
|
|
987
1374
|
}
|
|
988
1375
|
if (this.isProtocolV2BootloaderMode()) {
|
|
989
1376
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
990
|
-
this.
|
|
1377
|
+
this.protocolV2ExecutionInLoader = true;
|
|
991
1378
|
return false;
|
|
992
1379
|
}
|
|
993
1380
|
|
|
994
1381
|
try {
|
|
995
1382
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
996
1383
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1384
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
997
1385
|
await wait(1000);
|
|
998
1386
|
await this.waitForProtocolV2BootloaderMode();
|
|
999
|
-
this.
|
|
1387
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1000
1388
|
return true;
|
|
1001
1389
|
} catch (error) {
|
|
1002
1390
|
if (error instanceof HardwareError) {
|
|
@@ -1013,17 +1401,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1013
1401
|
) {
|
|
1014
1402
|
const startTime = Date.now();
|
|
1015
1403
|
let lastError: unknown;
|
|
1016
|
-
let shouldReconnect = true;
|
|
1017
1404
|
|
|
1018
1405
|
while (Date.now() - startTime < timeout) {
|
|
1019
1406
|
try {
|
|
1020
|
-
|
|
1021
|
-
await this.reconnectProtocolV2Device();
|
|
1022
|
-
shouldReconnect = false;
|
|
1023
|
-
}
|
|
1407
|
+
await this.reconnectProtocolV2Device();
|
|
1024
1408
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1025
1409
|
commands: this.device.getCommands(),
|
|
1026
|
-
timeoutMs:
|
|
1410
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1027
1411
|
});
|
|
1028
1412
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1029
1413
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1038,7 +1422,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1038
1422
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1039
1423
|
throw error;
|
|
1040
1424
|
}
|
|
1041
|
-
shouldReconnect = true;
|
|
1042
1425
|
lastError = error;
|
|
1043
1426
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
1044
1427
|
}
|
|
@@ -1091,6 +1474,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1091
1474
|
return entries;
|
|
1092
1475
|
}
|
|
1093
1476
|
|
|
1477
|
+
private buildProtocolV2ExecutionPhases({
|
|
1478
|
+
installSources,
|
|
1479
|
+
resourceSources,
|
|
1480
|
+
}: {
|
|
1481
|
+
installSources: ProtocolV2InstallSource[];
|
|
1482
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1483
|
+
}): ProtocolV2ExecutionPhase[] {
|
|
1484
|
+
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1485
|
+
if (resourceSources.length > 0) {
|
|
1486
|
+
phases.push({
|
|
1487
|
+
kind: 'resource-sync',
|
|
1488
|
+
installSources: [],
|
|
1489
|
+
resourceSources,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1493
|
+
if (bootloaderSources.length > 0) {
|
|
1494
|
+
phases.push(
|
|
1495
|
+
{
|
|
1496
|
+
kind: 'bootloader-install',
|
|
1497
|
+
installSources: bootloaderSources,
|
|
1498
|
+
resourceSources: [],
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
kind: 'bootloader-verify',
|
|
1502
|
+
installSources: [],
|
|
1503
|
+
resourceSources: [],
|
|
1504
|
+
}
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1508
|
+
if (componentSources.length > 0) {
|
|
1509
|
+
phases.push({
|
|
1510
|
+
kind: 'component-install',
|
|
1511
|
+
installSources: componentSources,
|
|
1512
|
+
resourceSources: [],
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
phases.push({
|
|
1516
|
+
kind: 'final-verify',
|
|
1517
|
+
installSources: [],
|
|
1518
|
+
resourceSources: [],
|
|
1519
|
+
});
|
|
1520
|
+
return phases;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
private async executeProtocolV2Phases({
|
|
1524
|
+
installSources,
|
|
1525
|
+
resourceSources,
|
|
1526
|
+
}: {
|
|
1527
|
+
installSources: ProtocolV2InstallSource[];
|
|
1528
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1529
|
+
}) {
|
|
1530
|
+
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1531
|
+
installSources,
|
|
1532
|
+
resourceSources,
|
|
1533
|
+
});
|
|
1534
|
+
for (const phase of phases) {
|
|
1535
|
+
if (phase.kind === 'final-verify') {
|
|
1536
|
+
return this.completeProtocolV2FinalVerification();
|
|
1537
|
+
}
|
|
1538
|
+
if (phase.kind === 'resource-sync') {
|
|
1539
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1540
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1541
|
+
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1542
|
+
await this.enterProtocolV2BootloaderMode();
|
|
1543
|
+
await this.executeProtocolV2TransferPhase(phase);
|
|
1544
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1545
|
+
} else if (phase.kind === 'bootloader-verify') {
|
|
1546
|
+
await this.waitForProtocolV2FinalFeatures();
|
|
1547
|
+
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
throw ERRORS.TypedError(
|
|
1551
|
+
HardwareErrorCode.RuntimeError,
|
|
1552
|
+
'Protocol V2 execution has no final verification phase'
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
private async executeProtocolV2SourceUpdate({
|
|
1557
|
+
installSources,
|
|
1558
|
+
resourceSources,
|
|
1559
|
+
}: {
|
|
1560
|
+
installSources: ProtocolV2InstallSource[];
|
|
1561
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1562
|
+
}) {
|
|
1563
|
+
return this.executeProtocolV2Phases({
|
|
1564
|
+
installSources,
|
|
1565
|
+
resourceSources,
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1094
1569
|
private async executeProtocolV2Update({
|
|
1095
1570
|
fwBinaryMap,
|
|
1096
1571
|
bootloaderBinary,
|
|
@@ -1102,104 +1577,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1102
1577
|
installItems?: ProtocolV2InstallItem[];
|
|
1103
1578
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1104
1579
|
}) {
|
|
1105
|
-
const
|
|
1580
|
+
const memoryInstallItems =
|
|
1106
1581
|
installItems ??
|
|
1107
1582
|
this.buildProtocolV2InstallItems({
|
|
1108
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1109
1583
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1584
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1110
1585
|
});
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
if (resourceBundles?.length) {
|
|
1120
|
-
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1121
|
-
resourceBundles,
|
|
1122
|
-
firmwareSize
|
|
1586
|
+
try {
|
|
1587
|
+
const installSources = await Promise.all(
|
|
1588
|
+
memoryInstallItems.map(async item => ({
|
|
1589
|
+
fileName: item.fileName,
|
|
1590
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1591
|
+
targetId: item.targetId,
|
|
1592
|
+
kind: item.kind,
|
|
1593
|
+
}))
|
|
1123
1594
|
);
|
|
1124
|
-
|
|
1125
|
-
|
|
1595
|
+
const resourceSources = await Promise.all(
|
|
1596
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1597
|
+
name: bundle.name,
|
|
1598
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1599
|
+
devicePath: bundle.devicePath,
|
|
1600
|
+
version: bundle.version,
|
|
1601
|
+
payloadHash: bundle.payloadHash,
|
|
1602
|
+
headerHash: bundle.headerHash,
|
|
1603
|
+
}))
|
|
1604
|
+
);
|
|
1605
|
+
return await this.executeProtocolV2Phases({
|
|
1606
|
+
installSources,
|
|
1607
|
+
resourceSources,
|
|
1608
|
+
});
|
|
1609
|
+
} finally {
|
|
1610
|
+
await this.closeProtocolV2PreparedSources();
|
|
1126
1611
|
}
|
|
1612
|
+
}
|
|
1127
1613
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1614
|
+
private async executeProtocolV2TransferPhase({
|
|
1615
|
+
installSources,
|
|
1616
|
+
resourceSources,
|
|
1617
|
+
}: ProtocolV2ExecutionPhase) {
|
|
1618
|
+
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1619
|
+
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1620
|
+
for (const resource of resourceSources) {
|
|
1621
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1622
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1623
|
+
} else {
|
|
1624
|
+
resourcesToSync.push(resource);
|
|
1625
|
+
totalSize += resource.source.size;
|
|
1133
1626
|
}
|
|
1134
|
-
return;
|
|
1135
1627
|
}
|
|
1136
|
-
let transferredSize = processedSize;
|
|
1137
|
-
const transferStartTime = Date.now();
|
|
1138
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1139
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1140
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1141
|
-
transferredSize = bytes;
|
|
1142
|
-
};
|
|
1143
|
-
Log.log(
|
|
1144
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1145
|
-
);
|
|
1146
|
-
|
|
1147
|
-
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1148
|
-
|
|
1149
|
-
try {
|
|
1150
|
-
for (const item of orderedInstallItems) {
|
|
1151
|
-
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1152
|
-
Log.log(
|
|
1153
|
-
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1154
|
-
);
|
|
1155
|
-
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1156
|
-
payload: item.binary,
|
|
1157
|
-
filePath,
|
|
1158
|
-
processedSize,
|
|
1159
|
-
totalSize,
|
|
1160
|
-
onTransferredBytes,
|
|
1161
|
-
});
|
|
1162
|
-
transferredSize = processedSize;
|
|
1163
|
-
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1164
1628
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1629
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1630
|
+
let processedSize = 0;
|
|
1631
|
+
for (const resource of resourcesToSync) {
|
|
1632
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1633
|
+
source: resource.source,
|
|
1634
|
+
filePath: resource.devicePath,
|
|
1635
|
+
processedSize,
|
|
1636
|
+
totalSize,
|
|
1637
|
+
});
|
|
1638
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
1639
|
+
}
|
|
1170
1640
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1641
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
1642
|
+
for (const item of installSources) {
|
|
1643
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1644
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
1645
|
+
source: item.source,
|
|
1646
|
+
filePath,
|
|
1647
|
+
processedSize,
|
|
1648
|
+
totalSize,
|
|
1649
|
+
});
|
|
1650
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1651
|
+
stagedInstallTargets.push({
|
|
1652
|
+
targetId: item.targetId,
|
|
1653
|
+
path: filePath,
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1174
1656
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
);
|
|
1181
|
-
} catch (error) {
|
|
1182
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1183
|
-
Log.warn(
|
|
1184
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1185
|
-
elapsedMs / 1000
|
|
1186
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1187
|
-
);
|
|
1188
|
-
throw error;
|
|
1657
|
+
if (totalSize > 0) {
|
|
1658
|
+
this.postProgressMessage(100, 'transferData');
|
|
1659
|
+
}
|
|
1660
|
+
if (stagedInstallTargets.length === 0) {
|
|
1661
|
+
return;
|
|
1189
1662
|
}
|
|
1190
1663
|
|
|
1191
1664
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1192
|
-
|
|
1193
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1665
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1194
1666
|
target_id: item.targetId,
|
|
1195
1667
|
path: item.path,
|
|
1196
1668
|
}));
|
|
1197
|
-
|
|
1198
|
-
await this.
|
|
1199
|
-
|
|
1669
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1670
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
private async protocolV2SourceUpdateProcess({
|
|
1674
|
+
source,
|
|
1675
|
+
filePath,
|
|
1676
|
+
processedSize,
|
|
1677
|
+
totalSize,
|
|
1678
|
+
}: {
|
|
1679
|
+
source: FirmwareByteSource;
|
|
1680
|
+
filePath: string;
|
|
1681
|
+
processedSize: number;
|
|
1682
|
+
totalSize: number;
|
|
1683
|
+
}) {
|
|
1684
|
+
let lastError: unknown;
|
|
1685
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1686
|
+
try {
|
|
1687
|
+
await writeFirmwareByteSource({
|
|
1688
|
+
source,
|
|
1689
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1690
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
1691
|
+
const chunkEnd = sourceOffset + length;
|
|
1692
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1693
|
+
processedSize + sourceOffset,
|
|
1694
|
+
processedSize + chunkEnd,
|
|
1695
|
+
totalSize
|
|
1696
|
+
);
|
|
1697
|
+
const response = await this.fileWriteChunk(
|
|
1698
|
+
filePath,
|
|
1699
|
+
source.size,
|
|
1700
|
+
sourceOffset,
|
|
1701
|
+
data,
|
|
1702
|
+
first,
|
|
1703
|
+
progress
|
|
1704
|
+
);
|
|
1705
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
1706
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1707
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1708
|
+
throw ERRORS.TypedError(
|
|
1709
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1710
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
this.postProgressMessage(progress, 'transferData');
|
|
1714
|
+
return length;
|
|
1715
|
+
},
|
|
1716
|
+
});
|
|
1717
|
+
return processedSize + source.size;
|
|
1718
|
+
} catch (error) {
|
|
1719
|
+
lastError = error;
|
|
1720
|
+
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1721
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
throw ERRORS.TypedError(
|
|
1726
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1727
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1728
|
+
);
|
|
1200
1729
|
}
|
|
1201
1730
|
|
|
1202
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1731
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1203
1732
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1204
1733
|
}
|
|
1205
1734
|
|
|
@@ -1218,7 +1747,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1218
1747
|
|
|
1219
1748
|
private assertProtocolV2TargetStatus(
|
|
1220
1749
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1221
|
-
expectedTargetIds: Set<number
|
|
1750
|
+
expectedTargetIds: Set<number>,
|
|
1751
|
+
expectedPaths = new Map<number, string>()
|
|
1222
1752
|
) {
|
|
1223
1753
|
Log.log(
|
|
1224
1754
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1249,17 +1779,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1249
1779
|
const matchingTargets = statusTargets.filter(target =>
|
|
1250
1780
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1251
1781
|
);
|
|
1782
|
+
const seenTargetIds = new Set<number>();
|
|
1783
|
+
for (const target of matchingTargets) {
|
|
1784
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1785
|
+
if (
|
|
1786
|
+
targetId === undefined ||
|
|
1787
|
+
seenTargetIds.has(targetId) ||
|
|
1788
|
+
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1789
|
+
) {
|
|
1790
|
+
throw ERRORS.TypedError(
|
|
1791
|
+
HardwareErrorCode.RuntimeError,
|
|
1792
|
+
`Protocol V2 install status conflicts with target ${target.target_id}`,
|
|
1793
|
+
{
|
|
1794
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
1795
|
+
}
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
seenTargetIds.add(targetId);
|
|
1799
|
+
}
|
|
1800
|
+
const completedTargets = matchingTargets.filter(target =>
|
|
1801
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
1802
|
+
);
|
|
1252
1803
|
const completedTargetIds = new Set<number>();
|
|
1253
|
-
|
|
1804
|
+
completedTargets.forEach(target => {
|
|
1254
1805
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1255
|
-
if (targetId !== undefined
|
|
1806
|
+
if (targetId !== undefined) {
|
|
1256
1807
|
completedTargetIds.add(targetId);
|
|
1257
1808
|
}
|
|
1258
1809
|
});
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1810
|
+
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
1811
|
+
for (const target of completedTargets) {
|
|
1812
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1813
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
1814
|
+
if (targetId === undefined) {
|
|
1815
|
+
throw ERRORS.TypedError(
|
|
1816
|
+
HardwareErrorCode.RuntimeError,
|
|
1817
|
+
'Protocol V2 completed target identity is invalid'
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1821
|
+
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1822
|
+
if (payloadVersion !== expectedVersion) {
|
|
1823
|
+
throw ERRORS.TypedError(
|
|
1824
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1825
|
+
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
if (payloadVersion !== undefined) {
|
|
1830
|
+
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1263
1833
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1264
1834
|
return true;
|
|
1265
1835
|
}
|
|
@@ -1280,43 +1850,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1280
1850
|
return false;
|
|
1281
1851
|
}
|
|
1282
1852
|
|
|
1283
|
-
private getProtocolV2MissingTargetIds(
|
|
1284
|
-
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1285
|
-
expectedTargetIds: Set<number>
|
|
1286
|
-
) {
|
|
1287
|
-
const reportedTargetIds = new Set<number>();
|
|
1288
|
-
statusTargets.forEach(target => {
|
|
1289
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1290
|
-
if (targetId !== undefined) {
|
|
1291
|
-
reportedTargetIds.add(targetId);
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
1853
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1298
1854
|
targets: Array<{ target_id: number; path: string }>
|
|
1299
1855
|
) {
|
|
1856
|
+
this.protocolV2FinalStatusVerified = false;
|
|
1300
1857
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1858
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1301
1859
|
const startTime = Date.now();
|
|
1302
1860
|
let lastError: unknown;
|
|
1303
|
-
let shouldReconnect = true;
|
|
1304
|
-
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
1305
|
-
let missingTargetStatusSince: number | undefined;
|
|
1306
|
-
let missingTargetStatusKey: string | undefined;
|
|
1307
|
-
const resetMissingTargetStatusGrace = () => {
|
|
1308
|
-
missingTargetStatusSince = undefined;
|
|
1309
|
-
missingTargetStatusKey = undefined;
|
|
1310
|
-
};
|
|
1311
1861
|
|
|
1312
1862
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1313
1863
|
try {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1317
|
-
shouldReconnect = false;
|
|
1318
|
-
}
|
|
1319
|
-
const currentDeviceInfo = deviceInfo;
|
|
1864
|
+
await this.reconnectProtocolV2Device();
|
|
1865
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1320
1866
|
try {
|
|
1321
1867
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1322
1868
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1328,89 +1874,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1328
1874
|
path: true,
|
|
1329
1875
|
},
|
|
1330
1876
|
},
|
|
1331
|
-
{ timeoutMs:
|
|
1877
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
1332
1878
|
);
|
|
1333
|
-
const statusTargets = (statusResponse.message.records ??
|
|
1334
|
-
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1335
|
-
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
1879
|
if (
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1880
|
+
this.assertProtocolV2TargetStatus(
|
|
1881
|
+
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1882
|
+
expectedTargetIds,
|
|
1883
|
+
expectedPaths
|
|
1884
|
+
)
|
|
1343
1885
|
) {
|
|
1344
|
-
|
|
1345
|
-
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
1346
|
-
);
|
|
1347
|
-
this.postProgressMessage(100, 'installingFirmware');
|
|
1886
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1348
1887
|
return;
|
|
1349
1888
|
}
|
|
1350
|
-
|
|
1351
|
-
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
1352
|
-
statusTargets,
|
|
1353
|
-
expectedTargetIds
|
|
1354
|
-
);
|
|
1355
|
-
if (missingTargetIds.length > 0) {
|
|
1356
|
-
const now = Date.now();
|
|
1357
|
-
const missingKey = missingTargetIds.join(',');
|
|
1358
|
-
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
1359
|
-
missingTargetStatusSince = now;
|
|
1360
|
-
missingTargetStatusKey = missingKey;
|
|
1361
|
-
} else if (
|
|
1362
|
-
now - missingTargetStatusSince >=
|
|
1363
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
1364
|
-
) {
|
|
1365
|
-
const reportedTargetIds = statusTargets
|
|
1366
|
-
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
1367
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
1368
|
-
throw ERRORS.TypedError(
|
|
1369
|
-
HardwareErrorCode.FirmwareError,
|
|
1370
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
1371
|
-
','
|
|
1372
|
-
)}`
|
|
1373
|
-
);
|
|
1374
|
-
}
|
|
1375
|
-
lastError = new Error(
|
|
1376
|
-
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
1377
|
-
);
|
|
1378
|
-
} else {
|
|
1379
|
-
resetMissingTargetStatusGrace();
|
|
1380
|
-
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1381
|
-
}
|
|
1889
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1382
1890
|
} catch (error) {
|
|
1383
|
-
if (
|
|
1384
|
-
error instanceof HardwareError &&
|
|
1385
|
-
error.errorCode === HardwareErrorCode.FirmwareError
|
|
1386
|
-
) {
|
|
1891
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
1387
1892
|
throw error;
|
|
1388
1893
|
}
|
|
1389
|
-
// Only consecutive full status dumps can prove that a record is absent.
|
|
1390
|
-
// A reboot or transport interruption starts a fresh grace window.
|
|
1391
|
-
resetMissingTargetStatusGrace();
|
|
1392
1894
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
1393
1895
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
1394
1896
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1395
|
-
if (
|
|
1396
|
-
throw ERRORS.TypedError(
|
|
1397
|
-
HardwareErrorCode.RuntimeError,
|
|
1398
|
-
'Protocol V2 device identity is unavailable during install polling'
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1401
|
-
if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
|
|
1897
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
1402
1898
|
Log.log(
|
|
1403
1899
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
1404
1900
|
);
|
|
1405
|
-
this.postProgressMessage(100, 'installingFirmware');
|
|
1406
1901
|
return;
|
|
1407
1902
|
}
|
|
1408
1903
|
lastError = new Error(
|
|
1409
1904
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
1410
1905
|
);
|
|
1411
1906
|
} else {
|
|
1412
|
-
shouldReconnect = true;
|
|
1413
|
-
deviceInfo = undefined;
|
|
1414
1907
|
lastError = error;
|
|
1415
1908
|
Log.log(
|
|
1416
1909
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -1423,12 +1916,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1423
1916
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1424
1917
|
throw error;
|
|
1425
1918
|
}
|
|
1426
|
-
if (error
|
|
1919
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
1920
|
+
throw error;
|
|
1921
|
+
}
|
|
1922
|
+
if (
|
|
1923
|
+
error instanceof HardwareError &&
|
|
1924
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1925
|
+
) {
|
|
1427
1926
|
throw error;
|
|
1428
1927
|
}
|
|
1429
|
-
shouldReconnect = true;
|
|
1430
|
-
deviceInfo = undefined;
|
|
1431
|
-
resetMissingTargetStatusGrace();
|
|
1432
1928
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1433
1929
|
}
|
|
1434
1930
|
await wait(1000);
|
|
@@ -1436,24 +1932,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1436
1932
|
|
|
1437
1933
|
throw ERRORS.TypedError(
|
|
1438
1934
|
HardwareErrorCode.RuntimeError,
|
|
1439
|
-
`Protocol V2 firmware update status
|
|
1440
|
-
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
1441
|
-
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1935
|
+
`Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
|
|
1442
1936
|
);
|
|
1443
1937
|
}
|
|
1444
1938
|
|
|
1445
1939
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1446
1940
|
await this.reconnectProtocolV2Device();
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
} catch (error) {
|
|
1454
|
-
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1455
|
-
}
|
|
1941
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
1942
|
+
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1943
|
+
// repeating it after an automatic App reboot is idempotent.
|
|
1456
1944
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1945
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1457
1946
|
}
|
|
1458
1947
|
|
|
1459
1948
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1469,13 +1958,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1469
1958
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1470
1959
|
);
|
|
1471
1960
|
|
|
1472
|
-
return this.getProtocolV2VersionResult(features);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
private getProtocolV2VersionResult(features: Features) {
|
|
1476
1961
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1477
1962
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1478
1963
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1964
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1479
1965
|
if (firmwareVersion === '0.0.0') {
|
|
1480
1966
|
Log.warn(
|
|
1481
1967
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1489,20 +1975,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1489
1975
|
};
|
|
1490
1976
|
}
|
|
1491
1977
|
|
|
1978
|
+
private async completeProtocolV2FinalVerification() {
|
|
1979
|
+
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1980
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
1981
|
+
}
|
|
1982
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1983
|
+
this.assertExpectedProtocolV2Versions();
|
|
1984
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1985
|
+
DevicePool.resetState();
|
|
1986
|
+
return versions;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1492
1989
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1493
1990
|
const startTime = Date.now();
|
|
1494
1991
|
let lastError: unknown;
|
|
1495
|
-
let shouldReconnect = true;
|
|
1496
1992
|
|
|
1497
1993
|
while (Date.now() - startTime < timeout) {
|
|
1498
1994
|
try {
|
|
1499
|
-
|
|
1500
|
-
await this.reconnectProtocolV2Device();
|
|
1501
|
-
shouldReconnect = false;
|
|
1502
|
-
}
|
|
1995
|
+
await this.reconnectProtocolV2Device();
|
|
1503
1996
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1504
1997
|
commands: this.device.getCommands(),
|
|
1505
|
-
timeoutMs:
|
|
1998
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1506
1999
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
1507
2000
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1508
2001
|
});
|
|
@@ -1511,22 +2004,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1511
2004
|
deviceInfo,
|
|
1512
2005
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1513
2006
|
);
|
|
1514
|
-
if (features.mode
|
|
1515
|
-
|
|
2007
|
+
if (features.mode !== 'normal' || features.bootloaderMode) {
|
|
2008
|
+
throw ERRORS.TypedError(
|
|
2009
|
+
HardwareErrorCode.DeviceNotFound,
|
|
2010
|
+
'Protocol V2 device is still in bootloader mode'
|
|
2011
|
+
);
|
|
1516
2012
|
}
|
|
1517
|
-
|
|
1518
|
-
HardwareErrorCode.DeviceNotFound,
|
|
1519
|
-
'Protocol V2 device is still in bootloader mode'
|
|
1520
|
-
);
|
|
2013
|
+
return features;
|
|
1521
2014
|
} catch (error) {
|
|
1522
2015
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1523
2016
|
throw error;
|
|
1524
2017
|
}
|
|
1525
|
-
shouldReconnect = true;
|
|
1526
2018
|
lastError = error;
|
|
1527
2019
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
2020
|
+
await wait(1000);
|
|
1528
2021
|
}
|
|
1529
|
-
await wait(1000);
|
|
1530
2022
|
}
|
|
1531
2023
|
|
|
1532
2024
|
throw ERRORS.TypedError(
|
|
@@ -1550,13 +2042,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1550
2042
|
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
1551
2043
|
devicesDescriptor.length === 1
|
|
1552
2044
|
) {
|
|
1553
|
-
const descriptor = devicesDescriptor[0];
|
|
1554
|
-
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
1555
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1556
|
-
}
|
|
1557
2045
|
this.device.updateDescriptor(
|
|
1558
2046
|
{
|
|
1559
|
-
...
|
|
2047
|
+
...devicesDescriptor[0],
|
|
1560
2048
|
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1561
2049
|
},
|
|
1562
2050
|
true
|
|
@@ -1567,29 +2055,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1567
2055
|
return;
|
|
1568
2056
|
}
|
|
1569
2057
|
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
2058
|
+
// App and bootloader serials may differ temporarily. During V4 reconnect, accept
|
|
2059
|
+
// only a uniquely enumerated device instead of repeatedly using the old App path.
|
|
1572
2060
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
1573
2061
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1574
2062
|
});
|
|
1575
|
-
|
|
1576
|
-
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
1577
|
-
const reconnectDevice = deviceList.find(candidate => {
|
|
1578
|
-
const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
|
|
1579
|
-
if (expectedSerialNumber && candidateSerialNumber) {
|
|
1580
|
-
return candidateSerialNumber === expectedSerialNumber;
|
|
1581
|
-
}
|
|
1582
|
-
return !!expectedPath && candidate.getConnectId() === expectedPath;
|
|
1583
|
-
});
|
|
1584
|
-
if (!reconnectDevice) {
|
|
2063
|
+
if (deviceList.length !== 1) {
|
|
1585
2064
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1586
2065
|
}
|
|
1587
2066
|
|
|
1588
2067
|
Log.debug(
|
|
1589
|
-
'Protocol V2 firmware reconnect using
|
|
1590
|
-
|
|
2068
|
+
'Protocol V2 firmware reconnect using single enumerated device:',
|
|
2069
|
+
deviceList[0].getConnectId()
|
|
1591
2070
|
);
|
|
1592
|
-
this.device.updateFromCache(
|
|
2071
|
+
this.device.updateFromCache(deviceList[0]);
|
|
1593
2072
|
await this.ensureProtocolV2DeviceAcquired();
|
|
1594
2073
|
this.device.commands.disposed = false;
|
|
1595
2074
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -1613,99 +2092,35 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1613
2092
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1614
2093
|
}
|
|
1615
2094
|
|
|
1616
|
-
private async
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1628
|
-
break;
|
|
1629
|
-
}
|
|
1630
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
throw ERRORS.TypedError(
|
|
1635
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1636
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
private async protocolV2WriteWholeFile({
|
|
1641
|
-
payload,
|
|
1642
|
-
filePath,
|
|
1643
|
-
processedSize,
|
|
1644
|
-
totalSize,
|
|
1645
|
-
onTransferredBytes,
|
|
1646
|
-
}: ProtocolV2FileTransferParams) {
|
|
1647
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1648
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1649
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1650
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1651
|
-
}
|
|
1652
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1653
|
-
};
|
|
1654
|
-
|
|
1655
|
-
try {
|
|
1656
|
-
await writeProtocolV2File({
|
|
1657
|
-
commands: this.device.getCommands(),
|
|
2095
|
+
private async fileWriteChunk(
|
|
2096
|
+
filePath: string,
|
|
2097
|
+
totalFileSize: number,
|
|
2098
|
+
offset: number,
|
|
2099
|
+
chunk: ArrayBuffer | Buffer,
|
|
2100
|
+
overwrite: boolean,
|
|
2101
|
+
progress: number | null
|
|
2102
|
+
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
2103
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2104
|
+
const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
|
|
2105
|
+
file: {
|
|
1658
2106
|
path: filePath,
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
onProgress: progress => {
|
|
1673
|
-
const transferredBytes = (processedSize ?? 0) + progress.transferredBytes;
|
|
1674
|
-
onTransferredBytes?.(transferredBytes);
|
|
1675
|
-
this.postProgressMessage(getUploadProgress(progress.transferredBytes), 'transferData', {
|
|
1676
|
-
transferredBytes,
|
|
1677
|
-
totalBytes: totalSize ?? payload.byteLength,
|
|
1678
|
-
rateBytesPerSecond: progress.rateBytesPerSecond,
|
|
1679
|
-
elapsedMs: progress.elapsedMs,
|
|
1680
|
-
});
|
|
1681
|
-
},
|
|
1682
|
-
});
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
if (
|
|
1685
|
-
error instanceof HardwareError &&
|
|
1686
|
-
error.errorCode === HardwareErrorCode.RuntimeError &&
|
|
1687
|
-
error.message.includes('FilesystemFileWrite')
|
|
1688
|
-
) {
|
|
1689
|
-
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, error.message);
|
|
2107
|
+
offset,
|
|
2108
|
+
total_size: totalFileSize,
|
|
2109
|
+
data: chunk,
|
|
2110
|
+
},
|
|
2111
|
+
overwrite,
|
|
2112
|
+
append: false,
|
|
2113
|
+
ui_percentage: progress ?? undefined,
|
|
2114
|
+
});
|
|
2115
|
+
if (writeRes.type !== 'FilesystemFile') {
|
|
2116
|
+
if ((writeRes as any).type === 'CallMethodError') {
|
|
2117
|
+
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
2118
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
2119
|
+
}
|
|
1690
2120
|
}
|
|
1691
|
-
throw error;
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
private getProtocolV2FirmwareTransferTransport() {
|
|
1698
|
-
const env = DataManager.getSettings('env');
|
|
1699
|
-
if (env && DataManager.isBleConnect(env)) {
|
|
1700
|
-
return 'BLE';
|
|
1701
|
-
}
|
|
1702
|
-
if (
|
|
1703
|
-
env &&
|
|
1704
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1705
|
-
) {
|
|
1706
|
-
return 'WebUSB';
|
|
2121
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
|
|
1707
2122
|
}
|
|
1708
|
-
return
|
|
2123
|
+
return writeRes;
|
|
1709
2124
|
}
|
|
1710
2125
|
|
|
1711
2126
|
private async recoverProtocolV2FileTransfer() {
|