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