@onekeyfe/hd-core 1.2.0-alpha.64 → 1.2.0-alpha.66
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 +313 -0
- package/__tests__/DeviceCommands.test.ts +59 -13
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-settings.test.ts +39 -18
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +13 -1
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +11 -1
- package/__tests__/deviceUploadNft.test.ts +316 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +340 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
- package/__tests__/protocol-v2.test.ts +1506 -1097
- package/__tests__/protocolV2FileWrite.test.ts +152 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +13 -27
- 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 +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- 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/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- 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 +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +6 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +9 -4
- 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 +29 -6
- 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 +2 -1
- 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 +4 -0
- 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 +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +243 -248
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3051 -3774
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- 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/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- 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 +3 -5
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -71
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- 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 +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +38 -21
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- 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 +4 -0
- 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/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +59 -60
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -316
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +597 -1005
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +71 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
- package/src/api/device/DeviceSettings.ts +5 -4
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -125
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +202 -63
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- 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 +4 -0
- package/src/core/index.ts +213 -111
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +113 -41
- package/src/data-manager/TransportManager.ts +6 -0
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +274 -49
- package/src/device/DeviceCommands.ts +31 -4
- package/src/device/DeviceConnector.ts +33 -13
- package/src/device/DevicePool.ts +21 -7
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +390 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +3 -5
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +6 -79
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +15 -3
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +60 -22
- package/src/utils/deviceFeaturesCompat.ts +5 -6
- package/src/utils/deviceFeaturesUtils.ts +14 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -1,772 +0,0 @@
|
|
|
1
|
-
import { bytesToHex } from '@noble/hashes/utils';
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
|
-
import semver from 'semver';
|
|
5
|
-
|
|
6
|
-
import { getDeviceType, getDeviceUUID } from '../../utils/deviceInfoUtils';
|
|
7
|
-
import { resolveDeviceBootloaderMode } from '../../utils/deviceFeaturesCompat';
|
|
8
|
-
import {
|
|
9
|
-
getDeviceBootloaderVersion,
|
|
10
|
-
getDeviceFirmwareVersion,
|
|
11
|
-
} from '../../utils/deviceVersionUtils';
|
|
12
|
-
|
|
13
|
-
import type {
|
|
14
|
-
FirmwareUpdatePlan,
|
|
15
|
-
FirmwareUpdatePlanArtifact,
|
|
16
|
-
FirmwareUpdatePlanForceTarget,
|
|
17
|
-
FirmwareUpdatePlanTarget,
|
|
18
|
-
} from '../../types/api/firmwareUpdatePlan';
|
|
19
|
-
import type { Features } from '../../types';
|
|
20
|
-
|
|
21
|
-
type FirmwareUpdatePlatform = 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
|
|
22
|
-
|
|
23
|
-
type ReleaseRecord = {
|
|
24
|
-
url?: unknown;
|
|
25
|
-
webUpdate?: unknown;
|
|
26
|
-
resource?: unknown;
|
|
27
|
-
fullResource?: unknown;
|
|
28
|
-
fullResourceRange?: unknown;
|
|
29
|
-
bootloaderResource?: unknown;
|
|
30
|
-
version?: unknown;
|
|
31
|
-
components?: unknown;
|
|
32
|
-
installOrder?: unknown;
|
|
33
|
-
resourceBundles?: unknown;
|
|
34
|
-
fingerprint?: unknown;
|
|
35
|
-
fingerprintWeb?: unknown;
|
|
36
|
-
expectedSize?: unknown;
|
|
37
|
-
resourceFingerprint?: unknown;
|
|
38
|
-
resourceExpectedSize?: unknown;
|
|
39
|
-
fullResourceFingerprint?: unknown;
|
|
40
|
-
fullResourceExpectedSize?: unknown;
|
|
41
|
-
bootloaderFingerprint?: unknown;
|
|
42
|
-
bootloaderExpectedSize?: unknown;
|
|
43
|
-
bootloaderVersion?: unknown;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type ReleaseSelection = {
|
|
47
|
-
shouldUpdate?: boolean;
|
|
48
|
-
status?: unknown;
|
|
49
|
-
release?: unknown;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const PROTOCOL_V2_TARGETS: Readonly<
|
|
53
|
-
Record<string, Exclude<FirmwareUpdatePlanTarget, 'firmware' | 'ble' | 'bootloader' | 'resource'>>
|
|
54
|
-
> = {
|
|
55
|
-
BOOTLOADER: 'boot',
|
|
56
|
-
APPLICATION_P1: 'app_v1',
|
|
57
|
-
APPLICATION_P2: 'app_v2',
|
|
58
|
-
COPROCESSOR: 'coprocessor',
|
|
59
|
-
SE01: 'se01',
|
|
60
|
-
SE02: 'se02',
|
|
61
|
-
SE03: 'se03',
|
|
62
|
-
SE04: 'se04',
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
66
|
-
value && typeof value === 'object' && !Array.isArray(value)
|
|
67
|
-
? (value as Record<string, unknown>)
|
|
68
|
-
: undefined;
|
|
69
|
-
|
|
70
|
-
const asRelease = (release: ReleaseSelection | undefined): ReleaseRecord | undefined =>
|
|
71
|
-
asRecord(release?.release) as ReleaseRecord | undefined;
|
|
72
|
-
|
|
73
|
-
const asString = (value: unknown): string | undefined =>
|
|
74
|
-
typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
75
|
-
|
|
76
|
-
const asVersion = (value: unknown): string | undefined => {
|
|
77
|
-
if (
|
|
78
|
-
!Array.isArray(value) ||
|
|
79
|
-
value.length !== 3 ||
|
|
80
|
-
value.some(part => typeof part !== 'number' || !Number.isSafeInteger(part) || part < 0)
|
|
81
|
-
) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
return value.join('.');
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const asIntegrity = ({
|
|
88
|
-
size,
|
|
89
|
-
sha256: value,
|
|
90
|
-
}: {
|
|
91
|
-
size: unknown;
|
|
92
|
-
sha256: unknown;
|
|
93
|
-
}): Pick<FirmwareUpdatePlanArtifact, 'expectedSize' | 'expectedSha256'> => {
|
|
94
|
-
const sha256Value =
|
|
95
|
-
typeof value === 'string' && /^[a-f0-9]{64}$/iu.test(value) ? value.toLowerCase() : undefined;
|
|
96
|
-
return {
|
|
97
|
-
...(Number.isSafeInteger(size) && (size as number) > 0 ? { expectedSize: size as number } : {}),
|
|
98
|
-
...(sha256Value ? { expectedSha256: sha256Value } : {}),
|
|
99
|
-
};
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const isUpgrade = (release: ReleaseSelection | undefined): boolean =>
|
|
103
|
-
release?.status === 'required' ||
|
|
104
|
-
release?.status === 'outdated' ||
|
|
105
|
-
release?.shouldUpdate === true;
|
|
106
|
-
|
|
107
|
-
const assertArtifactUrl = (value: unknown, label: string): string => {
|
|
108
|
-
const raw = asString(value);
|
|
109
|
-
if (!raw) {
|
|
110
|
-
throw ERRORS.TypedError(
|
|
111
|
-
HardwareErrorCode.RuntimeError,
|
|
112
|
-
`${label} has no downloadable artifact`,
|
|
113
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
let parsed: URL;
|
|
117
|
-
try {
|
|
118
|
-
parsed = new URL(raw);
|
|
119
|
-
} catch {
|
|
120
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, `${label} URL is invalid`, {
|
|
121
|
-
firmwareUpdateCode: 'FirmwarePlanInvalid',
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
if (
|
|
125
|
-
parsed.protocol !== 'https:' ||
|
|
126
|
-
parsed.port !== '' ||
|
|
127
|
-
parsed.username ||
|
|
128
|
-
parsed.password ||
|
|
129
|
-
parsed.hash
|
|
130
|
-
) {
|
|
131
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, `${label} URL is not canonical HTTPS`, {
|
|
132
|
-
firmwareUpdateCode: 'FirmwarePlanInvalid',
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
return raw;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const canonicalizeFirmwareUpdateContract = (value: unknown): unknown => {
|
|
139
|
-
if (Array.isArray(value)) {
|
|
140
|
-
return value.map(canonicalizeFirmwareUpdateContract);
|
|
141
|
-
}
|
|
142
|
-
if (value && typeof value === 'object') {
|
|
143
|
-
return Object.keys(value as Record<string, unknown>)
|
|
144
|
-
.sort()
|
|
145
|
-
.reduce<Record<string, unknown>>((result, key) => {
|
|
146
|
-
const item = (value as Record<string, unknown>)[key];
|
|
147
|
-
if (item !== undefined) {
|
|
148
|
-
result[key] = canonicalizeFirmwareUpdateContract(item);
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}, {});
|
|
152
|
-
}
|
|
153
|
-
return value;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
export const digestFirmwareUpdateContract = (value: unknown): string =>
|
|
157
|
-
bytesToHex(
|
|
158
|
-
sha256(new TextEncoder().encode(JSON.stringify(canonicalizeFirmwareUpdateContract(value))))
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const digestFirmwareUpdatePlan = (plan: Omit<FirmwareUpdatePlan, 'planDigest'>): string =>
|
|
162
|
-
digestFirmwareUpdateContract(plan);
|
|
163
|
-
|
|
164
|
-
const planError = (message: string): never => {
|
|
165
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, message, {
|
|
166
|
-
firmwareUpdateCode: 'FirmwarePlanInvalid',
|
|
167
|
-
});
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const FIRMWARE_UPDATE_PLAN_FORCE_TARGETS = new Set<FirmwareUpdatePlanForceTarget>([
|
|
171
|
-
'firmware',
|
|
172
|
-
'ble',
|
|
173
|
-
'bootloader',
|
|
174
|
-
'resource',
|
|
175
|
-
]);
|
|
176
|
-
|
|
177
|
-
export const validateFirmwareUpdatePlanForceTargets = (
|
|
178
|
-
value: unknown
|
|
179
|
-
): FirmwareUpdatePlanForceTarget[] => {
|
|
180
|
-
if (value === undefined) {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
if (
|
|
184
|
-
!Array.isArray(value) ||
|
|
185
|
-
value.length > FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.size ||
|
|
186
|
-
value.some(target => !FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.has(target)) ||
|
|
187
|
-
new Set(value).size !== value.length
|
|
188
|
-
) {
|
|
189
|
-
return planError('Firmware update force targets are invalid');
|
|
190
|
-
}
|
|
191
|
-
return [...value] as FirmwareUpdatePlanForceTarget[];
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const assertExactKeys = (
|
|
195
|
-
value: Record<string, unknown>,
|
|
196
|
-
required: readonly string[],
|
|
197
|
-
optional: readonly string[] = []
|
|
198
|
-
) => {
|
|
199
|
-
const allowed = new Set([...required, ...optional]);
|
|
200
|
-
if (
|
|
201
|
-
required.some(key => !Object.prototype.hasOwnProperty.call(value, key)) ||
|
|
202
|
-
Object.keys(value).some(key => !allowed.has(key))
|
|
203
|
-
) {
|
|
204
|
-
planError('Firmware update plan contains an invalid field set');
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const assertBoundedString = (value: unknown, label: string, maxLength = 256): string => {
|
|
209
|
-
if (typeof value !== 'string' || value.length === 0 || value.length > maxLength) {
|
|
210
|
-
return planError(`Firmware update plan ${label} is invalid`);
|
|
211
|
-
}
|
|
212
|
-
return value;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
export const FIRMWARE_UPDATE_PLAN_TARGETS = new Set<FirmwareUpdatePlanTarget>([
|
|
216
|
-
'firmware',
|
|
217
|
-
'ble',
|
|
218
|
-
'bootloader',
|
|
219
|
-
'resource',
|
|
220
|
-
'boot',
|
|
221
|
-
'app_v1',
|
|
222
|
-
'app_v2',
|
|
223
|
-
'coprocessor',
|
|
224
|
-
'se01',
|
|
225
|
-
'se02',
|
|
226
|
-
'se03',
|
|
227
|
-
'se04',
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
export const FIRMWARE_UPDATE_PLAN_ROLES = new Set([
|
|
231
|
-
'firmware',
|
|
232
|
-
'ble',
|
|
233
|
-
'bootloader',
|
|
234
|
-
'resource',
|
|
235
|
-
'component',
|
|
236
|
-
'resourceBundle',
|
|
237
|
-
]);
|
|
238
|
-
|
|
239
|
-
const PLAN_CONTAINERS = new Set(['raw', 'zip']);
|
|
240
|
-
|
|
241
|
-
const PLAN_PLATFORMS = new Set<FirmwareUpdatePlatform>([
|
|
242
|
-
'native',
|
|
243
|
-
'desktop',
|
|
244
|
-
'ext',
|
|
245
|
-
'web',
|
|
246
|
-
'web-embed',
|
|
247
|
-
]);
|
|
248
|
-
|
|
249
|
-
export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan => {
|
|
250
|
-
const plan = asRecord(value);
|
|
251
|
-
if (!plan) {
|
|
252
|
-
return planError('Firmware update plan must be an object');
|
|
253
|
-
}
|
|
254
|
-
assertExactKeys(plan, [
|
|
255
|
-
'schemaVersion',
|
|
256
|
-
'planDigest',
|
|
257
|
-
'executor',
|
|
258
|
-
'deviceIdentity',
|
|
259
|
-
'deviceModel',
|
|
260
|
-
'firmwareType',
|
|
261
|
-
'platform',
|
|
262
|
-
'artifacts',
|
|
263
|
-
'targetsToUpdate',
|
|
264
|
-
]);
|
|
265
|
-
if (
|
|
266
|
-
plan.schemaVersion !== 2 ||
|
|
267
|
-
typeof plan.planDigest !== 'string' ||
|
|
268
|
-
!/^[a-f0-9]{64}$/u.test(plan.planDigest) ||
|
|
269
|
-
(plan.executor !== 'v2' && plan.executor !== 'v3' && plan.executor !== 'v4') ||
|
|
270
|
-
!PLAN_PLATFORMS.has(plan.platform as FirmwareUpdatePlatform) ||
|
|
271
|
-
(plan.firmwareType !== EFirmwareType.Universal &&
|
|
272
|
-
plan.firmwareType !== EFirmwareType.BitcoinOnly)
|
|
273
|
-
) {
|
|
274
|
-
return planError('Firmware update plan header is invalid');
|
|
275
|
-
}
|
|
276
|
-
assertBoundedString(plan.deviceIdentity, 'device identity');
|
|
277
|
-
assertBoundedString(plan.deviceModel, 'device model', 64);
|
|
278
|
-
if (!Array.isArray(plan.artifacts) || plan.artifacts.length > 128) {
|
|
279
|
-
return planError('Firmware update plan artifact set is invalid');
|
|
280
|
-
}
|
|
281
|
-
const artifactIds = new Set<string>();
|
|
282
|
-
const artifactTargets = new Set<FirmwareUpdatePlanTarget>();
|
|
283
|
-
for (const item of plan.artifacts) {
|
|
284
|
-
const artifact = asRecord(item);
|
|
285
|
-
if (!artifact) {
|
|
286
|
-
return planError('Firmware update plan artifact is invalid');
|
|
287
|
-
}
|
|
288
|
-
assertExactKeys(
|
|
289
|
-
artifact,
|
|
290
|
-
['artifactId', 'role', 'target', 'url', 'container'],
|
|
291
|
-
['logicalName', 'expectedSize', 'expectedSha256', 'targetVersion']
|
|
292
|
-
);
|
|
293
|
-
const artifactId = assertBoundedString(artifact.artifactId, 'artifact id', 160);
|
|
294
|
-
if (artifactIds.has(artifactId)) {
|
|
295
|
-
return planError('Firmware update plan contains duplicate artifact ids');
|
|
296
|
-
}
|
|
297
|
-
artifactIds.add(artifactId);
|
|
298
|
-
if (
|
|
299
|
-
!FIRMWARE_UPDATE_PLAN_ROLES.has(artifact.role as string) ||
|
|
300
|
-
!FIRMWARE_UPDATE_PLAN_TARGETS.has(artifact.target as FirmwareUpdatePlanTarget) ||
|
|
301
|
-
!PLAN_CONTAINERS.has(artifact.container as string)
|
|
302
|
-
) {
|
|
303
|
-
return planError('Firmware update plan artifact contract is invalid');
|
|
304
|
-
}
|
|
305
|
-
artifactTargets.add(artifact.target as FirmwareUpdatePlanTarget);
|
|
306
|
-
assertArtifactUrl(artifact.url, `Firmware artifact ${artifactId}`);
|
|
307
|
-
if (artifact.logicalName !== undefined) {
|
|
308
|
-
assertBoundedString(artifact.logicalName, 'logical name', 256);
|
|
309
|
-
}
|
|
310
|
-
if (
|
|
311
|
-
artifact.expectedSize !== undefined &&
|
|
312
|
-
(!Number.isSafeInteger(artifact.expectedSize) || (artifact.expectedSize as number) <= 0)
|
|
313
|
-
) {
|
|
314
|
-
return planError('Firmware update plan artifact size is invalid');
|
|
315
|
-
}
|
|
316
|
-
if (
|
|
317
|
-
artifact.expectedSha256 !== undefined &&
|
|
318
|
-
(typeof artifact.expectedSha256 !== 'string' ||
|
|
319
|
-
!/^[a-f0-9]{64}$/u.test(artifact.expectedSha256))
|
|
320
|
-
) {
|
|
321
|
-
return planError('Firmware update plan artifact digest is invalid');
|
|
322
|
-
}
|
|
323
|
-
if (artifact.targetVersion !== undefined) {
|
|
324
|
-
assertBoundedString(artifact.targetVersion, 'target version', 64);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (
|
|
328
|
-
!Array.isArray(plan.targetsToUpdate) ||
|
|
329
|
-
plan.targetsToUpdate.some(
|
|
330
|
-
target => !FIRMWARE_UPDATE_PLAN_TARGETS.has(target as FirmwareUpdatePlanTarget)
|
|
331
|
-
) ||
|
|
332
|
-
new Set(plan.targetsToUpdate).size !== plan.targetsToUpdate.length ||
|
|
333
|
-
plan.targetsToUpdate.length !== artifactTargets.size ||
|
|
334
|
-
plan.targetsToUpdate.some(target => !artifactTargets.has(target as FirmwareUpdatePlanTarget))
|
|
335
|
-
) {
|
|
336
|
-
return planError('Firmware update plan targets are invalid');
|
|
337
|
-
}
|
|
338
|
-
const legacyTargets = new Set<FirmwareUpdatePlanTarget>([
|
|
339
|
-
'firmware',
|
|
340
|
-
'ble',
|
|
341
|
-
'bootloader',
|
|
342
|
-
'resource',
|
|
343
|
-
]);
|
|
344
|
-
const legacyOnlyTargets = new Set<FirmwareUpdatePlanTarget>(['firmware', 'ble', 'bootloader']);
|
|
345
|
-
if (
|
|
346
|
-
((plan.executor === 'v2' || plan.executor === 'v3') &&
|
|
347
|
-
plan.targetsToUpdate.some(
|
|
348
|
-
target => !legacyTargets.has(target as FirmwareUpdatePlanTarget)
|
|
349
|
-
)) ||
|
|
350
|
-
(plan.executor === 'v4' &&
|
|
351
|
-
plan.targetsToUpdate.some(target =>
|
|
352
|
-
legacyOnlyTargets.has(target as FirmwareUpdatePlanTarget)
|
|
353
|
-
)) ||
|
|
354
|
-
// Only executor v2 services identity-less bootloader recovery (classic family);
|
|
355
|
-
// Pro (v3) and Pro2 (v4) report a serial even in bootloader mode, so their plans
|
|
356
|
-
// must always carry a real identity. Degraded v2 plans are bound to the live
|
|
357
|
-
// device at install time instead.
|
|
358
|
-
(plan.artifacts.length > 0 &&
|
|
359
|
-
(plan.platform === 'native' || plan.platform === 'desktop') &&
|
|
360
|
-
plan.executor !== 'v2' &&
|
|
361
|
-
plan.deviceIdentity === 'unavailable')
|
|
362
|
-
) {
|
|
363
|
-
return planError('Firmware update plan executor contract is invalid');
|
|
364
|
-
}
|
|
365
|
-
const { planDigest, ...withoutDigest } = plan as unknown as FirmwareUpdatePlan;
|
|
366
|
-
if (digestFirmwareUpdatePlan(withoutDigest) !== planDigest) {
|
|
367
|
-
return planError('Firmware update plan digest is invalid');
|
|
368
|
-
}
|
|
369
|
-
return plan as unknown as FirmwareUpdatePlan;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const selectResourceUrl = ({
|
|
373
|
-
release,
|
|
374
|
-
features,
|
|
375
|
-
platform,
|
|
376
|
-
}: {
|
|
377
|
-
release: ReleaseRecord;
|
|
378
|
-
features: Features;
|
|
379
|
-
platform: FirmwareUpdatePlatform;
|
|
380
|
-
}): string | undefined => {
|
|
381
|
-
const resource = asString(release.resource);
|
|
382
|
-
const fullResource = asString(release.fullResource);
|
|
383
|
-
const range = release.fullResourceRange;
|
|
384
|
-
const currentVersion = getDeviceFirmwareVersion(features).join('.');
|
|
385
|
-
const targetVersion = asVersion(release.version);
|
|
386
|
-
if (
|
|
387
|
-
platform === 'desktop' &&
|
|
388
|
-
fullResource &&
|
|
389
|
-
targetVersion &&
|
|
390
|
-
Array.isArray(range) &&
|
|
391
|
-
range.length === 2 &&
|
|
392
|
-
typeof range[0] === 'string' &&
|
|
393
|
-
typeof range[1] === 'string' &&
|
|
394
|
-
semver.valid(currentVersion) &&
|
|
395
|
-
semver.valid(targetVersion) &&
|
|
396
|
-
semver.valid(range[0]) &&
|
|
397
|
-
semver.valid(range[1]) &&
|
|
398
|
-
semver.lt(currentVersion, range[0]) &&
|
|
399
|
-
semver.gte(targetVersion, range[1])
|
|
400
|
-
) {
|
|
401
|
-
return fullResource;
|
|
402
|
-
}
|
|
403
|
-
return resource;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
const getExecutor = (features: Features): FirmwareUpdatePlan['executor'] => {
|
|
407
|
-
const deviceType = getDeviceType(features);
|
|
408
|
-
if (deviceType === EDeviceType.Pro2) {
|
|
409
|
-
return 'v4';
|
|
410
|
-
}
|
|
411
|
-
if (
|
|
412
|
-
deviceType === EDeviceType.Pro &&
|
|
413
|
-
semver.gte(getDeviceBootloaderVersion(features).join('.'), '2.8.0')
|
|
414
|
-
) {
|
|
415
|
-
return 'v3';
|
|
416
|
-
}
|
|
417
|
-
return 'v2';
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
const buildProtocolV2Artifacts = (
|
|
421
|
-
release: ReleaseRecord,
|
|
422
|
-
{
|
|
423
|
-
includeComponents = true,
|
|
424
|
-
includeResources = true,
|
|
425
|
-
}: {
|
|
426
|
-
includeComponents?: boolean;
|
|
427
|
-
includeResources?: boolean;
|
|
428
|
-
} = {}
|
|
429
|
-
): {
|
|
430
|
-
artifacts: FirmwareUpdatePlanArtifact[];
|
|
431
|
-
targets: FirmwareUpdatePlanTarget[];
|
|
432
|
-
} => {
|
|
433
|
-
const components = asRecord(release.components);
|
|
434
|
-
if (includeComponents && !components) {
|
|
435
|
-
throw ERRORS.TypedError(
|
|
436
|
-
HardwareErrorCode.RuntimeError,
|
|
437
|
-
'Protocol V2 release has no component set',
|
|
438
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
const installOrder = Array.isArray(release.installOrder)
|
|
442
|
-
? release.installOrder.filter((key): key is string => typeof key === 'string')
|
|
443
|
-
: [];
|
|
444
|
-
const componentKeys =
|
|
445
|
-
includeComponents && components
|
|
446
|
-
? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
|
|
447
|
-
: [];
|
|
448
|
-
const artifacts: FirmwareUpdatePlanArtifact[] = [];
|
|
449
|
-
const targets: FirmwareUpdatePlanTarget[] = [];
|
|
450
|
-
const componentTargets = new Set<FirmwareUpdatePlanTarget>();
|
|
451
|
-
for (const key of componentKeys) {
|
|
452
|
-
const component = asRecord(components?.[key]);
|
|
453
|
-
const targetName = asString(component?.target)?.toUpperCase();
|
|
454
|
-
if (targetName === 'ROMLOADER') {
|
|
455
|
-
throw ERRORS.TypedError(
|
|
456
|
-
HardwareErrorCode.RuntimeError,
|
|
457
|
-
'Protocol V2 ROMLOADER requires its dedicated loader flow',
|
|
458
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
462
|
-
if (!component || !target) {
|
|
463
|
-
throw ERRORS.TypedError(
|
|
464
|
-
HardwareErrorCode.RuntimeError,
|
|
465
|
-
`Protocol V2 component ${key} has an unsupported target`,
|
|
466
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
if (componentTargets.has(target)) {
|
|
470
|
-
throw ERRORS.TypedError(
|
|
471
|
-
HardwareErrorCode.RuntimeError,
|
|
472
|
-
`Protocol V2 component ${key} duplicates target ${target}`,
|
|
473
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
componentTargets.add(target);
|
|
477
|
-
artifacts.push({
|
|
478
|
-
artifactId: `component:${target}`,
|
|
479
|
-
role: 'component',
|
|
480
|
-
target,
|
|
481
|
-
url: assertArtifactUrl(component.url, `Protocol V2 component ${key}`),
|
|
482
|
-
container: 'raw',
|
|
483
|
-
logicalName: key,
|
|
484
|
-
...asIntegrity({
|
|
485
|
-
size: component.expectedSize,
|
|
486
|
-
sha256: component.fingerprint,
|
|
487
|
-
}),
|
|
488
|
-
...(asVersion(component.version) ? { targetVersion: asVersion(component.version) } : {}),
|
|
489
|
-
});
|
|
490
|
-
targets.push(target);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const bundles =
|
|
494
|
-
includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
|
|
495
|
-
const resourceBundleNames = new Set<string>();
|
|
496
|
-
for (const value of bundles) {
|
|
497
|
-
const bundle = asRecord(value);
|
|
498
|
-
const name = asString(bundle?.name);
|
|
499
|
-
if (!bundle || !name) {
|
|
500
|
-
throw ERRORS.TypedError(
|
|
501
|
-
HardwareErrorCode.RuntimeError,
|
|
502
|
-
'Protocol V2 resource bundle is invalid',
|
|
503
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
if (resourceBundleNames.has(name)) {
|
|
507
|
-
throw ERRORS.TypedError(
|
|
508
|
-
HardwareErrorCode.RuntimeError,
|
|
509
|
-
`Protocol V2 resource bundle duplicates name ${name}`,
|
|
510
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
resourceBundleNames.add(name);
|
|
514
|
-
artifacts.push({
|
|
515
|
-
artifactId: `resourceBundle:${name}`,
|
|
516
|
-
role: 'resourceBundle',
|
|
517
|
-
target: 'resource',
|
|
518
|
-
url: assertArtifactUrl(bundle.url, `Protocol V2 resource bundle ${name}`),
|
|
519
|
-
container: 'raw',
|
|
520
|
-
logicalName: name,
|
|
521
|
-
...asIntegrity({
|
|
522
|
-
size: bundle.expectedSize,
|
|
523
|
-
sha256: bundle.fingerprint,
|
|
524
|
-
}),
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
if (bundles.length > 0) {
|
|
528
|
-
targets.push('resource');
|
|
529
|
-
}
|
|
530
|
-
return { artifacts, targets: [...new Set(targets)] };
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
const isForcedTargetRepresented = ({
|
|
534
|
-
executor,
|
|
535
|
-
forcedTarget,
|
|
536
|
-
artifacts,
|
|
537
|
-
targetsToUpdate,
|
|
538
|
-
}: {
|
|
539
|
-
executor: FirmwareUpdatePlan['executor'];
|
|
540
|
-
forcedTarget: FirmwareUpdatePlanForceTarget;
|
|
541
|
-
artifacts: readonly FirmwareUpdatePlanArtifact[];
|
|
542
|
-
targetsToUpdate: readonly FirmwareUpdatePlanTarget[];
|
|
543
|
-
}): boolean => {
|
|
544
|
-
if (executor !== 'v4') {
|
|
545
|
-
return artifacts.some(
|
|
546
|
-
artifact => artifact.target === forcedTarget && targetsToUpdate.includes(forcedTarget)
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
if (forcedTarget === 'firmware') {
|
|
550
|
-
return artifacts.some(
|
|
551
|
-
artifact => artifact.role === 'component' && targetsToUpdate.includes(artifact.target)
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
if (forcedTarget === 'resource') {
|
|
555
|
-
return artifacts.some(
|
|
556
|
-
artifact =>
|
|
557
|
-
artifact.role === 'resourceBundle' &&
|
|
558
|
-
artifact.target === 'resource' &&
|
|
559
|
-
targetsToUpdate.includes('resource')
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
return false;
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
const assertForcedTargetsRepresented = ({
|
|
566
|
-
executor,
|
|
567
|
-
forcedTargets,
|
|
568
|
-
artifacts,
|
|
569
|
-
targetsToUpdate,
|
|
570
|
-
}: {
|
|
571
|
-
executor: FirmwareUpdatePlan['executor'];
|
|
572
|
-
forcedTargets: readonly FirmwareUpdatePlanForceTarget[];
|
|
573
|
-
artifacts: readonly FirmwareUpdatePlanArtifact[];
|
|
574
|
-
targetsToUpdate: readonly FirmwareUpdatePlanTarget[];
|
|
575
|
-
}) => {
|
|
576
|
-
for (const forcedTarget of forcedTargets) {
|
|
577
|
-
if (
|
|
578
|
-
!isForcedTargetRepresented({
|
|
579
|
-
executor,
|
|
580
|
-
forcedTarget,
|
|
581
|
-
artifacts,
|
|
582
|
-
targetsToUpdate,
|
|
583
|
-
})
|
|
584
|
-
) {
|
|
585
|
-
planError(`Forced firmware update target ${forcedTarget} is not represented by the plan`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
export const buildFirmwareUpdatePlan = ({
|
|
591
|
-
features,
|
|
592
|
-
firmwareType,
|
|
593
|
-
platform,
|
|
594
|
-
firmware,
|
|
595
|
-
ble,
|
|
596
|
-
bootloader,
|
|
597
|
-
forceUpdateTargets,
|
|
598
|
-
}: {
|
|
599
|
-
features: Features;
|
|
600
|
-
firmwareType: EFirmwareType;
|
|
601
|
-
platform: FirmwareUpdatePlatform;
|
|
602
|
-
firmware: ReleaseSelection;
|
|
603
|
-
ble: ReleaseSelection;
|
|
604
|
-
bootloader?: ReleaseSelection;
|
|
605
|
-
forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
|
|
606
|
-
}): FirmwareUpdatePlan => {
|
|
607
|
-
const executor = getExecutor(features);
|
|
608
|
-
let artifacts: FirmwareUpdatePlanArtifact[] = [];
|
|
609
|
-
let targetsToUpdate: FirmwareUpdatePlanTarget[] = [];
|
|
610
|
-
const firmwareRelease = asRelease(firmware);
|
|
611
|
-
const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
|
|
612
|
-
const forcedTargets = new Set(validatedForceTargets);
|
|
613
|
-
const bootloaderMode = resolveDeviceBootloaderMode(features);
|
|
614
|
-
// Bootloader recovery: an erased device reports firmware status 'none', a device
|
|
615
|
-
// with intact firmware in bootloader mode reports 'unknown' (version fields carry
|
|
616
|
-
// the bootloader version), and neither can report its BLE version — yet the app
|
|
617
|
-
// treats bootloader mode as a mandatory reinstall. Only bootloader mode may treat
|
|
618
|
-
// these statuses as install targets: in normal mode 'none' also describes devices
|
|
619
|
-
// without the component at all (e.g. Mini has no BLE) and must stay excluded.
|
|
620
|
-
const isRecoveryInstall = (release: ReleaseSelection | undefined): boolean =>
|
|
621
|
-
bootloaderMode &&
|
|
622
|
-
(release?.status === 'none' || release?.status === 'unknown') &&
|
|
623
|
-
!!asRelease(release);
|
|
624
|
-
const shouldUpdateFirmware =
|
|
625
|
-
isUpgrade(firmware) || isRecoveryInstall(firmware) || forcedTargets.has('firmware');
|
|
626
|
-
const shouldUpdateResource = isUpgrade(firmware) || forcedTargets.has('resource');
|
|
627
|
-
|
|
628
|
-
if (
|
|
629
|
-
executor === 'v4' &&
|
|
630
|
-
validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')
|
|
631
|
-
) {
|
|
632
|
-
planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
|
|
636
|
-
const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {}, {
|
|
637
|
-
includeComponents: shouldUpdateFirmware,
|
|
638
|
-
includeResources: shouldUpdateResource,
|
|
639
|
-
});
|
|
640
|
-
artifacts = protocolV2.artifacts;
|
|
641
|
-
targetsToUpdate = protocolV2.targets;
|
|
642
|
-
} else {
|
|
643
|
-
if (isUpgrade(bootloader) || forcedTargets.has('bootloader')) {
|
|
644
|
-
artifacts.push({
|
|
645
|
-
artifactId: 'bootloader',
|
|
646
|
-
role: 'bootloader',
|
|
647
|
-
target: 'bootloader',
|
|
648
|
-
url: assertArtifactUrl(asRelease(bootloader)?.bootloaderResource, 'Bootloader release'),
|
|
649
|
-
container: 'raw',
|
|
650
|
-
...asIntegrity({
|
|
651
|
-
size: asRelease(bootloader)?.bootloaderExpectedSize,
|
|
652
|
-
sha256: asRelease(bootloader)?.bootloaderFingerprint,
|
|
653
|
-
}),
|
|
654
|
-
...(asVersion(asRelease(bootloader)?.bootloaderVersion)
|
|
655
|
-
? {
|
|
656
|
-
targetVersion: asVersion(asRelease(bootloader)?.bootloaderVersion),
|
|
657
|
-
}
|
|
658
|
-
: {}),
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
if (shouldUpdateFirmware || shouldUpdateResource) {
|
|
662
|
-
if (!firmwareRelease) {
|
|
663
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Firmware release is invalid', {
|
|
664
|
-
firmwareUpdateCode: 'FirmwarePlanInvalid',
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
if (shouldUpdateFirmware) {
|
|
668
|
-
artifacts.push({
|
|
669
|
-
artifactId: 'firmware',
|
|
670
|
-
role: 'firmware',
|
|
671
|
-
target: 'firmware',
|
|
672
|
-
url: assertArtifactUrl(firmwareRelease.url, 'Firmware release'),
|
|
673
|
-
container: 'raw',
|
|
674
|
-
...asIntegrity({
|
|
675
|
-
size: firmwareRelease.expectedSize,
|
|
676
|
-
sha256: firmwareRelease.fingerprint,
|
|
677
|
-
}),
|
|
678
|
-
...(asVersion(firmwareRelease.version)
|
|
679
|
-
? { targetVersion: asVersion(firmwareRelease.version) }
|
|
680
|
-
: {}),
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
if (shouldUpdateResource) {
|
|
684
|
-
const resourceUrl = selectResourceUrl({
|
|
685
|
-
release: firmwareRelease,
|
|
686
|
-
features,
|
|
687
|
-
platform,
|
|
688
|
-
});
|
|
689
|
-
if (resourceUrl) {
|
|
690
|
-
artifacts.push({
|
|
691
|
-
artifactId: 'resource',
|
|
692
|
-
role: 'resource',
|
|
693
|
-
target: 'resource',
|
|
694
|
-
url: assertArtifactUrl(resourceUrl, 'Firmware resource release'),
|
|
695
|
-
container: 'zip',
|
|
696
|
-
...asIntegrity({
|
|
697
|
-
size:
|
|
698
|
-
resourceUrl === asString(firmwareRelease.fullResource)
|
|
699
|
-
? firmwareRelease.fullResourceExpectedSize
|
|
700
|
-
: firmwareRelease.resourceExpectedSize,
|
|
701
|
-
sha256:
|
|
702
|
-
resourceUrl === asString(firmwareRelease.fullResource)
|
|
703
|
-
? firmwareRelease.fullResourceFingerprint
|
|
704
|
-
: firmwareRelease.resourceFingerprint,
|
|
705
|
-
}),
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
if (isUpgrade(ble) || isRecoveryInstall(ble) || forcedTargets.has('ble')) {
|
|
711
|
-
const bleRelease = asRelease(ble);
|
|
712
|
-
artifacts.push({
|
|
713
|
-
artifactId: 'ble',
|
|
714
|
-
role: 'ble',
|
|
715
|
-
target: 'ble',
|
|
716
|
-
url: assertArtifactUrl(bleRelease?.webUpdate ?? bleRelease?.url, 'BLE release'),
|
|
717
|
-
container: 'raw',
|
|
718
|
-
...asIntegrity({
|
|
719
|
-
size: bleRelease?.expectedSize,
|
|
720
|
-
sha256: bleRelease?.fingerprintWeb ?? bleRelease?.fingerprint,
|
|
721
|
-
}),
|
|
722
|
-
...(asVersion(bleRelease?.version)
|
|
723
|
-
? { targetVersion: asVersion(bleRelease?.version) }
|
|
724
|
-
: {}),
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
targetsToUpdate = [...new Set(artifacts.map(artifact => artifact.target))];
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
assertForcedTargetsRepresented({
|
|
731
|
-
executor,
|
|
732
|
-
forcedTargets: validatedForceTargets,
|
|
733
|
-
artifacts,
|
|
734
|
-
targetsToUpdate,
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// 'unavailable' is the reserved degraded-identity sentinel; a device-reported
|
|
738
|
-
// serial must never be able to collide with it.
|
|
739
|
-
const reportedIdentity = getDeviceUUID(features);
|
|
740
|
-
const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
|
|
741
|
-
// Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
|
|
742
|
-
// a serial number, so their recovery plans fall back to the degraded 'unavailable'
|
|
743
|
-
// identity. Binding is then enforced against the live device at install time instead
|
|
744
|
-
// (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
|
|
745
|
-
// Pro (v3) and Pro2 (v4) report a serial even in bootloader mode and stay strict.
|
|
746
|
-
if (
|
|
747
|
-
artifacts.length > 0 &&
|
|
748
|
-
(platform === 'native' || platform === 'desktop') &&
|
|
749
|
-
!deviceIdentity &&
|
|
750
|
-
!(bootloaderMode && executor === 'v2')
|
|
751
|
-
) {
|
|
752
|
-
throw ERRORS.TypedError(
|
|
753
|
-
HardwareErrorCode.RuntimeError,
|
|
754
|
-
'Prepared firmware update requires a stable device identity',
|
|
755
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
|
|
759
|
-
schemaVersion: 2,
|
|
760
|
-
executor,
|
|
761
|
-
deviceIdentity: deviceIdentity || 'unavailable',
|
|
762
|
-
deviceModel: String(getDeviceType(features)),
|
|
763
|
-
firmwareType,
|
|
764
|
-
platform,
|
|
765
|
-
artifacts,
|
|
766
|
-
targetsToUpdate,
|
|
767
|
-
};
|
|
768
|
-
return assertFirmwareUpdatePlan({
|
|
769
|
-
...planWithoutDigest,
|
|
770
|
-
planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
|
|
771
|
-
});
|
|
772
|
-
};
|