@onekeyfe/hd-core 1.2.0-alpha.52 → 1.2.0-alpha.53
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 +78 -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__/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__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +446 -33
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +284 -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 +1349 -1005
- 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/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 +2559 -3497
- 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.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 +5 -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 +566 -965
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +54 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -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 +11 -4
- package/src/api/index.ts +2 -0
- 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 +67 -38
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +258 -42
- 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 -3
- 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 +180 -30
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +2 -4
- package/src/types/api/detectDeviceConnectProtocol.ts +7 -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,69 @@ 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
|
-
throw
|
|
534
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
640
535
|
}
|
|
641
536
|
|
|
642
|
-
if (
|
|
537
|
+
if (
|
|
538
|
+
!bootloaderBinary &&
|
|
539
|
+
fwBinaryMap.length === 0 &&
|
|
540
|
+
!installItems?.length &&
|
|
541
|
+
!resourceBundles?.length &&
|
|
542
|
+
!needsRemoteResources
|
|
543
|
+
) {
|
|
643
544
|
throw ERRORS.TypedError(
|
|
644
545
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
645
546
|
'No firmware to update'
|
|
646
547
|
);
|
|
647
548
|
}
|
|
648
549
|
|
|
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
|
-
}
|
|
550
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
732
551
|
|
|
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,
|
|
552
|
+
if (needsRemoteResources) {
|
|
553
|
+
try {
|
|
554
|
+
resourceBundles = await this.prepareProtocolV2ResourceBundles();
|
|
555
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
if (enteredBootloader) {
|
|
558
|
+
try {
|
|
559
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
560
|
+
} catch (restoreError) {
|
|
561
|
+
Log.warn(
|
|
562
|
+
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
563
|
+
restoreError
|
|
564
|
+
);
|
|
770
565
|
}
|
|
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
|
-
);
|
|
566
|
+
}
|
|
567
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
806
568
|
}
|
|
807
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
808
|
-
|
|
809
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
810
|
-
installSources,
|
|
811
|
-
resourceSources,
|
|
812
|
-
});
|
|
813
|
-
} finally {
|
|
814
|
-
await this.closeProtocolV2PreparedSources();
|
|
815
569
|
}
|
|
816
|
-
}
|
|
817
570
|
|
|
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
|
-
}
|
|
571
|
+
await this.executeProtocolV2Update({
|
|
572
|
+
fwBinaryMap,
|
|
573
|
+
bootloaderBinary,
|
|
574
|
+
...(installItems ? { installItems } : undefined),
|
|
575
|
+
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
576
|
+
});
|
|
843
577
|
|
|
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
|
-
}
|
|
578
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
579
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
857
580
|
|
|
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
|
-
}
|
|
581
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
582
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
583
|
+
DevicePool.resetState();
|
|
911
584
|
|
|
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);
|
|
585
|
+
return versions;
|
|
932
586
|
}
|
|
933
587
|
|
|
934
588
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -954,26 +608,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
954
608
|
);
|
|
955
609
|
}
|
|
956
610
|
|
|
611
|
+
private getProtocolV2DeviceInfoTimeout() {
|
|
612
|
+
return this.isBleReconnect()
|
|
613
|
+
? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
|
|
614
|
+
: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
|
|
615
|
+
}
|
|
616
|
+
|
|
957
617
|
private async requestProtocolV2PhysicalIdentity() {
|
|
958
618
|
return requestProtocolV2DeviceInfo({
|
|
959
619
|
commands: this.device.getCommands(),
|
|
960
|
-
timeoutMs:
|
|
620
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
961
621
|
});
|
|
962
622
|
}
|
|
963
623
|
|
|
964
624
|
private async captureProtocolV2PhysicalIdentity() {
|
|
965
625
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
966
626
|
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
|
-
}
|
|
627
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
977
628
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
978
629
|
}
|
|
979
630
|
|
|
@@ -991,6 +642,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
991
642
|
return (
|
|
992
643
|
!!this.params.resourceBundleFiles?.length ||
|
|
993
644
|
!!this.params.bootloaderBinary ||
|
|
645
|
+
!!this.params.bootResourcesBinary ||
|
|
994
646
|
fwBinaryMap.length > 0
|
|
995
647
|
);
|
|
996
648
|
}
|
|
@@ -1052,57 +704,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1052
704
|
return target;
|
|
1053
705
|
}
|
|
1054
706
|
|
|
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
707
|
private async downloadRemoteProtocolV2Component(
|
|
1107
708
|
key: string,
|
|
1108
709
|
component: IProtocolV2FirmwareComponent
|
|
@@ -1191,155 +792,159 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1191
792
|
};
|
|
1192
793
|
}
|
|
1193
794
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
795
|
+
private validateProtocolV2BootResourcesBinary(
|
|
796
|
+
binary: ArrayBuffer,
|
|
797
|
+
resource?: IProtocolV2BootResources
|
|
798
|
+
) {
|
|
799
|
+
if (resource && !isProtocolV2ResourceFileValid(binary, resource)) {
|
|
800
|
+
throw new Error('Pro2 boot resources file verification failed');
|
|
801
|
+
}
|
|
802
|
+
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
803
|
+
if (!header || header.type !== 'CRAT') {
|
|
804
|
+
throw new Error('Invalid Pro2 boot resources CRATE header');
|
|
805
|
+
}
|
|
806
|
+
if (resource) {
|
|
807
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(resource.payloadHash);
|
|
808
|
+
const expectedHeaderHash = normalizeProtocolV2Hex(resource.headerHash);
|
|
809
|
+
if (header.payloadHash !== expectedPayloadHash) {
|
|
810
|
+
throw new Error('Pro2 boot resources payload hash mismatch');
|
|
811
|
+
}
|
|
812
|
+
if (header.headerHash !== expectedHeaderHash) {
|
|
813
|
+
throw new Error('Pro2 boot resources header hash mismatch');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
1197
817
|
|
|
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
|
-
});
|
|
818
|
+
private async prepareProtocolV2BootResources(): Promise<ProtocolV2InstallItem | undefined> {
|
|
819
|
+
let binary = this.params.bootResourcesBinary;
|
|
820
|
+
let resource: IProtocolV2BootResources | undefined;
|
|
821
|
+
|
|
822
|
+
if (!binary) {
|
|
823
|
+
if (!this.params.targetsToUpdate?.includes('boot_resources')) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
resource = DataManager.getProtocolV2BootResources();
|
|
827
|
+
if (!resource) {
|
|
828
|
+
throw new Error('Missing Pro2 boot resources configuration');
|
|
829
|
+
}
|
|
830
|
+
Log.log('[FirmwareUpdateV4] downloading Pro2 boot resources CRATE');
|
|
831
|
+
({ binary } = await getSysResourceBinary(resource.url));
|
|
1223
832
|
}
|
|
1224
833
|
|
|
834
|
+
this.validateProtocolV2BootResourcesBinary(binary, resource);
|
|
835
|
+
return {
|
|
836
|
+
fileName: PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME,
|
|
837
|
+
binary,
|
|
838
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
|
|
839
|
+
kind: 'boot_resources',
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private prepareExplicitProtocolV2ResourceBundles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
844
|
+
if (!this.params.resourceBundleFiles?.length) return undefined;
|
|
845
|
+
|
|
846
|
+
return this.params.resourceBundleFiles.map((file, index) => {
|
|
847
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
848
|
+
file.devicePath,
|
|
849
|
+
`resourceBundleFiles[${index}].devicePath`
|
|
850
|
+
);
|
|
851
|
+
return {
|
|
852
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
853
|
+
binary: file.binary,
|
|
854
|
+
devicePath,
|
|
855
|
+
};
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
860
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
861
|
+
> {
|
|
1225
862
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
1226
863
|
return undefined;
|
|
1227
864
|
}
|
|
1228
865
|
|
|
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
|
-
}
|
|
866
|
+
const resources = DataManager.getProtocolV2Resources();
|
|
867
|
+
if (!resources?.length) {
|
|
868
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const inventory = this.params.forcedUpdateRes
|
|
872
|
+
? undefined
|
|
873
|
+
: await readProtocolV2ResourceInventory({
|
|
874
|
+
commands: this.device.getCommands(),
|
|
875
|
+
resources,
|
|
876
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
877
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
878
|
+
});
|
|
879
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
880
|
+
resources,
|
|
881
|
+
inventory,
|
|
882
|
+
mode: 'bootloader-recovery',
|
|
883
|
+
forced: this.params.forcedUpdateRes,
|
|
884
|
+
});
|
|
885
|
+
Log.log(
|
|
886
|
+
`[FirmwareUpdateV4] Pro2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
890
|
+
for (const resource of plan.resources) {
|
|
891
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
1301
892
|
}
|
|
1302
893
|
return bundles;
|
|
1303
894
|
}
|
|
1304
895
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
896
|
+
private async downloadProtocolV2Resource(
|
|
897
|
+
resource: IProtocolV2Resource
|
|
898
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
899
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
900
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
901
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
902
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
name: `${resource.type}.okpkg`,
|
|
906
|
+
binary,
|
|
907
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
908
|
+
};
|
|
909
|
+
}
|
|
1316
910
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
911
|
+
private async syncProtocolV2ResourceBundles(
|
|
912
|
+
bundles: ProtocolV2ResourceBundleBinary[],
|
|
913
|
+
firmwareSize: number
|
|
914
|
+
): Promise<{ processedSize: number; totalSize: number }> {
|
|
915
|
+
const transferStartTime = Date.now();
|
|
916
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1320
917
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
918
|
+
// Write directly to the device through FileWrite.
|
|
919
|
+
let totalSize = 0;
|
|
920
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
921
|
+
|
|
922
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
923
|
+
let processedSize = 0;
|
|
924
|
+
for (const bundle of bundles) {
|
|
925
|
+
Log.log(
|
|
926
|
+
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
927
|
+
);
|
|
928
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
929
|
+
payload: bundle.binary,
|
|
930
|
+
filePath: bundle.devicePath,
|
|
931
|
+
processedSize,
|
|
932
|
+
totalSize: transferTotalSize,
|
|
933
|
+
});
|
|
1337
934
|
}
|
|
935
|
+
|
|
936
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
937
|
+
Log.log(
|
|
938
|
+
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
939
|
+
elapsedMs / 1000
|
|
940
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
941
|
+
);
|
|
942
|
+
return { processedSize, totalSize: transferTotalSize };
|
|
1338
943
|
}
|
|
1339
944
|
|
|
1340
945
|
private isProtocolV2BootloaderMode() {
|
|
1341
946
|
if (typeof this.device.isBootloader === 'function') {
|
|
1342
|
-
return this.device.isBootloader();
|
|
947
|
+
return !!this.device.isBootloader();
|
|
1343
948
|
}
|
|
1344
949
|
return (
|
|
1345
950
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -1362,12 +967,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1362
967
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1363
968
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1364
969
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1365
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1366
970
|
return false;
|
|
1367
971
|
}
|
|
1368
972
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1369
973
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1370
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1371
974
|
return false;
|
|
1372
975
|
}
|
|
1373
976
|
|
|
@@ -1377,7 +980,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1377
980
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1378
981
|
await wait(1000);
|
|
1379
982
|
await this.waitForProtocolV2BootloaderMode();
|
|
1380
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1381
983
|
return true;
|
|
1382
984
|
} catch (error) {
|
|
1383
985
|
if (error instanceof HardwareError) {
|
|
@@ -1394,13 +996,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1394
996
|
) {
|
|
1395
997
|
const startTime = Date.now();
|
|
1396
998
|
let lastError: unknown;
|
|
999
|
+
let shouldReconnect = true;
|
|
1397
1000
|
|
|
1398
1001
|
while (Date.now() - startTime < timeout) {
|
|
1399
1002
|
try {
|
|
1400
|
-
|
|
1003
|
+
if (shouldReconnect) {
|
|
1004
|
+
await this.reconnectProtocolV2Device();
|
|
1005
|
+
shouldReconnect = false;
|
|
1006
|
+
}
|
|
1401
1007
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1402
1008
|
commands: this.device.getCommands(),
|
|
1403
|
-
timeoutMs:
|
|
1009
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1404
1010
|
});
|
|
1405
1011
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1406
1012
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1415,6 +1021,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1415
1021
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1416
1022
|
throw error;
|
|
1417
1023
|
}
|
|
1024
|
+
shouldReconnect = true;
|
|
1418
1025
|
lastError = error;
|
|
1419
1026
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
1420
1027
|
}
|
|
@@ -1467,98 +1074,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1467
1074
|
return entries;
|
|
1468
1075
|
}
|
|
1469
1076
|
|
|
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
1077
|
private async executeProtocolV2Update({
|
|
1563
1078
|
fwBinaryMap,
|
|
1564
1079
|
bootloaderBinary,
|
|
@@ -1570,158 +1085,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1570
1085
|
installItems?: ProtocolV2InstallItem[];
|
|
1571
1086
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1572
1087
|
}) {
|
|
1573
|
-
const
|
|
1088
|
+
const orderedInstallItems =
|
|
1574
1089
|
installItems ??
|
|
1575
1090
|
this.buildProtocolV2InstallItems({
|
|
1576
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1577
1091
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1092
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1578
1093
|
});
|
|
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
1094
|
|
|
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
|
-
}
|
|
1095
|
+
let firmwareSize = 0;
|
|
1096
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1621
1097
|
|
|
1622
1098
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1099
|
+
|
|
1623
1100
|
let processedSize = 0;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1101
|
+
let totalSize = firmwareSize;
|
|
1102
|
+
if (resourceBundles?.length) {
|
|
1103
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1104
|
+
resourceBundles,
|
|
1105
|
+
firmwareSize
|
|
1106
|
+
);
|
|
1107
|
+
processedSize = resourceTransfer.processedSize;
|
|
1108
|
+
totalSize = resourceTransfer.totalSize;
|
|
1632
1109
|
}
|
|
1633
1110
|
|
|
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
|
-
});
|
|
1111
|
+
// Skip staging and installation when the update contains RESC bundles only.
|
|
1112
|
+
if (orderedInstallItems.length === 0) {
|
|
1113
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
|
|
1114
|
+
if (totalSize > 0) {
|
|
1115
|
+
this.postProgressMessage(100, 'transferData');
|
|
1116
|
+
}
|
|
1117
|
+
return;
|
|
1648
1118
|
}
|
|
1119
|
+
let transferredSize = processedSize;
|
|
1120
|
+
const transferStartTime = Date.now();
|
|
1121
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1122
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1123
|
+
const onTransferredBytes = (bytes: number) => {
|
|
1124
|
+
transferredSize = bytes;
|
|
1125
|
+
};
|
|
1126
|
+
Log.log(
|
|
1127
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1128
|
+
);
|
|
1649
1129
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1130
|
+
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1131
|
+
|
|
1132
|
+
try {
|
|
1133
|
+
for (const item of orderedInstallItems) {
|
|
1134
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1135
|
+
Log.log(
|
|
1136
|
+
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1137
|
+
);
|
|
1138
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1139
|
+
payload: item.binary,
|
|
1140
|
+
filePath,
|
|
1141
|
+
processedSize,
|
|
1142
|
+
totalSize,
|
|
1143
|
+
onTransferredBytes,
|
|
1144
|
+
});
|
|
1145
|
+
transferredSize = processedSize;
|
|
1146
|
+
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1147
|
+
|
|
1148
|
+
stagedInstallTargets.push({
|
|
1149
|
+
...item,
|
|
1150
|
+
path: filePath,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (totalSize > 0) {
|
|
1155
|
+
this.postProgressMessage(100, 'transferData');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1159
|
+
Log.log(
|
|
1160
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1161
|
+
elapsedMs / 1000
|
|
1162
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1163
|
+
);
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1166
|
+
Log.warn(
|
|
1167
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1168
|
+
elapsedMs / 1000
|
|
1169
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1170
|
+
);
|
|
1171
|
+
throw error;
|
|
1655
1172
|
}
|
|
1656
1173
|
|
|
1657
1174
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1658
|
-
|
|
1175
|
+
|
|
1176
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1659
1177
|
target_id: item.targetId,
|
|
1660
1178
|
path: item.path,
|
|
1661
1179
|
}));
|
|
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
|
-
);
|
|
1180
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1181
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1182
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1722
1183
|
}
|
|
1723
1184
|
|
|
1724
|
-
private getProtocolV2InstallItemStagingPath(item:
|
|
1185
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1725
1186
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1726
1187
|
}
|
|
1727
1188
|
|
|
@@ -1740,8 +1201,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1740
1201
|
|
|
1741
1202
|
private assertProtocolV2TargetStatus(
|
|
1742
1203
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1743
|
-
expectedTargetIds: Set<number
|
|
1744
|
-
expectedPaths = new Map<number, string>()
|
|
1204
|
+
expectedTargetIds: Set<number>
|
|
1745
1205
|
) {
|
|
1746
1206
|
Log.log(
|
|
1747
1207
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1772,54 +1232,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1772
1232
|
const matchingTargets = statusTargets.filter(target =>
|
|
1773
1233
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1774
1234
|
);
|
|
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
1235
|
const completedTargetIds = new Set<number>();
|
|
1794
|
-
|
|
1236
|
+
matchingTargets.forEach(target => {
|
|
1795
1237
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1796
|
-
if (targetId !== undefined) {
|
|
1238
|
+
if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
|
|
1797
1239
|
completedTargetIds.add(targetId);
|
|
1798
1240
|
}
|
|
1799
1241
|
});
|
|
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
|
-
}
|
|
1242
|
+
const allExpectedTargetsCompleted =
|
|
1243
|
+
expectedTargetIds.size > 0 &&
|
|
1244
|
+
Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
|
|
1245
|
+
if (allExpectedTargetsCompleted) {
|
|
1823
1246
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1824
1247
|
return true;
|
|
1825
1248
|
}
|
|
@@ -1840,19 +1263,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1840
1263
|
return false;
|
|
1841
1264
|
}
|
|
1842
1265
|
|
|
1266
|
+
private getProtocolV2MissingTargetIds(
|
|
1267
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1268
|
+
expectedTargetIds: Set<number>
|
|
1269
|
+
) {
|
|
1270
|
+
const reportedTargetIds = new Set<number>();
|
|
1271
|
+
statusTargets.forEach(target => {
|
|
1272
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1273
|
+
if (targetId !== undefined) {
|
|
1274
|
+
reportedTargetIds.add(targetId);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1843
1280
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1844
1281
|
targets: Array<{ target_id: number; path: string }>
|
|
1845
1282
|
) {
|
|
1846
|
-
this.protocolV2FinalStatusVerified = false;
|
|
1847
1283
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1848
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1849
1284
|
const startTime = Date.now();
|
|
1850
1285
|
let lastError: unknown;
|
|
1286
|
+
let shouldReconnect = true;
|
|
1287
|
+
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
1288
|
+
let missingTargetStatusSince: number | undefined;
|
|
1289
|
+
let missingTargetStatusKey: string | undefined;
|
|
1290
|
+
const resetMissingTargetStatusGrace = () => {
|
|
1291
|
+
missingTargetStatusSince = undefined;
|
|
1292
|
+
missingTargetStatusKey = undefined;
|
|
1293
|
+
};
|
|
1851
1294
|
|
|
1852
1295
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1853
1296
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
1297
|
+
if (shouldReconnect) {
|
|
1298
|
+
await this.reconnectProtocolV2Device();
|
|
1299
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1300
|
+
shouldReconnect = false;
|
|
1301
|
+
}
|
|
1302
|
+
const currentDeviceInfo = deviceInfo;
|
|
1856
1303
|
try {
|
|
1857
1304
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1858
1305
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1864,19 +1311,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1864
1311
|
path: true,
|
|
1865
1312
|
},
|
|
1866
1313
|
},
|
|
1867
|
-
{ timeoutMs:
|
|
1314
|
+
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
1868
1315
|
);
|
|
1316
|
+
const statusTargets = (statusResponse.message.records ??
|
|
1317
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1318
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1869
1322
|
if (
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
expectedPaths
|
|
1874
|
-
)
|
|
1323
|
+
statusTargets.length === 0 &&
|
|
1324
|
+
currentDeviceInfo &&
|
|
1325
|
+
(await this.probeProtocolV2NormalMode(currentDeviceInfo))
|
|
1875
1326
|
) {
|
|
1876
|
-
|
|
1327
|
+
Log.log(
|
|
1328
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
1329
|
+
);
|
|
1330
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1877
1331
|
return;
|
|
1878
1332
|
}
|
|
1879
|
-
|
|
1333
|
+
|
|
1334
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
1335
|
+
statusTargets,
|
|
1336
|
+
expectedTargetIds
|
|
1337
|
+
);
|
|
1338
|
+
if (missingTargetIds.length > 0) {
|
|
1339
|
+
const now = Date.now();
|
|
1340
|
+
const missingKey = missingTargetIds.join(',');
|
|
1341
|
+
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
1342
|
+
missingTargetStatusSince = now;
|
|
1343
|
+
missingTargetStatusKey = missingKey;
|
|
1344
|
+
} else if (
|
|
1345
|
+
now - missingTargetStatusSince >=
|
|
1346
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
1347
|
+
) {
|
|
1348
|
+
const reportedTargetIds = statusTargets
|
|
1349
|
+
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
1350
|
+
.filter((targetId): targetId is number => targetId !== undefined);
|
|
1351
|
+
throw ERRORS.TypedError(
|
|
1352
|
+
HardwareErrorCode.FirmwareError,
|
|
1353
|
+
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
1354
|
+
','
|
|
1355
|
+
)}`
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
lastError = new Error(
|
|
1359
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
1360
|
+
);
|
|
1361
|
+
} else {
|
|
1362
|
+
resetMissingTargetStatusGrace();
|
|
1363
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1364
|
+
}
|
|
1880
1365
|
} catch (error) {
|
|
1881
1366
|
if (
|
|
1882
1367
|
error instanceof HardwareError &&
|
|
@@ -1884,19 +1369,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1884
1369
|
) {
|
|
1885
1370
|
throw error;
|
|
1886
1371
|
}
|
|
1372
|
+
// Only consecutive full status dumps can prove that a record is absent.
|
|
1373
|
+
// A reboot or transport interruption starts a fresh grace window.
|
|
1374
|
+
resetMissingTargetStatusGrace();
|
|
1887
1375
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
1888
1376
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
1889
1377
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1890
|
-
if (
|
|
1378
|
+
if (!currentDeviceInfo) {
|
|
1379
|
+
throw ERRORS.TypedError(
|
|
1380
|
+
HardwareErrorCode.RuntimeError,
|
|
1381
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
|
|
1891
1385
|
Log.log(
|
|
1892
1386
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
1893
1387
|
);
|
|
1388
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1894
1389
|
return;
|
|
1895
1390
|
}
|
|
1896
1391
|
lastError = new Error(
|
|
1897
1392
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
1898
1393
|
);
|
|
1899
1394
|
} else {
|
|
1395
|
+
shouldReconnect = true;
|
|
1396
|
+
deviceInfo = undefined;
|
|
1900
1397
|
lastError = error;
|
|
1901
1398
|
Log.log(
|
|
1902
1399
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -1912,12 +1409,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1912
1409
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1913
1410
|
throw error;
|
|
1914
1411
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
) {
|
|
1919
|
-
throw error;
|
|
1920
|
-
}
|
|
1412
|
+
shouldReconnect = true;
|
|
1413
|
+
deviceInfo = undefined;
|
|
1414
|
+
resetMissingTargetStatusGrace();
|
|
1921
1415
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1922
1416
|
}
|
|
1923
1417
|
await wait(1000);
|
|
@@ -1925,17 +1419,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1925
1419
|
|
|
1926
1420
|
throw ERRORS.TypedError(
|
|
1927
1421
|
HardwareErrorCode.RuntimeError,
|
|
1928
|
-
`Protocol V2 firmware update status
|
|
1422
|
+
`Protocol V2 firmware update status not complete within ${
|
|
1423
|
+
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
1424
|
+
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1929
1425
|
);
|
|
1930
1426
|
}
|
|
1931
1427
|
|
|
1932
1428
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1933
1429
|
await this.reconnectProtocolV2Device();
|
|
1934
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
1935
|
-
|
|
1936
|
-
|
|
1430
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1431
|
+
try {
|
|
1432
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
1433
|
+
Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1438
|
+
}
|
|
1937
1439
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1938
|
-
this.protocolV2ExecutionInLoader = false;
|
|
1939
1440
|
}
|
|
1940
1441
|
|
|
1941
1442
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1951,10 +1452,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1951
1452
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1952
1453
|
);
|
|
1953
1454
|
|
|
1455
|
+
return this.getProtocolV2VersionResult(features);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1954
1459
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1955
1460
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1956
1461
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1957
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
1958
1462
|
if (firmwareVersion === '0.0.0') {
|
|
1959
1463
|
Log.warn(
|
|
1960
1464
|
'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 +1472,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1968
1472
|
};
|
|
1969
1473
|
}
|
|
1970
1474
|
|
|
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
1475
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1983
1476
|
const startTime = Date.now();
|
|
1984
1477
|
let lastError: unknown;
|
|
1478
|
+
let shouldReconnect = true;
|
|
1985
1479
|
|
|
1986
1480
|
while (Date.now() - startTime < timeout) {
|
|
1987
1481
|
try {
|
|
1988
|
-
|
|
1482
|
+
if (shouldReconnect) {
|
|
1483
|
+
await this.reconnectProtocolV2Device();
|
|
1484
|
+
shouldReconnect = false;
|
|
1485
|
+
}
|
|
1989
1486
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1990
1487
|
commands: this.device.getCommands(),
|
|
1991
|
-
timeoutMs:
|
|
1488
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1992
1489
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
1993
1490
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1994
1491
|
});
|
|
@@ -1997,21 +1494,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1997
1494
|
deviceInfo,
|
|
1998
1495
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1999
1496
|
);
|
|
2000
|
-
if (features.mode
|
|
2001
|
-
|
|
2002
|
-
HardwareErrorCode.DeviceNotFound,
|
|
2003
|
-
'Protocol V2 device is still in bootloader mode'
|
|
2004
|
-
);
|
|
1497
|
+
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
1498
|
+
return features;
|
|
2005
1499
|
}
|
|
2006
|
-
|
|
1500
|
+
lastError = ERRORS.TypedError(
|
|
1501
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1502
|
+
'Protocol V2 device is still in bootloader mode'
|
|
1503
|
+
);
|
|
2007
1504
|
} catch (error) {
|
|
2008
1505
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2009
1506
|
throw error;
|
|
2010
1507
|
}
|
|
1508
|
+
shouldReconnect = true;
|
|
2011
1509
|
lastError = error;
|
|
2012
1510
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
2013
|
-
await wait(1000);
|
|
2014
1511
|
}
|
|
1512
|
+
await wait(1000);
|
|
2015
1513
|
}
|
|
2016
1514
|
|
|
2017
1515
|
throw ERRORS.TypedError(
|
|
@@ -2048,20 +1546,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2048
1546
|
return;
|
|
2049
1547
|
}
|
|
2050
1548
|
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
1549
|
+
// Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
|
|
1550
|
+
// Enumeration order is not stable and must never decide the reconnect target.
|
|
2053
1551
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
2054
1552
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2055
1553
|
});
|
|
2056
|
-
|
|
1554
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
1555
|
+
const identityMatch = expectedSerialNumber
|
|
1556
|
+
? deviceList.find(
|
|
1557
|
+
candidate => candidate.getCurrentSerialNo?.().trim() === expectedSerialNumber
|
|
1558
|
+
)
|
|
1559
|
+
: undefined;
|
|
1560
|
+
const singleCandidate = deviceList.length === 1 ? deviceList.at(0) : undefined;
|
|
1561
|
+
const singleCandidateSerialNumber = singleCandidate?.getCurrentSerialNo?.().trim();
|
|
1562
|
+
const reconnectDevice =
|
|
1563
|
+
identityMatch ??
|
|
1564
|
+
(singleCandidate && (!expectedSerialNumber || !singleCandidateSerialNumber)
|
|
1565
|
+
? singleCandidate
|
|
1566
|
+
: undefined);
|
|
1567
|
+
if (!reconnectDevice) {
|
|
2057
1568
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2058
1569
|
}
|
|
2059
1570
|
|
|
2060
1571
|
Log.debug(
|
|
2061
|
-
'Protocol V2 firmware reconnect using
|
|
2062
|
-
|
|
1572
|
+
'Protocol V2 firmware reconnect using matched device:',
|
|
1573
|
+
reconnectDevice.getConnectId()
|
|
2063
1574
|
);
|
|
2064
|
-
this.device.updateFromCache(
|
|
1575
|
+
this.device.updateFromCache(reconnectDevice);
|
|
2065
1576
|
await this.ensureProtocolV2DeviceAcquired();
|
|
2066
1577
|
this.device.commands.disposed = false;
|
|
2067
1578
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -2085,6 +1596,96 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2085
1596
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
2086
1597
|
}
|
|
2087
1598
|
|
|
1599
|
+
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1600
|
+
let lastError: unknown;
|
|
1601
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1602
|
+
try {
|
|
1603
|
+
return await this.protocolV2WriteWholeFile(params);
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
lastError = error;
|
|
1606
|
+
Log.error(
|
|
1607
|
+
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1608
|
+
error
|
|
1609
|
+
);
|
|
1610
|
+
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
throw ERRORS.TypedError(
|
|
1618
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1619
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
private async protocolV2WriteWholeFile({
|
|
1624
|
+
payload,
|
|
1625
|
+
filePath,
|
|
1626
|
+
processedSize,
|
|
1627
|
+
totalSize,
|
|
1628
|
+
onTransferredBytes,
|
|
1629
|
+
}: ProtocolV2FileTransferParams) {
|
|
1630
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1631
|
+
let offset = 0;
|
|
1632
|
+
const getUploadProgress = (fileOffset: number) => {
|
|
1633
|
+
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1634
|
+
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1635
|
+
}
|
|
1636
|
+
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
while (offset < payload.byteLength) {
|
|
1640
|
+
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1641
|
+
const chunkLength = chunkEnd - offset;
|
|
1642
|
+
const chunk = payload.slice(offset, chunkEnd);
|
|
1643
|
+
const overwrite = offset === 0;
|
|
1644
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1645
|
+
(processedSize ?? 0) + offset,
|
|
1646
|
+
(processedSize ?? 0) + chunkEnd,
|
|
1647
|
+
totalSize ?? payload.byteLength
|
|
1648
|
+
);
|
|
1649
|
+
|
|
1650
|
+
const writeRes = await this.fileWriteChunk(
|
|
1651
|
+
filePath,
|
|
1652
|
+
payload.byteLength,
|
|
1653
|
+
offset,
|
|
1654
|
+
chunk,
|
|
1655
|
+
overwrite,
|
|
1656
|
+
progress
|
|
1657
|
+
);
|
|
1658
|
+
const rawProcessedByte = writeRes.message.processed_byte;
|
|
1659
|
+
const processedByte = Number(rawProcessedByte);
|
|
1660
|
+
const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
|
|
1661
|
+
if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
|
|
1662
|
+
throw ERRORS.TypedError(
|
|
1663
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1664
|
+
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
offset = nextOffset;
|
|
1668
|
+
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1669
|
+
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1676
|
+
const env = DataManager.getSettings('env');
|
|
1677
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1678
|
+
return 'BLE';
|
|
1679
|
+
}
|
|
1680
|
+
if (
|
|
1681
|
+
env &&
|
|
1682
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1683
|
+
) {
|
|
1684
|
+
return 'WebUSB';
|
|
1685
|
+
}
|
|
1686
|
+
return env ?? 'unknown';
|
|
1687
|
+
}
|
|
1688
|
+
|
|
2088
1689
|
private async fileWriteChunk(
|
|
2089
1690
|
filePath: string,
|
|
2090
1691
|
totalFileSize: number,
|