@onekeyfe/hd-core 1.2.0-alpha.50 → 1.2.0-alpha.52
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__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -111
- 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 -196
- package/__tests__/protocol-v2.test.ts +1032 -1056
- 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.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 -9
- 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/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/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/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 +230 -203
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3469 -2419
- 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 -4
- 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 -1
- package/dist/types/api/checkAllFirmwareRelease.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 -1
- 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/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 -2
- 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 +53 -41
- 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 +937 -430
- 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 +1 -1
- 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/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 -257
- 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 +2 -9
- 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 -169
- package/src/topLevelInject.ts +8 -7
- package/src/types/api/checkAllFirmwareRelease.ts +4 -1
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- 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/params.ts +1 -7
- package/src/types/settings.ts +13 -42
- package/src/utils/deviceFeaturesCompat.ts +6 -0
- package/src/utils/deviceFeaturesUtils.ts +0 -4
- 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 -124
- 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,30 +28,32 @@ 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
|
|
|
@@ -75,7 +79,6 @@ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
|
75
79
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
76
80
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
77
81
|
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
78
|
-
const PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME = 'boot_resources.crate.okpkg';
|
|
79
82
|
|
|
80
83
|
const getProtocolV2DeviceTransferProgress = (
|
|
81
84
|
bytesBeforeChunk: number,
|
|
@@ -94,11 +97,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
94
97
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
95
98
|
};
|
|
96
99
|
|
|
97
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
98
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
99
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
100
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
103
101
|
target_id: number | string;
|
|
104
102
|
status?: number | string;
|
|
@@ -110,24 +108,18 @@ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
|
110
108
|
|
|
111
109
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
112
110
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
113
|
-
kind: ProtocolV2RemoteComponentTarget['kind']
|
|
114
|
-
};
|
|
115
|
-
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
116
|
-
path: string;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
120
|
-
filePath: string;
|
|
121
|
-
processedSize?: number;
|
|
122
|
-
totalSize?: number;
|
|
123
|
-
onTransferredBytes?: (transferredBytes: number) => void;
|
|
111
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
124
112
|
};
|
|
125
|
-
|
|
126
113
|
/** RESC bundle okpkg written independently to devicePath through FileWrite. */
|
|
127
114
|
type ProtocolV2ResourceBundleBinary = {
|
|
128
115
|
name: string;
|
|
129
116
|
binary: ArrayBuffer;
|
|
130
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;
|
|
131
123
|
};
|
|
132
124
|
|
|
133
125
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -140,6 +132,35 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
140
132
|
kind: 'bootloader' | 'firmware';
|
|
141
133
|
};
|
|
142
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
|
+
|
|
143
164
|
type ProtocolV2OkppHeader = {
|
|
144
165
|
type: string;
|
|
145
166
|
version: IVersionArray;
|
|
@@ -279,6 +300,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
279
300
|
|
|
280
301
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
281
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
|
+
|
|
282
315
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
283
316
|
Array.from(bytes)
|
|
284
317
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -371,10 +404,11 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
371
404
|
expectedSerialNumber?: string,
|
|
372
405
|
actualSerialNumber?: string
|
|
373
406
|
) => {
|
|
374
|
-
// Some Pro2 loader builds omit serial_no. Reconnect still uses the same BLE
|
|
375
|
-
// descriptor or requires a uniquely enumerated USB device before this check.
|
|
376
407
|
if (!expectedSerialNumber || !actualSerialNumber) {
|
|
377
|
-
|
|
408
|
+
throw ERRORS.TypedError(
|
|
409
|
+
HardwareErrorCode.DeviceNotFound,
|
|
410
|
+
'Protocol V2 reconnect physical identity is unavailable'
|
|
411
|
+
);
|
|
378
412
|
}
|
|
379
413
|
if (actualSerialNumber !== expectedSerialNumber) {
|
|
380
414
|
throw ERRORS.TypedError(
|
|
@@ -399,6 +433,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
399
433
|
return ['V2'] as const;
|
|
400
434
|
}
|
|
401
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
|
+
|
|
402
446
|
init() {
|
|
403
447
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
404
448
|
this.requireDeviceMode = [];
|
|
@@ -425,7 +469,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
425
469
|
{ name: 'chunkSize', type: 'number' },
|
|
426
470
|
{ name: 'forcedUpdateRes', type: 'boolean' },
|
|
427
471
|
{ name: 'bootloaderBinary', type: 'buffer' },
|
|
428
|
-
{ name: 'bootResourcesBinary', type: 'buffer' },
|
|
429
472
|
{ name: 'romloaderBinary', type: 'buffer' },
|
|
430
473
|
{ name: 'applicationP1Binary', type: 'buffer' },
|
|
431
474
|
{ name: 'applicationP2Binary', type: 'buffer' },
|
|
@@ -437,14 +480,65 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
437
480
|
{ name: 'firmwareType', type: 'string' },
|
|
438
481
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
439
482
|
{ name: 'platform', type: 'string' },
|
|
483
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
440
484
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
441
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
|
+
}
|
|
442
536
|
|
|
443
537
|
this.params = {
|
|
538
|
+
preparedPlan: payload.preparedPlan,
|
|
444
539
|
chunkSize: payload.chunkSize,
|
|
445
540
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
446
541
|
bootloaderBinary: payload.bootloaderBinary,
|
|
447
|
-
bootResourcesBinary: payload.bootResourcesBinary,
|
|
448
542
|
romloaderBinary: payload.romloaderBinary,
|
|
449
543
|
applicationP1Binary: payload.applicationP1Binary,
|
|
450
544
|
applicationP2Binary: payload.applicationP2Binary,
|
|
@@ -456,17 +550,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
456
550
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
457
551
|
firmwareType: payload.firmwareType,
|
|
458
552
|
targetsToUpdate: payload.targetsToUpdate,
|
|
553
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
459
554
|
platform: payload.platform,
|
|
555
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
556
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
557
|
+
componentArtifacts: payload.componentArtifacts,
|
|
558
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
460
559
|
};
|
|
461
560
|
}
|
|
462
561
|
|
|
463
|
-
private getProtocolV2FirmwareChunkSize() {
|
|
562
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
|
|
464
563
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
465
564
|
const env = DataManager.getSettings('env');
|
|
466
565
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
467
566
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
468
567
|
if (isBle) {
|
|
469
|
-
maxChunkSize =
|
|
568
|
+
maxChunkSize =
|
|
569
|
+
direction === 'read'
|
|
570
|
+
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
571
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
470
572
|
}
|
|
471
573
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
472
574
|
return maxChunkSize;
|
|
@@ -487,29 +589,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
487
589
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
488
590
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
489
591
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
+
}
|
|
493
600
|
|
|
494
601
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
495
602
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
496
|
-
let bootResourcesInstallItem: ProtocolV2InstallItem | undefined;
|
|
497
603
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
498
604
|
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
499
605
|
try {
|
|
500
606
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
501
|
-
resourceBundles = this.prepareExplicitProtocolV2ResourceBundles();
|
|
502
607
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
503
608
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
+
}
|
|
513
622
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
514
623
|
firmwareType,
|
|
515
624
|
deviceFeatures
|
|
@@ -518,68 +627,308 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
518
627
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
519
628
|
installItems = remoteBinaries.installItems;
|
|
520
629
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
530
|
-
}
|
|
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);
|
|
531
638
|
} catch (err) {
|
|
532
|
-
throw
|
|
639
|
+
throw normalizeFirmwarePreparationError(err);
|
|
533
640
|
}
|
|
534
641
|
|
|
535
|
-
if (
|
|
536
|
-
!bootloaderBinary &&
|
|
537
|
-
fwBinaryMap.length === 0 &&
|
|
538
|
-
!installItems?.length &&
|
|
539
|
-
!resourceBundles?.length &&
|
|
540
|
-
!needsRemoteResources
|
|
541
|
-
) {
|
|
642
|
+
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
542
643
|
throw ERRORS.TypedError(
|
|
543
644
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
544
645
|
'No firmware to update'
|
|
545
646
|
);
|
|
546
647
|
}
|
|
547
648
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (needsRemoteResources) {
|
|
551
|
-
try {
|
|
552
|
-
resourceBundles = await this.prepareProtocolV2ResourceBundles();
|
|
553
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
554
|
-
} catch (err) {
|
|
555
|
-
if (enteredBootloader) {
|
|
556
|
-
try {
|
|
557
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
558
|
-
} catch (restoreError) {
|
|
559
|
-
Log.warn(
|
|
560
|
-
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
561
|
-
restoreError
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
await this.executeProtocolV2Update({
|
|
649
|
+
return this.executeProtocolV2Update({
|
|
570
650
|
fwBinaryMap,
|
|
571
651
|
bootloaderBinary,
|
|
572
652
|
...(installItems ? { installItems } : undefined),
|
|
573
653
|
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
574
654
|
});
|
|
655
|
+
}
|
|
575
656
|
|
|
576
|
-
|
|
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
|
+
}
|
|
577
674
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
+
}
|
|
581
686
|
|
|
582
|
-
|
|
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);
|
|
583
932
|
}
|
|
584
933
|
|
|
585
934
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -615,7 +964,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
615
964
|
private async captureProtocolV2PhysicalIdentity() {
|
|
616
965
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
617
966
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
618
|
-
|
|
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
|
+
}
|
|
619
977
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
620
978
|
}
|
|
621
979
|
|
|
@@ -633,7 +991,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
633
991
|
return (
|
|
634
992
|
!!this.params.resourceBundleFiles?.length ||
|
|
635
993
|
!!this.params.bootloaderBinary ||
|
|
636
|
-
!!this.params.bootResourcesBinary ||
|
|
637
994
|
fwBinaryMap.length > 0
|
|
638
995
|
);
|
|
639
996
|
}
|
|
@@ -695,6 +1052,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
695
1052
|
return target;
|
|
696
1053
|
}
|
|
697
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
|
+
|
|
698
1106
|
private async downloadRemoteProtocolV2Component(
|
|
699
1107
|
key: string,
|
|
700
1108
|
component: IProtocolV2FirmwareComponent
|
|
@@ -783,159 +1191,155 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
783
1191
|
};
|
|
784
1192
|
}
|
|
785
1193
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
) {
|
|
790
|
-
if (resource && !isProtocolV2ResourceFileValid(binary, resource)) {
|
|
791
|
-
throw new Error('Pro2 boot resources file verification failed');
|
|
792
|
-
}
|
|
793
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
794
|
-
if (!header || header.type !== 'CRAT') {
|
|
795
|
-
throw new Error('Invalid Pro2 boot resources CRATE header');
|
|
796
|
-
}
|
|
797
|
-
if (resource) {
|
|
798
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(resource.payloadHash);
|
|
799
|
-
const expectedHeaderHash = normalizeProtocolV2Hex(resource.headerHash);
|
|
800
|
-
if (header.payloadHash !== expectedPayloadHash) {
|
|
801
|
-
throw new Error('Pro2 boot resources payload hash mismatch');
|
|
802
|
-
}
|
|
803
|
-
if (header.headerHash !== expectedHeaderHash) {
|
|
804
|
-
throw new Error('Pro2 boot resources header hash mismatch');
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
1194
|
+
// ============================================================
|
|
1195
|
+
// Incremental RESC bundle sync through FileRead and FilesystemFileWrite
|
|
1196
|
+
// ============================================================
|
|
808
1197
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
+
});
|
|
823
1223
|
}
|
|
824
1224
|
|
|
825
|
-
this.validateProtocolV2BootResourcesBinary(binary, resource);
|
|
826
|
-
return {
|
|
827
|
-
fileName: PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME,
|
|
828
|
-
binary,
|
|
829
|
-
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
|
|
830
|
-
kind: 'boot_resources',
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
private prepareExplicitProtocolV2ResourceBundles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
835
|
-
if (!this.params.resourceBundleFiles?.length) return undefined;
|
|
836
|
-
|
|
837
|
-
return this.params.resourceBundleFiles.map((file, index) => {
|
|
838
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
839
|
-
file.devicePath,
|
|
840
|
-
`resourceBundleFiles[${index}].devicePath`
|
|
841
|
-
);
|
|
842
|
-
return {
|
|
843
|
-
name: devicePath.split('/').pop() ?? devicePath,
|
|
844
|
-
binary: file.binary,
|
|
845
|
-
devicePath,
|
|
846
|
-
};
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
851
|
-
ProtocolV2ResourceBundleBinary[] | undefined
|
|
852
|
-
> {
|
|
853
1225
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
854
1226
|
return undefined;
|
|
855
1227
|
}
|
|
856
1228
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
:
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
)
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
+
}
|
|
883
1301
|
}
|
|
884
1302
|
return bundles;
|
|
885
1303
|
}
|
|
886
1304
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
private async syncProtocolV2ResourceBundles(
|
|
903
|
-
bundles: ProtocolV2ResourceBundleBinary[],
|
|
904
|
-
firmwareSize: number
|
|
905
|
-
): Promise<{ processedSize: number; totalSize: number }> {
|
|
906
|
-
const transferStartTime = Date.now();
|
|
907
|
-
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;
|
|
908
1316
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1317
|
+
try {
|
|
1318
|
+
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1319
|
+
if (!header) return false;
|
|
912
1320
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
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;
|
|
925
1337
|
}
|
|
926
|
-
|
|
927
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
928
|
-
Log.log(
|
|
929
|
-
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
930
|
-
elapsedMs / 1000
|
|
931
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
932
|
-
);
|
|
933
|
-
return { processedSize, totalSize: transferTotalSize };
|
|
934
1338
|
}
|
|
935
1339
|
|
|
936
1340
|
private isProtocolV2BootloaderMode() {
|
|
937
1341
|
if (typeof this.device.isBootloader === 'function') {
|
|
938
|
-
return
|
|
1342
|
+
return this.device.isBootloader();
|
|
939
1343
|
}
|
|
940
1344
|
return (
|
|
941
1345
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -958,10 +1362,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
958
1362
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
959
1363
|
if (this.isProtocolV2RomloaderMode()) {
|
|
960
1364
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1365
|
+
this.protocolV2ExecutionInLoader = true;
|
|
961
1366
|
return false;
|
|
962
1367
|
}
|
|
963
1368
|
if (this.isProtocolV2BootloaderMode()) {
|
|
964
1369
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1370
|
+
this.protocolV2ExecutionInLoader = true;
|
|
965
1371
|
return false;
|
|
966
1372
|
}
|
|
967
1373
|
|
|
@@ -971,6 +1377,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
971
1377
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
972
1378
|
await wait(1000);
|
|
973
1379
|
await this.waitForProtocolV2BootloaderMode();
|
|
1380
|
+
this.protocolV2ExecutionInLoader = true;
|
|
974
1381
|
return true;
|
|
975
1382
|
} catch (error) {
|
|
976
1383
|
if (error instanceof HardwareError) {
|
|
@@ -1060,6 +1467,98 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1060
1467
|
return entries;
|
|
1061
1468
|
}
|
|
1062
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
|
+
|
|
1063
1562
|
private async executeProtocolV2Update({
|
|
1064
1563
|
fwBinaryMap,
|
|
1065
1564
|
bootloaderBinary,
|
|
@@ -1071,104 +1570,158 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1071
1570
|
installItems?: ProtocolV2InstallItem[];
|
|
1072
1571
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1073
1572
|
}) {
|
|
1074
|
-
const
|
|
1573
|
+
const memoryInstallItems =
|
|
1075
1574
|
installItems ??
|
|
1076
1575
|
this.buildProtocolV2InstallItems({
|
|
1077
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
1078
1576
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
1577
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1079
1578
|
});
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1090
|
-
resourceBundles
|
|
1091
|
-
|
|
1579
|
+
try {
|
|
1580
|
+
const installSources = await Promise.all(
|
|
1581
|
+
memoryInstallItems.map(async item => ({
|
|
1582
|
+
fileName: item.fileName,
|
|
1583
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1584
|
+
targetId: item.targetId,
|
|
1585
|
+
kind: item.kind,
|
|
1586
|
+
}))
|
|
1587
|
+
);
|
|
1588
|
+
const resourceSources = await Promise.all(
|
|
1589
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1590
|
+
name: bundle.name,
|
|
1591
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1592
|
+
devicePath: bundle.devicePath,
|
|
1593
|
+
version: bundle.version,
|
|
1594
|
+
payloadHash: bundle.payloadHash,
|
|
1595
|
+
headerHash: bundle.headerHash,
|
|
1596
|
+
}))
|
|
1092
1597
|
);
|
|
1093
|
-
|
|
1094
|
-
|
|
1598
|
+
return await this.executeProtocolV2Phases({
|
|
1599
|
+
installSources,
|
|
1600
|
+
resourceSources,
|
|
1601
|
+
});
|
|
1602
|
+
} finally {
|
|
1603
|
+
await this.closeProtocolV2PreparedSources();
|
|
1095
1604
|
}
|
|
1605
|
+
}
|
|
1096
1606
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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;
|
|
1102
1619
|
}
|
|
1103
|
-
return;
|
|
1104
1620
|
}
|
|
1105
|
-
let transferredSize = processedSize;
|
|
1106
|
-
const transferStartTime = Date.now();
|
|
1107
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1108
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1109
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1110
|
-
transferredSize = bytes;
|
|
1111
|
-
};
|
|
1112
|
-
Log.log(
|
|
1113
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1114
|
-
);
|
|
1115
|
-
|
|
1116
|
-
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1117
1621
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
processedSize
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
onTransferredBytes,
|
|
1130
|
-
});
|
|
1131
|
-
transferredSize = processedSize;
|
|
1132
|
-
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1133
|
-
|
|
1134
|
-
stagedInstallTargets.push({
|
|
1135
|
-
...item,
|
|
1136
|
-
path: filePath,
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
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
|
+
}
|
|
1139
1633
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
+
}
|
|
1143
1649
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
);
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1152
|
-
Log.warn(
|
|
1153
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1154
|
-
elapsedMs / 1000
|
|
1155
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1156
|
-
);
|
|
1157
|
-
throw error;
|
|
1650
|
+
if (totalSize > 0) {
|
|
1651
|
+
this.postProgressMessage(100, 'transferData');
|
|
1652
|
+
}
|
|
1653
|
+
if (stagedInstallTargets.length === 0) {
|
|
1654
|
+
return;
|
|
1158
1655
|
}
|
|
1159
1656
|
|
|
1160
1657
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1161
|
-
|
|
1162
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
1658
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1163
1659
|
target_id: item.targetId,
|
|
1164
1660
|
path: item.path,
|
|
1165
1661
|
}));
|
|
1166
|
-
|
|
1167
|
-
await this.
|
|
1168
|
-
|
|
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
|
+
);
|
|
1169
1722
|
}
|
|
1170
1723
|
|
|
1171
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1724
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1172
1725
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1173
1726
|
}
|
|
1174
1727
|
|
|
@@ -1187,7 +1740,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1187
1740
|
|
|
1188
1741
|
private assertProtocolV2TargetStatus(
|
|
1189
1742
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1190
|
-
expectedTargetIds: Set<number
|
|
1743
|
+
expectedTargetIds: Set<number>,
|
|
1744
|
+
expectedPaths = new Map<number, string>()
|
|
1191
1745
|
) {
|
|
1192
1746
|
Log.log(
|
|
1193
1747
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1218,17 +1772,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1218
1772
|
const matchingTargets = statusTargets.filter(target =>
|
|
1219
1773
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1220
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
|
+
);
|
|
1221
1793
|
const completedTargetIds = new Set<number>();
|
|
1222
|
-
|
|
1794
|
+
completedTargets.forEach(target => {
|
|
1223
1795
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1224
|
-
if (targetId !== undefined
|
|
1796
|
+
if (targetId !== undefined) {
|
|
1225
1797
|
completedTargetIds.add(targetId);
|
|
1226
1798
|
}
|
|
1227
1799
|
});
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
+
}
|
|
1232
1823
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1233
1824
|
return true;
|
|
1234
1825
|
}
|
|
@@ -1252,7 +1843,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1252
1843
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1253
1844
|
targets: Array<{ target_id: number; path: string }>
|
|
1254
1845
|
) {
|
|
1846
|
+
this.protocolV2FinalStatusVerified = false;
|
|
1255
1847
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1848
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1256
1849
|
const startTime = Date.now();
|
|
1257
1850
|
let lastError: unknown;
|
|
1258
1851
|
|
|
@@ -1276,9 +1869,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1276
1869
|
if (
|
|
1277
1870
|
this.assertProtocolV2TargetStatus(
|
|
1278
1871
|
(statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1279
|
-
expectedTargetIds
|
|
1872
|
+
expectedTargetIds,
|
|
1873
|
+
expectedPaths
|
|
1280
1874
|
)
|
|
1281
1875
|
) {
|
|
1876
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1282
1877
|
return;
|
|
1283
1878
|
}
|
|
1284
1879
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
@@ -1317,6 +1912,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1317
1912
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1318
1913
|
throw error;
|
|
1319
1914
|
}
|
|
1915
|
+
if (
|
|
1916
|
+
error instanceof HardwareError &&
|
|
1917
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
1918
|
+
) {
|
|
1919
|
+
throw error;
|
|
1920
|
+
}
|
|
1320
1921
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1321
1922
|
}
|
|
1322
1923
|
await wait(1000);
|
|
@@ -1334,6 +1935,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1334
1935
|
// The connection may still be in bootloader. Request a Normal reboot directly;
|
|
1335
1936
|
// repeating it after an automatic App reboot is idempotent.
|
|
1336
1937
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1938
|
+
this.protocolV2ExecutionInLoader = false;
|
|
1337
1939
|
}
|
|
1338
1940
|
|
|
1339
1941
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1349,13 +1951,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1349
1951
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1350
1952
|
);
|
|
1351
1953
|
|
|
1352
|
-
return this.getProtocolV2VersionResult(features);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
private getProtocolV2VersionResult(features: Features) {
|
|
1356
1954
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1357
1955
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1358
1956
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1957
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1359
1958
|
if (firmwareVersion === '0.0.0') {
|
|
1360
1959
|
Log.warn(
|
|
1361
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.'
|
|
@@ -1369,6 +1968,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1369
1968
|
};
|
|
1370
1969
|
}
|
|
1371
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
|
+
|
|
1372
1982
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1373
1983
|
const startTime = Date.now();
|
|
1374
1984
|
let lastError: unknown;
|
|
@@ -1438,33 +2048,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1438
2048
|
return;
|
|
1439
2049
|
}
|
|
1440
2050
|
|
|
1441
|
-
//
|
|
1442
|
-
//
|
|
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.
|
|
1443
2053
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
1444
2054
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1445
2055
|
});
|
|
1446
|
-
|
|
1447
|
-
const identityMatch = expectedSerialNumber
|
|
1448
|
-
? deviceList.find(
|
|
1449
|
-
candidate => candidate.getCurrentSerialNo?.().trim() === expectedSerialNumber
|
|
1450
|
-
)
|
|
1451
|
-
: undefined;
|
|
1452
|
-
const singleCandidate = deviceList.length === 1 ? deviceList.at(0) : undefined;
|
|
1453
|
-
const singleCandidateSerialNumber = singleCandidate?.getCurrentSerialNo?.().trim();
|
|
1454
|
-
const reconnectDevice =
|
|
1455
|
-
identityMatch ??
|
|
1456
|
-
(singleCandidate && (!expectedSerialNumber || !singleCandidateSerialNumber)
|
|
1457
|
-
? singleCandidate
|
|
1458
|
-
: undefined);
|
|
1459
|
-
if (!reconnectDevice) {
|
|
2056
|
+
if (deviceList.length !== 1) {
|
|
1460
2057
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1461
2058
|
}
|
|
1462
2059
|
|
|
1463
2060
|
Log.debug(
|
|
1464
|
-
'Protocol V2 firmware reconnect using
|
|
1465
|
-
|
|
2061
|
+
'Protocol V2 firmware reconnect using single enumerated device:',
|
|
2062
|
+
deviceList[0].getConnectId()
|
|
1466
2063
|
);
|
|
1467
|
-
this.device.updateFromCache(
|
|
2064
|
+
this.device.updateFromCache(deviceList[0]);
|
|
1468
2065
|
await this.ensureProtocolV2DeviceAcquired();
|
|
1469
2066
|
this.device.commands.disposed = false;
|
|
1470
2067
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -1488,96 +2085,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1488
2085
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
1489
2086
|
}
|
|
1490
2087
|
|
|
1491
|
-
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1492
|
-
let lastError: unknown;
|
|
1493
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1494
|
-
try {
|
|
1495
|
-
return await this.protocolV2WriteWholeFile(params);
|
|
1496
|
-
} catch (error) {
|
|
1497
|
-
lastError = error;
|
|
1498
|
-
Log.error(
|
|
1499
|
-
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1500
|
-
error
|
|
1501
|
-
);
|
|
1502
|
-
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1503
|
-
break;
|
|
1504
|
-
}
|
|
1505
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
throw ERRORS.TypedError(
|
|
1510
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1511
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1512
|
-
);
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
private async protocolV2WriteWholeFile({
|
|
1516
|
-
payload,
|
|
1517
|
-
filePath,
|
|
1518
|
-
processedSize,
|
|
1519
|
-
totalSize,
|
|
1520
|
-
onTransferredBytes,
|
|
1521
|
-
}: ProtocolV2FileTransferParams) {
|
|
1522
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1523
|
-
let offset = 0;
|
|
1524
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1525
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1526
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1527
|
-
}
|
|
1528
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1529
|
-
};
|
|
1530
|
-
|
|
1531
|
-
while (offset < payload.byteLength) {
|
|
1532
|
-
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1533
|
-
const chunkLength = chunkEnd - offset;
|
|
1534
|
-
const chunk = payload.slice(offset, chunkEnd);
|
|
1535
|
-
const overwrite = offset === 0;
|
|
1536
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1537
|
-
(processedSize ?? 0) + offset,
|
|
1538
|
-
(processedSize ?? 0) + chunkEnd,
|
|
1539
|
-
totalSize ?? payload.byteLength
|
|
1540
|
-
);
|
|
1541
|
-
|
|
1542
|
-
const writeRes = await this.fileWriteChunk(
|
|
1543
|
-
filePath,
|
|
1544
|
-
payload.byteLength,
|
|
1545
|
-
offset,
|
|
1546
|
-
chunk,
|
|
1547
|
-
overwrite,
|
|
1548
|
-
progress
|
|
1549
|
-
);
|
|
1550
|
-
const rawProcessedByte = writeRes.message.processed_byte;
|
|
1551
|
-
const processedByte = Number(rawProcessedByte);
|
|
1552
|
-
const nextOffset = rawProcessedByte === undefined ? offset + chunkLength : processedByte;
|
|
1553
|
-
if (!Number.isFinite(nextOffset) || nextOffset <= offset || nextOffset > chunkEnd) {
|
|
1554
|
-
throw ERRORS.TypedError(
|
|
1555
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1556
|
-
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
offset = nextOffset;
|
|
1560
|
-
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1561
|
-
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
private getProtocolV2FirmwareTransferTransport() {
|
|
1568
|
-
const env = DataManager.getSettings('env');
|
|
1569
|
-
if (env && DataManager.isBleConnect(env)) {
|
|
1570
|
-
return 'BLE';
|
|
1571
|
-
}
|
|
1572
|
-
if (
|
|
1573
|
-
env &&
|
|
1574
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1575
|
-
) {
|
|
1576
|
-
return 'WebUSB';
|
|
1577
|
-
}
|
|
1578
|
-
return env ?? 'unknown';
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
2088
|
private async fileWriteChunk(
|
|
1582
2089
|
filePath: string,
|
|
1583
2090
|
totalFileSize: number,
|