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