@onekeyfe/hd-core 1.2.0-alpha.54 → 1.2.0-alpha.56
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 +231 -0
- package/__tests__/DeviceCommands.test.ts +45 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +1 -1
- package/__tests__/deviceSettings.test.ts +1 -1
- package/__tests__/deviceUploadNft.test.ts +293 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +286 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
- package/__tests__/protocol-v2.test.ts +1353 -1011
- package/__tests__/protocolV2FileWrite.test.ts +39 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +86 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +11 -25
- 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 +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -9
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +4 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -2
- 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 +29 -6
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +214 -231
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2634 -3530
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -4
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +2 -70
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +22 -13
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +54 -57
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -316
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +584 -975
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +70 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +34 -11
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -122
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +27 -7
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +1 -12
- package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -0
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +167 -83
- package/src/core/uiPromiseRegistry.ts +41 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +71 -39
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +259 -43
- package/src/device/DeviceCommands.ts +7 -1
- package/src/device/DevicePool.ts +7 -2
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +359 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +118 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +2 -4
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +3 -76
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +3 -0
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +42 -13
- package/src/utils/deviceFeaturesCompat.ts +0 -6
- package/src/utils/deviceFeaturesUtils.ts +8 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -294
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -415
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -2,7 +2,6 @@ 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,
|
|
6
5
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
6
|
} from '@onekeyfe/hd-transport';
|
|
8
7
|
import { sha256 } from '@noble/hashes/sha256';
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
getLogger,
|
|
20
19
|
} from '../utils';
|
|
21
20
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
22
|
-
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
23
21
|
import { DataManager } from '../data-manager';
|
|
24
22
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
25
23
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -28,41 +26,41 @@ import {
|
|
|
28
26
|
ProtocolV2FirmwareTargetType,
|
|
29
27
|
} from '../protocols/protocol-v2';
|
|
30
28
|
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';
|
|
31
35
|
import {
|
|
32
36
|
getProtocolV2UnknownErrorText,
|
|
33
37
|
isProtocolV2DeviceDisconnectedError,
|
|
34
38
|
} 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';
|
|
41
39
|
|
|
42
|
-
import type {
|
|
43
|
-
FirmwareArtifactReference,
|
|
44
|
-
FirmwareUpdateV4Params,
|
|
45
|
-
FirmwareUpdateV4Target,
|
|
46
|
-
} from '../types/api/firmwareUpdate';
|
|
40
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
47
41
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
48
42
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
43
|
+
import type { PROTO } from '../constants';
|
|
49
44
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
50
45
|
import type {
|
|
51
46
|
Features,
|
|
52
47
|
IFirmwareReleaseInfo,
|
|
48
|
+
IProtocolV2BootResources,
|
|
53
49
|
IProtocolV2FirmwareComponent,
|
|
50
|
+
IProtocolV2Resource,
|
|
54
51
|
IVersionArray,
|
|
55
52
|
} from '../types';
|
|
56
|
-
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
57
53
|
|
|
58
54
|
const Log = getLogger(LoggerNames.Method);
|
|
59
55
|
|
|
60
56
|
const SESSION_ERROR = 'session not found';
|
|
61
|
-
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT =
|
|
62
|
-
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT =
|
|
57
|
+
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
58
|
+
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
63
59
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
60
|
+
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
64
61
|
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
65
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
62
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
63
|
+
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
66
64
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
67
65
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
68
66
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -73,12 +71,13 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
|
73
71
|
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
74
72
|
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
75
73
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
76
|
-
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT =
|
|
74
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
77
75
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
78
76
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
79
77
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
80
78
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
81
79
|
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
80
|
+
const PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME = 'boot_resources.crate.okpkg';
|
|
82
81
|
|
|
83
82
|
const getProtocolV2DeviceTransferProgress = (
|
|
84
83
|
bytesBeforeChunk: number,
|
|
@@ -97,6 +96,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
97
96
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
98
97
|
};
|
|
99
98
|
|
|
99
|
+
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
100
|
+
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
101
|
+
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
102
|
+
};
|
|
103
|
+
|
|
100
104
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
101
105
|
target_id: number | string;
|
|
102
106
|
status?: number | string;
|
|
@@ -108,18 +112,24 @@ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
|
108
112
|
|
|
109
113
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
110
114
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
111
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
115
|
+
kind: ProtocolV2RemoteComponentTarget['kind'] | 'boot_resources';
|
|
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;
|
|
112
126
|
};
|
|
127
|
+
|
|
113
128
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
114
129
|
type ProtocolV2ResourceBundleBinary = {
|
|
115
130
|
name: string;
|
|
116
131
|
binary: ArrayBuffer;
|
|
117
132
|
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;
|
|
123
133
|
};
|
|
124
134
|
|
|
125
135
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -132,35 +142,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
132
142
|
kind: 'bootloader' | 'firmware';
|
|
133
143
|
};
|
|
134
144
|
|
|
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
|
-
|
|
164
145
|
type ProtocolV2OkppHeader = {
|
|
165
146
|
type: string;
|
|
166
147
|
version: IVersionArray;
|
|
@@ -300,18 +281,6 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
300
281
|
|
|
301
282
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
302
283
|
|
|
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
|
-
|
|
315
284
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
316
285
|
Array.from(bytes)
|
|
317
286
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -404,11 +373,10 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
404
373
|
expectedSerialNumber?: string,
|
|
405
374
|
actualSerialNumber?: string
|
|
406
375
|
) => {
|
|
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.
|
|
407
378
|
if (!expectedSerialNumber || !actualSerialNumber) {
|
|
408
|
-
|
|
409
|
-
HardwareErrorCode.DeviceNotFound,
|
|
410
|
-
'Protocol V2 reconnect physical identity is unavailable'
|
|
411
|
-
);
|
|
379
|
+
return;
|
|
412
380
|
}
|
|
413
381
|
if (actualSerialNumber !== expectedSerialNumber) {
|
|
414
382
|
throw ERRORS.TypedError(
|
|
@@ -433,16 +401,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
433
401
|
return ['V2'] as const;
|
|
434
402
|
}
|
|
435
403
|
|
|
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
|
-
|
|
446
404
|
init() {
|
|
447
405
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
448
406
|
this.requireDeviceMode = [];
|
|
@@ -469,6 +427,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
469
427
|
{ name: 'chunkSize', type: 'number' },
|
|
470
428
|
{ name: 'forcedUpdateRes', type: 'boolean' },
|
|
471
429
|
{ name: 'bootloaderBinary', type: 'buffer' },
|
|
430
|
+
{ name: 'bootResourcesBinary', type: 'buffer' },
|
|
472
431
|
{ name: 'romloaderBinary', type: 'buffer' },
|
|
473
432
|
{ name: 'applicationP1Binary', type: 'buffer' },
|
|
474
433
|
{ name: 'applicationP2Binary', type: 'buffer' },
|
|
@@ -480,65 +439,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
480
439
|
{ name: 'firmwareType', type: 'string' },
|
|
481
440
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
482
441
|
{ name: 'platform', type: 'string' },
|
|
483
|
-
{ name: 'expectedDeviceId', type: 'string' },
|
|
484
442
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
485
443
|
]);
|
|
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
|
-
}
|
|
536
444
|
|
|
537
445
|
this.params = {
|
|
538
|
-
preparedPlan: payload.preparedPlan,
|
|
539
446
|
chunkSize: payload.chunkSize,
|
|
540
447
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
541
448
|
bootloaderBinary: payload.bootloaderBinary,
|
|
449
|
+
bootResourcesBinary: payload.bootResourcesBinary,
|
|
542
450
|
romloaderBinary: payload.romloaderBinary,
|
|
543
451
|
applicationP1Binary: payload.applicationP1Binary,
|
|
544
452
|
applicationP2Binary: payload.applicationP2Binary,
|
|
@@ -550,25 +458,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
550
458
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
551
459
|
firmwareType: payload.firmwareType,
|
|
552
460
|
targetsToUpdate: payload.targetsToUpdate,
|
|
553
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
554
461
|
platform: payload.platform,
|
|
555
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
556
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
557
|
-
componentArtifacts: payload.componentArtifacts,
|
|
558
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
559
462
|
};
|
|
560
463
|
}
|
|
561
464
|
|
|
562
|
-
private getProtocolV2FirmwareChunkSize(
|
|
465
|
+
private getProtocolV2FirmwareChunkSize() {
|
|
563
466
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
564
467
|
const env = DataManager.getSettings('env');
|
|
565
468
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
566
469
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
567
470
|
if (isBle) {
|
|
568
|
-
maxChunkSize =
|
|
569
|
-
direction === 'read'
|
|
570
|
-
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
571
|
-
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
471
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
572
472
|
}
|
|
573
473
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
574
474
|
return maxChunkSize;
|
|
@@ -589,36 +489,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
589
489
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
590
490
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
591
491
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
596
|
-
this.params.resourceBundleArtifacts?.length
|
|
597
|
-
) {
|
|
598
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
599
|
-
}
|
|
492
|
+
const needsRemoteResources =
|
|
493
|
+
!this.params.resourceBundleFiles?.length &&
|
|
494
|
+
!!this.params.targetsToUpdate?.includes('resource');
|
|
600
495
|
|
|
601
496
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
602
497
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
498
|
+
let bootResourcesInstallItem: ProtocolV2InstallItem | undefined;
|
|
603
499
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
604
500
|
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
605
501
|
try {
|
|
606
502
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
503
|
+
resourceBundles = this.prepareExplicitProtocolV2ResourceBundles();
|
|
607
504
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
608
505
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
619
|
-
}
|
|
620
|
-
);
|
|
621
|
-
}
|
|
506
|
+
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
507
|
+
const needsRemoteBootResources =
|
|
508
|
+
!this.params.bootResourcesBinary &&
|
|
509
|
+
!!this.params.targetsToUpdate?.includes('boot_resources');
|
|
510
|
+
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
511
|
+
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
512
|
+
await DataManager.forceReloadData();
|
|
513
|
+
}
|
|
514
|
+
if (needsRemoteFirmware) {
|
|
622
515
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
623
516
|
firmwareType,
|
|
624
517
|
deviceFeatures
|
|
@@ -627,308 +520,72 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
627
520
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
628
521
|
installItems = remoteBinaries.installItems;
|
|
629
522
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
523
|
+
bootResourcesInstallItem = await this.prepareProtocolV2BootResources();
|
|
524
|
+
if (bootResourcesInstallItem) {
|
|
525
|
+
installItems = [
|
|
526
|
+
bootResourcesInstallItem,
|
|
527
|
+
...(installItems ?? this.buildProtocolV2InstallItems({ bootloaderBinary, fwBinaryMap })),
|
|
528
|
+
];
|
|
529
|
+
}
|
|
530
|
+
if (!needsRemoteResources) {
|
|
531
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
532
|
+
}
|
|
638
533
|
} catch (err) {
|
|
639
|
-
|
|
534
|
+
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
535
|
+
throw err;
|
|
536
|
+
}
|
|
537
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
640
538
|
}
|
|
641
539
|
|
|
642
|
-
if (
|
|
540
|
+
if (
|
|
541
|
+
!bootloaderBinary &&
|
|
542
|
+
fwBinaryMap.length === 0 &&
|
|
543
|
+
!installItems?.length &&
|
|
544
|
+
!resourceBundles?.length &&
|
|
545
|
+
!needsRemoteResources
|
|
546
|
+
) {
|
|
643
547
|
throw ERRORS.TypedError(
|
|
644
548
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
645
549
|
'No firmware to update'
|
|
646
550
|
);
|
|
647
551
|
}
|
|
648
552
|
|
|
649
|
-
|
|
650
|
-
fwBinaryMap,
|
|
651
|
-
bootloaderBinary,
|
|
652
|
-
...(installItems ? { installItems } : undefined),
|
|
653
|
-
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
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
|
-
}
|
|
674
|
-
|
|
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
|
-
}
|
|
686
|
-
|
|
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
|
-
}
|
|
553
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
732
554
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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,
|
|
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
|
+
);
|
|
770
568
|
}
|
|
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
|
-
);
|
|
569
|
+
}
|
|
570
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
806
571
|
}
|
|
807
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
808
|
-
|
|
809
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
810
|
-
installSources,
|
|
811
|
-
resourceSources,
|
|
812
|
-
});
|
|
813
|
-
} finally {
|
|
814
|
-
await this.closeProtocolV2PreparedSources();
|
|
815
572
|
}
|
|
816
|
-
}
|
|
817
573
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
}
|
|
574
|
+
await this.executeProtocolV2Update({
|
|
575
|
+
fwBinaryMap,
|
|
576
|
+
bootloaderBinary,
|
|
577
|
+
...(installItems ? { installItems } : undefined),
|
|
578
|
+
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
579
|
+
});
|
|
843
580
|
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
}
|
|
581
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
582
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
857
583
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
}
|
|
584
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
585
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
586
|
+
DevicePool.resetState();
|
|
911
587
|
|
|
912
|
-
|
|
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);
|
|
588
|
+
return versions;
|
|
932
589
|
}
|
|
933
590
|
|
|
934
591
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -954,26 +611,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
954
611
|
);
|
|
955
612
|
}
|
|
956
613
|
|
|
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
|
+
|
|
957
620
|
private async requestProtocolV2PhysicalIdentity() {
|
|
958
621
|
return requestProtocolV2DeviceInfo({
|
|
959
622
|
commands: this.device.getCommands(),
|
|
960
|
-
timeoutMs:
|
|
623
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
961
624
|
});
|
|
962
625
|
}
|
|
963
626
|
|
|
964
627
|
private async captureProtocolV2PhysicalIdentity() {
|
|
965
628
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
966
629
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
967
|
-
|
|
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
|
-
}
|
|
630
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
977
631
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
978
632
|
}
|
|
979
633
|
|
|
@@ -991,6 +645,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
991
645
|
return (
|
|
992
646
|
!!this.params.resourceBundleFiles?.length ||
|
|
993
647
|
!!this.params.bootloaderBinary ||
|
|
648
|
+
!!this.params.bootResourcesBinary ||
|
|
994
649
|
fwBinaryMap.length > 0
|
|
995
650
|
);
|
|
996
651
|
}
|
|
@@ -1052,57 +707,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1052
707
|
return target;
|
|
1053
708
|
}
|
|
1054
709
|
|
|
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
|
-
|
|
1106
710
|
private async downloadRemoteProtocolV2Component(
|
|
1107
711
|
key: string,
|
|
1108
712
|
component: IProtocolV2FirmwareComponent
|
|
@@ -1191,155 +795,159 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1191
795
|
};
|
|
1192
796
|
}
|
|
1193
797
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
798
|
+
private validateProtocolV2BootResourcesBinary(
|
|
799
|
+
binary: ArrayBuffer,
|
|
800
|
+
resource?: IProtocolV2BootResources
|
|
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
|
+
}
|
|
1197
820
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
-
});
|
|
821
|
+
private async prepareProtocolV2BootResources(): Promise<ProtocolV2InstallItem | undefined> {
|
|
822
|
+
let binary = this.params.bootResourcesBinary;
|
|
823
|
+
let resource: IProtocolV2BootResources | undefined;
|
|
824
|
+
|
|
825
|
+
if (!binary) {
|
|
826
|
+
if (!this.params.targetsToUpdate?.includes('boot_resources')) {
|
|
827
|
+
return undefined;
|
|
828
|
+
}
|
|
829
|
+
resource = DataManager.getProtocolV2BootResources();
|
|
830
|
+
if (!resource) {
|
|
831
|
+
throw new Error('Missing Pro2 boot resources configuration');
|
|
832
|
+
}
|
|
833
|
+
Log.log('[FirmwareUpdateV4] downloading Pro2 boot resources CRATE');
|
|
834
|
+
({ binary } = await getSysResourceBinary(resource.url));
|
|
1223
835
|
}
|
|
1224
836
|
|
|
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
|
+
> {
|
|
1225
865
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
1226
866
|
return undefined;
|
|
1227
867
|
}
|
|
1228
868
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
}
|
|
869
|
+
const resources = DataManager.getProtocolV2Resources();
|
|
870
|
+
if (!resources?.length) {
|
|
871
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const inventory = this.params.forcedUpdateRes
|
|
875
|
+
? undefined
|
|
876
|
+
: await readProtocolV2ResourceInventory({
|
|
877
|
+
commands: this.device.getCommands(),
|
|
878
|
+
resources,
|
|
879
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
880
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
881
|
+
});
|
|
882
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
883
|
+
resources,
|
|
884
|
+
inventory,
|
|
885
|
+
mode: 'bootloader-recovery',
|
|
886
|
+
forced: this.params.forcedUpdateRes,
|
|
887
|
+
});
|
|
888
|
+
Log.log(
|
|
889
|
+
`[FirmwareUpdateV4] Pro2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
893
|
+
for (const resource of plan.resources) {
|
|
894
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
1301
895
|
}
|
|
1302
896
|
return bundles;
|
|
1303
897
|
}
|
|
1304
898
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
899
|
+
private async downloadProtocolV2Resource(
|
|
900
|
+
resource: IProtocolV2Resource
|
|
901
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
902
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
903
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
904
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
905
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
name: `${resource.type}.okpkg`,
|
|
909
|
+
binary,
|
|
910
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
911
|
+
};
|
|
912
|
+
}
|
|
1316
913
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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();
|
|
1320
920
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
921
|
+
// Write directly to the device through FileWrite.
|
|
922
|
+
let totalSize = 0;
|
|
923
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
924
|
+
|
|
925
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
926
|
+
let processedSize = 0;
|
|
927
|
+
for (const bundle of bundles) {
|
|
928
|
+
Log.log(
|
|
929
|
+
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
930
|
+
);
|
|
931
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
932
|
+
payload: bundle.binary,
|
|
933
|
+
filePath: bundle.devicePath,
|
|
934
|
+
processedSize,
|
|
935
|
+
totalSize: transferTotalSize,
|
|
936
|
+
});
|
|
1337
937
|
}
|
|
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 };
|
|
1338
946
|
}
|
|
1339
947
|
|
|
1340
948
|
private isProtocolV2BootloaderMode() {
|
|
1341
949
|
if (typeof this.device.isBootloader === 'function') {
|
|
1342
|
-
return this.device.isBootloader();
|
|
950
|
+
return !!this.device.isBootloader();
|
|
1343
951
|
}
|
|
1344
952
|
return (
|
|
1345
953
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -1362,12 +970,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1362
970
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1363
971
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1364
972
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1365
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1366
973
|
return false;
|
|
1367
974
|
}
|
|
1368
975
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1369
976
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1370
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1371
977
|
return false;
|
|
1372
978
|
}
|
|
1373
979
|
|
|
@@ -1377,7 +983,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1377
983
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1378
984
|
await wait(1000);
|
|
1379
985
|
await this.waitForProtocolV2BootloaderMode();
|
|
1380
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1381
986
|
return true;
|
|
1382
987
|
} catch (error) {
|
|
1383
988
|
if (error instanceof HardwareError) {
|
|
@@ -1394,13 +999,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1394
999
|
) {
|
|
1395
1000
|
const startTime = Date.now();
|
|
1396
1001
|
let lastError: unknown;
|
|
1002
|
+
let shouldReconnect = true;
|
|
1397
1003
|
|
|
1398
1004
|
while (Date.now() - startTime < timeout) {
|
|
1399
1005
|
try {
|
|
1400
|
-
|
|
1006
|
+
if (shouldReconnect) {
|
|
1007
|
+
await this.reconnectProtocolV2Device();
|
|
1008
|
+
shouldReconnect = false;
|
|
1009
|
+
}
|
|
1401
1010
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1402
1011
|
commands: this.device.getCommands(),
|
|
1403
|
-
timeoutMs:
|
|
1012
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1404
1013
|
});
|
|
1405
1014
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1406
1015
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1415,6 +1024,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1415
1024
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1416
1025
|
throw error;
|
|
1417
1026
|
}
|
|
1027
|
+
shouldReconnect = true;
|
|
1418
1028
|
lastError = error;
|
|
1419
1029
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
1420
1030
|
}
|
|
@@ -1467,98 +1077,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1467
1077
|
return entries;
|
|
1468
1078
|
}
|
|
1469
1079
|
|
|
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
|
-
|
|
1562
1080
|
private async executeProtocolV2Update({
|
|
1563
1081
|
fwBinaryMap,
|
|
1564
1082
|
bootloaderBinary,
|
|
@@ -1570,158 +1088,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1570
1088
|
installItems?: ProtocolV2InstallItem[];
|
|
1571
1089
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1572
1090
|
}) {
|
|
1573
|
-
const
|
|
1091
|
+
const orderedInstallItems =
|
|
1574
1092
|
installItems ??
|
|
1575
1093
|
this.buildProtocolV2InstallItems({
|
|
1576
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1577
1094
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1095
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1578
1096
|
});
|
|
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
|
-
}))
|
|
1597
|
-
);
|
|
1598
|
-
return await this.executeProtocolV2Phases({
|
|
1599
|
-
installSources,
|
|
1600
|
-
resourceSources,
|
|
1601
|
-
});
|
|
1602
|
-
} finally {
|
|
1603
|
-
await this.closeProtocolV2PreparedSources();
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
1097
|
|
|
1607
|
-
|
|
1608
|
-
|
|
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;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1098
|
+
let firmwareSize = 0;
|
|
1099
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1621
1100
|
|
|
1622
1101
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1102
|
+
|
|
1623
1103
|
let processedSize = 0;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1104
|
+
let totalSize = firmwareSize;
|
|
1105
|
+
if (resourceBundles?.length) {
|
|
1106
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1107
|
+
resourceBundles,
|
|
1108
|
+
firmwareSize
|
|
1109
|
+
);
|
|
1110
|
+
processedSize = resourceTransfer.processedSize;
|
|
1111
|
+
totalSize = resourceTransfer.totalSize;
|
|
1632
1112
|
}
|
|
1633
1113
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
totalSize,
|
|
1642
|
-
});
|
|
1643
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1644
|
-
stagedInstallTargets.push({
|
|
1645
|
-
targetId: item.targetId,
|
|
1646
|
-
path: filePath,
|
|
1647
|
-
});
|
|
1114
|
+
// Skip staging and installation when the update contains RESC bundles only.
|
|
1115
|
+
if (orderedInstallItems.length === 0) {
|
|
1116
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
|
|
1117
|
+
if (totalSize > 0) {
|
|
1118
|
+
this.postProgressMessage(100, 'transferData');
|
|
1119
|
+
}
|
|
1120
|
+
return;
|
|
1648
1121
|
}
|
|
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
|
+
);
|
|
1649
1132
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
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
|
+
|
|
1151
|
+
stagedInstallTargets.push({
|
|
1152
|
+
...item,
|
|
1153
|
+
path: filePath,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
if (totalSize > 0) {
|
|
1158
|
+
this.postProgressMessage(100, 'transferData');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1162
|
+
Log.log(
|
|
1163
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1164
|
+
elapsedMs / 1000
|
|
1165
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
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;
|
|
1655
1175
|
}
|
|
1656
1176
|
|
|
1657
1177
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1658
|
-
|
|
1178
|
+
|
|
1179
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1659
1180
|
target_id: item.targetId,
|
|
1660
1181
|
path: item.path,
|
|
1661
1182
|
}));
|
|
1662
|
-
|
|
1663
|
-
await this.
|
|
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
|
-
);
|
|
1183
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1184
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1185
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1722
1186
|
}
|
|
1723
1187
|
|
|
1724
|
-
private getProtocolV2InstallItemStagingPath(item:
|
|
1188
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1725
1189
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1726
1190
|
}
|
|
1727
1191
|
|
|
@@ -1740,8 +1204,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1740
1204
|
|
|
1741
1205
|
private assertProtocolV2TargetStatus(
|
|
1742
1206
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1743
|
-
expectedTargetIds: Set<number
|
|
1744
|
-
expectedPaths = new Map<number, string>()
|
|
1207
|
+
expectedTargetIds: Set<number>
|
|
1745
1208
|
) {
|
|
1746
1209
|
Log.log(
|
|
1747
1210
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1772,54 +1235,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1772
1235
|
const matchingTargets = statusTargets.filter(target =>
|
|
1773
1236
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1774
1237
|
);
|
|
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
|
-
);
|
|
1793
1238
|
const completedTargetIds = new Set<number>();
|
|
1794
|
-
|
|
1239
|
+
matchingTargets.forEach(target => {
|
|
1795
1240
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1796
|
-
if (targetId !== undefined) {
|
|
1241
|
+
if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
|
|
1797
1242
|
completedTargetIds.add(targetId);
|
|
1798
1243
|
}
|
|
1799
1244
|
});
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
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
|
-
}
|
|
1245
|
+
const allExpectedTargetsCompleted =
|
|
1246
|
+
expectedTargetIds.size > 0 &&
|
|
1247
|
+
Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
|
|
1248
|
+
if (allExpectedTargetsCompleted) {
|
|
1823
1249
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1824
1250
|
return true;
|
|
1825
1251
|
}
|
|
@@ -1840,19 +1266,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1840
1266
|
return false;
|
|
1841
1267
|
}
|
|
1842
1268
|
|
|
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
|
+
|
|
1843
1283
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1844
1284
|
targets: Array<{ target_id: number; path: string }>
|
|
1845
1285
|
) {
|
|
1846
|
-
this.protocolV2FinalStatusVerified = false;
|
|
1847
1286
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1848
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1849
1287
|
const startTime = Date.now();
|
|
1850
1288
|
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
|
+
};
|
|
1851
1297
|
|
|
1852
1298
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1853
1299
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
1300
|
+
if (shouldReconnect) {
|
|
1301
|
+
await this.reconnectProtocolV2Device();
|
|
1302
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1303
|
+
shouldReconnect = false;
|
|
1304
|
+
}
|
|
1305
|
+
const currentDeviceInfo = deviceInfo;
|
|
1856
1306
|
try {
|
|
1857
1307
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1858
1308
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1864,19 +1314,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1864
1314
|
path: true,
|
|
1865
1315
|
},
|
|
1866
1316
|
},
|
|
1867
|
-
{ timeoutMs:
|
|
1317
|
+
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
1868
1318
|
);
|
|
1319
|
+
const statusTargets = (statusResponse.message.records ??
|
|
1320
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1321
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1869
1325
|
if (
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
expectedPaths
|
|
1874
|
-
)
|
|
1326
|
+
statusTargets.length === 0 &&
|
|
1327
|
+
currentDeviceInfo &&
|
|
1328
|
+
(await this.probeProtocolV2NormalMode(currentDeviceInfo))
|
|
1875
1329
|
) {
|
|
1876
|
-
|
|
1330
|
+
Log.log(
|
|
1331
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
1332
|
+
);
|
|
1333
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1877
1334
|
return;
|
|
1878
1335
|
}
|
|
1879
|
-
|
|
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
|
+
}
|
|
1880
1368
|
} catch (error) {
|
|
1881
1369
|
if (
|
|
1882
1370
|
error instanceof HardwareError &&
|
|
@@ -1884,19 +1372,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1884
1372
|
) {
|
|
1885
1373
|
throw error;
|
|
1886
1374
|
}
|
|
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();
|
|
1887
1378
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
1888
1379
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
1889
1380
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1890
|
-
if (
|
|
1381
|
+
if (!currentDeviceInfo) {
|
|
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)) {
|
|
1891
1388
|
Log.log(
|
|
1892
1389
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
1893
1390
|
);
|
|
1391
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1894
1392
|
return;
|
|
1895
1393
|
}
|
|
1896
1394
|
lastError = new Error(
|
|
1897
1395
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
1898
1396
|
);
|
|
1899
1397
|
} else {
|
|
1398
|
+
shouldReconnect = true;
|
|
1399
|
+
deviceInfo = undefined;
|
|
1900
1400
|
lastError = error;
|
|
1901
1401
|
Log.log(
|
|
1902
1402
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -1912,12 +1412,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1912
1412
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1913
1413
|
throw error;
|
|
1914
1414
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
) {
|
|
1919
|
-
throw error;
|
|
1920
|
-
}
|
|
1415
|
+
shouldReconnect = true;
|
|
1416
|
+
deviceInfo = undefined;
|
|
1417
|
+
resetMissingTargetStatusGrace();
|
|
1921
1418
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1922
1419
|
}
|
|
1923
1420
|
await wait(1000);
|
|
@@ -1925,17 +1422,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1925
1422
|
|
|
1926
1423
|
throw ERRORS.TypedError(
|
|
1927
1424
|
HardwareErrorCode.RuntimeError,
|
|
1928
|
-
`Protocol V2 firmware update status
|
|
1425
|
+
`Protocol V2 firmware update status not complete within ${
|
|
1426
|
+
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
1427
|
+
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1929
1428
|
);
|
|
1930
1429
|
}
|
|
1931
1430
|
|
|
1932
1431
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1933
1432
|
await this.reconnectProtocolV2Device();
|
|
1934
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
1935
|
-
|
|
1936
|
-
|
|
1433
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1434
|
+
try {
|
|
1435
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
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
|
+
}
|
|
1937
1442
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1938
|
-
this.protocolV2ExecutionInLoader = false;
|
|
1939
1443
|
}
|
|
1940
1444
|
|
|
1941
1445
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1951,10 +1455,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1951
1455
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1952
1456
|
);
|
|
1953
1457
|
|
|
1458
|
+
return this.getProtocolV2VersionResult(features);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1954
1462
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1955
1463
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1956
1464
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1957
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
1958
1465
|
if (firmwareVersion === '0.0.0') {
|
|
1959
1466
|
Log.warn(
|
|
1960
1467
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1968,27 +1475,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1968
1475
|
};
|
|
1969
1476
|
}
|
|
1970
1477
|
|
|
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
|
-
|
|
1982
1478
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1983
1479
|
const startTime = Date.now();
|
|
1984
1480
|
let lastError: unknown;
|
|
1481
|
+
let shouldReconnect = true;
|
|
1985
1482
|
|
|
1986
1483
|
while (Date.now() - startTime < timeout) {
|
|
1987
1484
|
try {
|
|
1988
|
-
|
|
1485
|
+
if (shouldReconnect) {
|
|
1486
|
+
await this.reconnectProtocolV2Device();
|
|
1487
|
+
shouldReconnect = false;
|
|
1488
|
+
}
|
|
1989
1489
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1990
1490
|
commands: this.device.getCommands(),
|
|
1991
|
-
timeoutMs:
|
|
1491
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1992
1492
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
1993
1493
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1994
1494
|
});
|
|
@@ -1997,21 +1497,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1997
1497
|
deviceInfo,
|
|
1998
1498
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1999
1499
|
);
|
|
2000
|
-
if (features.mode
|
|
2001
|
-
|
|
2002
|
-
HardwareErrorCode.DeviceNotFound,
|
|
2003
|
-
'Protocol V2 device is still in bootloader mode'
|
|
2004
|
-
);
|
|
1500
|
+
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
1501
|
+
return features;
|
|
2005
1502
|
}
|
|
2006
|
-
|
|
1503
|
+
lastError = ERRORS.TypedError(
|
|
1504
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1505
|
+
'Protocol V2 device is still in bootloader mode'
|
|
1506
|
+
);
|
|
2007
1507
|
} catch (error) {
|
|
2008
1508
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2009
1509
|
throw error;
|
|
2010
1510
|
}
|
|
1511
|
+
shouldReconnect = true;
|
|
2011
1512
|
lastError = error;
|
|
2012
1513
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
2013
|
-
await wait(1000);
|
|
2014
1514
|
}
|
|
1515
|
+
await wait(1000);
|
|
2015
1516
|
}
|
|
2016
1517
|
|
|
2017
1518
|
throw ERRORS.TypedError(
|
|
@@ -2048,20 +1549,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2048
1549
|
return;
|
|
2049
1550
|
}
|
|
2050
1551
|
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
1552
|
+
// Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
|
|
1553
|
+
// Enumeration order is not stable and must never decide the reconnect target.
|
|
2053
1554
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
2054
1555
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2055
1556
|
});
|
|
2056
|
-
|
|
1557
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
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) {
|
|
2057
1571
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2058
1572
|
}
|
|
2059
1573
|
|
|
2060
1574
|
Log.debug(
|
|
2061
|
-
'Protocol V2 firmware reconnect using
|
|
2062
|
-
|
|
1575
|
+
'Protocol V2 firmware reconnect using matched device:',
|
|
1576
|
+
reconnectDevice.getConnectId()
|
|
2063
1577
|
);
|
|
2064
|
-
this.device.updateFromCache(
|
|
1578
|
+
this.device.updateFromCache(reconnectDevice);
|
|
2065
1579
|
await this.ensureProtocolV2DeviceAcquired();
|
|
2066
1580
|
this.device.commands.disposed = false;
|
|
2067
1581
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -2085,6 +1599,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2085
1599
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
2086
1600
|
}
|
|
2087
1601
|
|
|
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
|
+
|
|
2088
1692
|
private async fileWriteChunk(
|
|
2089
1693
|
filePath: string,
|
|
2090
1694
|
totalFileSize: number,
|
|
@@ -2094,17 +1698,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2094
1698
|
progress: number | null
|
|
2095
1699
|
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
2096
1700
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2097
|
-
const writeRes = await typedCall(
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
1701
|
+
const writeRes = await typedCall(
|
|
1702
|
+
'FilesystemFileWrite',
|
|
1703
|
+
'FilesystemFile',
|
|
1704
|
+
{
|
|
1705
|
+
file: {
|
|
1706
|
+
path: filePath,
|
|
1707
|
+
offset,
|
|
1708
|
+
total_size: totalFileSize,
|
|
1709
|
+
data: chunk,
|
|
1710
|
+
},
|
|
1711
|
+
overwrite,
|
|
1712
|
+
append: false,
|
|
1713
|
+
ui_percentage: progress ?? undefined,
|
|
2103
1714
|
},
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
ui_percentage: progress ?? undefined,
|
|
2107
|
-
});
|
|
1715
|
+
{ writeWithResponse: true }
|
|
1716
|
+
);
|
|
2108
1717
|
if (writeRes.type !== 'FilesystemFile') {
|
|
2109
1718
|
if ((writeRes as any).type === 'CallMethodError') {
|
|
2110
1719
|
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|