@onekeyfe/hd-core 1.2.0-alpha.67 → 1.2.0-alpha.68
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 +313 -0
- package/__tests__/DeviceCommands.test.ts +59 -13
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-settings.test.ts +39 -18
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +13 -1
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +11 -1
- package/__tests__/deviceUploadNft.test.ts +316 -0
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +5 -0
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +340 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
- package/__tests__/protocol-v2.test.ts +1449 -867
- package/__tests__/protocolV2FileWrite.test.ts +152 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +11 -4
- 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/DeviceSettings.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/firmware/FirmwareUpdateBaseMethod.d.ts +4 -3
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +6 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +9 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +29 -5
- 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/DeviceConnector.d.ts +2 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +243 -69
- package/dist/index.js +2563 -1217
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -2
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +6 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +38 -8
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +61 -11
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +6 -2
- package/src/api/FirmwareUpdateV2.ts +5 -2
- package/src/api/FirmwareUpdateV3.ts +6 -2
- package/src/api/FirmwareUpdateV4.ts +463 -258
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +71 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
- package/src/api/device/DeviceSettings.ts +5 -4
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -12
- package/src/api/firmware/uploadFirmware.ts +17 -4
- package/src/api/helpers/protocolV2FileWrite.ts +202 -63
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +213 -111
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +88 -11
- package/src/data-manager/TransportManager.ts +6 -0
- package/src/device/Device.ts +273 -43
- package/src/device/DeviceCommands.ts +31 -4
- package/src/device/DeviceConnector.ts +33 -13
- package/src/device/DevicePool.ts +21 -7
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/inject.ts +58 -2
- package/src/lowLevelInject.ts +6 -3
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +390 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +6 -3
- package/src/types/api/checkAllFirmwareRelease.ts +3 -1
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/export.ts +1 -0
- package/src/types/api/firmwareUpdate.ts +6 -3
- package/src/types/api/index.ts +13 -0
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +15 -3
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +60 -9
- package/src/utils/deviceFeaturesCompat.ts +5 -0
- package/src/utils/deviceFeaturesUtils.ts +14 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
|
-
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
4
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
5
|
+
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
6
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
8
|
import { sha256 } from '@noble/hashes/sha256';
|
|
@@ -28,6 +28,12 @@ import {
|
|
|
28
28
|
ProtocolV2FirmwareTargetType,
|
|
29
29
|
} from '../protocols/protocol-v2';
|
|
30
30
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
31
|
+
import {
|
|
32
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
33
|
+
buildProtocolV2ResourceUpdatePlan,
|
|
34
|
+
isProtocolV2ResourceFileValid,
|
|
35
|
+
readProtocolV2ResourceInventory,
|
|
36
|
+
} from '../protocols/protocol-v2/resources';
|
|
31
37
|
import {
|
|
32
38
|
getProtocolV2UnknownErrorText,
|
|
33
39
|
isProtocolV2DeviceDisconnectedError,
|
|
@@ -51,18 +57,21 @@ import type {
|
|
|
51
57
|
Features,
|
|
52
58
|
IFirmwareReleaseInfo,
|
|
53
59
|
IProtocolV2FirmwareComponent,
|
|
60
|
+
IProtocolV2Resource,
|
|
61
|
+
IProtocolV2ResourceFile,
|
|
54
62
|
IVersionArray,
|
|
55
63
|
} from '../types';
|
|
56
64
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
57
65
|
|
|
58
66
|
const Log = getLogger(LoggerNames.Method);
|
|
59
67
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
|
|
68
|
+
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
69
|
+
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
63
70
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
71
|
+
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
64
72
|
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
65
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
73
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
74
|
+
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
66
75
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
67
76
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
68
77
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -73,7 +82,7 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
|
73
82
|
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
74
83
|
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
75
84
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
76
|
-
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT =
|
|
85
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
77
86
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
78
87
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
79
88
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
@@ -111,16 +120,11 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
111
120
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
112
121
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
113
122
|
};
|
|
114
|
-
/**
|
|
123
|
+
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
115
124
|
type ProtocolV2ResourceBundleBinary = {
|
|
116
125
|
name: string;
|
|
117
126
|
binary: ArrayBuffer;
|
|
118
127
|
devicePath: string;
|
|
119
|
-
/** Download URL for remote-config mode; omitted in manual mode. */
|
|
120
|
-
url?: string;
|
|
121
|
-
version?: IVersionArray;
|
|
122
|
-
payloadHash?: string;
|
|
123
|
-
headerHash?: string;
|
|
124
128
|
};
|
|
125
129
|
|
|
126
130
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -409,20 +413,30 @@ export const isProtocolV2FirmwareFingerprintValid = (
|
|
|
409
413
|
|
|
410
414
|
export const assertProtocolV2ReconnectIdentity = (
|
|
411
415
|
expectedSerialNumber?: string,
|
|
412
|
-
actualSerialNumber?: string
|
|
416
|
+
actualSerialNumber?: string,
|
|
417
|
+
expectedPath?: string,
|
|
418
|
+
actualPath?: string
|
|
413
419
|
) => {
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
if (expectedSerialNumber && actualSerialNumber) {
|
|
421
|
+
if (actualSerialNumber !== expectedSerialNumber) {
|
|
422
|
+
throw ERRORS.TypedError(
|
|
423
|
+
HardwareErrorCode.DeviceNotFound,
|
|
424
|
+
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
419
428
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
424
|
-
);
|
|
429
|
+
|
|
430
|
+
if (expectedPath && actualPath && expectedPath === actualPath) {
|
|
431
|
+
return;
|
|
425
432
|
}
|
|
433
|
+
|
|
434
|
+
throw ERRORS.TypedError(
|
|
435
|
+
HardwareErrorCode.DeviceNotFound,
|
|
436
|
+
`Protocol V2 reconnect physical identity unavailable: expected path ${
|
|
437
|
+
expectedPath ?? 'unknown'
|
|
438
|
+
}, received ${actualPath ?? 'unknown'}`
|
|
439
|
+
);
|
|
426
440
|
};
|
|
427
441
|
|
|
428
442
|
/**
|
|
@@ -436,6 +450,8 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
436
450
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
437
451
|
private protocolV2ExpectedSerialNumber?: string;
|
|
438
452
|
|
|
453
|
+
private protocolV2ExpectedPath?: string;
|
|
454
|
+
|
|
439
455
|
getSupportedProtocols() {
|
|
440
456
|
return ['V2'] as const;
|
|
441
457
|
}
|
|
@@ -488,7 +504,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
488
504
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
489
505
|
{ name: 'platform', type: 'string' },
|
|
490
506
|
{ name: 'expectedDeviceId', type: 'string' },
|
|
491
|
-
{ name: '
|
|
507
|
+
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
492
508
|
]);
|
|
493
509
|
if (
|
|
494
510
|
payload.expectedDeviceId !== undefined &&
|
|
@@ -554,7 +570,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
554
570
|
se02Binary: payload.se02Binary,
|
|
555
571
|
se03Binary: payload.se03Binary,
|
|
556
572
|
se04Binary: payload.se04Binary,
|
|
557
|
-
|
|
573
|
+
resourceFiles: payload.resourceFiles,
|
|
558
574
|
firmwareType: payload.firmwareType,
|
|
559
575
|
targetsToUpdate: payload.targetsToUpdate,
|
|
560
576
|
expectedTargetVersions: payload.expectedTargetVersions,
|
|
@@ -604,6 +620,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
604
620
|
) {
|
|
605
621
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
606
622
|
}
|
|
623
|
+
const needsRemoteResources =
|
|
624
|
+
!this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
|
|
625
|
+
const needsRemoteBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
607
626
|
|
|
608
627
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
609
628
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -611,21 +630,30 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
611
630
|
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
612
631
|
try {
|
|
613
632
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
633
|
+
resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
|
|
614
634
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
615
635
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
636
|
+
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
637
|
+
if (
|
|
638
|
+
(needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) &&
|
|
639
|
+
(this.params.artifactReader ||
|
|
640
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
641
|
+
) {
|
|
642
|
+
throw ERRORS.TypedError(
|
|
643
|
+
HardwareErrorCode.RuntimeError,
|
|
644
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
645
|
+
{
|
|
646
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
651
|
+
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
652
|
+
await DataManager.forceReloadData({
|
|
653
|
+
requireResources: needsRemoteResources || needsRemoteBootResources,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (needsRemoteFirmware) {
|
|
629
657
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
630
658
|
firmwareType,
|
|
631
659
|
deviceFeatures
|
|
@@ -634,25 +662,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
634
662
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
635
663
|
installItems = remoteBinaries.installItems;
|
|
636
664
|
}
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
665
|
+
const bootResourceFiles = await this.prepareProtocolV2BootResources();
|
|
666
|
+
if (bootResourceFiles?.length) {
|
|
667
|
+
resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
|
|
668
|
+
}
|
|
669
|
+
if (!needsRemoteResources) {
|
|
670
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
671
|
+
}
|
|
645
672
|
} catch (err) {
|
|
673
|
+
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
674
|
+
throw err;
|
|
675
|
+
}
|
|
646
676
|
throw normalizeFirmwarePreparationError(err);
|
|
647
677
|
}
|
|
648
678
|
|
|
649
|
-
if (
|
|
679
|
+
if (
|
|
680
|
+
!bootloaderBinary &&
|
|
681
|
+
fwBinaryMap.length === 0 &&
|
|
682
|
+
!installItems?.length &&
|
|
683
|
+
!resourceBundles?.length &&
|
|
684
|
+
!needsRemoteResources
|
|
685
|
+
) {
|
|
650
686
|
throw ERRORS.TypedError(
|
|
651
687
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
652
688
|
'No firmware to update'
|
|
653
689
|
);
|
|
654
690
|
}
|
|
655
691
|
|
|
692
|
+
if (needsRemoteResources) {
|
|
693
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
694
|
+
try {
|
|
695
|
+
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
696
|
+
resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
|
|
697
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
698
|
+
} catch (err) {
|
|
699
|
+
if (enteredBootloader) {
|
|
700
|
+
try {
|
|
701
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
702
|
+
} catch (restoreError) {
|
|
703
|
+
Log.warn(
|
|
704
|
+
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
705
|
+
restoreError
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
throw normalizeFirmwarePreparationError(err);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
656
713
|
return this.executeProtocolV2Update({
|
|
657
714
|
fwBinaryMap,
|
|
658
715
|
bootloaderBinary,
|
|
@@ -924,7 +981,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
924
981
|
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
925
982
|
targets.add(target as FirmwareUpdateV4Target)
|
|
926
983
|
);
|
|
927
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.
|
|
984
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
|
|
928
985
|
targets.add('resource');
|
|
929
986
|
}
|
|
930
987
|
if (this.params.bootloaderBinary) targets.add('boot');
|
|
@@ -957,20 +1014,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
957
1014
|
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
958
1015
|
assertProtocolV2ReconnectIdentity(
|
|
959
1016
|
this.protocolV2ExpectedSerialNumber,
|
|
960
|
-
this.getProtocolV2SerialNumber(deviceInfo)
|
|
1017
|
+
this.getProtocolV2SerialNumber(deviceInfo),
|
|
1018
|
+
this.protocolV2ExpectedPath,
|
|
1019
|
+
this.device.originalDescriptor.path
|
|
961
1020
|
);
|
|
962
1021
|
}
|
|
963
1022
|
|
|
1023
|
+
private getProtocolV2DeviceInfoTimeout() {
|
|
1024
|
+
return this.isBleReconnect()
|
|
1025
|
+
? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
|
|
1026
|
+
: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
964
1029
|
private async requestProtocolV2PhysicalIdentity() {
|
|
965
1030
|
return requestProtocolV2DeviceInfo({
|
|
966
1031
|
commands: this.device.getCommands(),
|
|
967
|
-
timeoutMs:
|
|
1032
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
968
1033
|
});
|
|
969
1034
|
}
|
|
970
1035
|
|
|
971
1036
|
private async captureProtocolV2PhysicalIdentity() {
|
|
972
1037
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
973
1038
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
1039
|
+
const path = this.device.originalDescriptor?.path?.trim() || undefined;
|
|
974
1040
|
if (this.params?.preparedPlan) {
|
|
975
1041
|
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
976
1042
|
preparedPlan: this.params.preparedPlan,
|
|
@@ -979,9 +1045,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
979
1045
|
} else if (this.params?.expectedDeviceId) {
|
|
980
1046
|
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
981
1047
|
} else {
|
|
982
|
-
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
1048
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
983
1049
|
}
|
|
984
1050
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
1051
|
+
this.protocolV2ExpectedPath = path;
|
|
985
1052
|
}
|
|
986
1053
|
|
|
987
1054
|
private async verifyProtocolV2ReconnectIdentity() {
|
|
@@ -996,7 +1063,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
996
1063
|
|
|
997
1064
|
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
998
1065
|
return (
|
|
999
|
-
!!this.params.
|
|
1066
|
+
!!this.params.resourceFiles?.length ||
|
|
1000
1067
|
!!this.params.bootloaderBinary ||
|
|
1001
1068
|
fwBinaryMap.length > 0
|
|
1002
1069
|
);
|
|
@@ -1059,57 +1126,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1059
1126
|
return target;
|
|
1060
1127
|
}
|
|
1061
1128
|
|
|
1062
|
-
private getProtocolV2ResourceFilePath(path: string) {
|
|
1063
|
-
if (path.startsWith('vol')) return path;
|
|
1064
|
-
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1065
|
-
return `vol0:/${path}`;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1069
|
-
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1070
|
-
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1071
|
-
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1072
|
-
path: filePath,
|
|
1073
|
-
});
|
|
1074
|
-
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1075
|
-
if (
|
|
1076
|
-
!pathInfoRes.message?.exist ||
|
|
1077
|
-
pathInfoRes.message?.directory ||
|
|
1078
|
-
fileSize === undefined ||
|
|
1079
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1080
|
-
) {
|
|
1081
|
-
return null;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1085
|
-
const chunks: Uint8Array[] = [];
|
|
1086
|
-
let offset = 0;
|
|
1087
|
-
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1088
|
-
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1089
|
-
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1090
|
-
file: {
|
|
1091
|
-
path: filePath,
|
|
1092
|
-
offset,
|
|
1093
|
-
total_size: 0,
|
|
1094
|
-
},
|
|
1095
|
-
chunk_len: readLen,
|
|
1096
|
-
ui_percentage: undefined,
|
|
1097
|
-
});
|
|
1098
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
1099
|
-
if (data.byteLength === 0) return null;
|
|
1100
|
-
chunks.push(data);
|
|
1101
|
-
offset += data.byteLength;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
const headerBytes = new Uint8Array(offset);
|
|
1105
|
-
let cursor = 0;
|
|
1106
|
-
chunks.forEach(chunk => {
|
|
1107
|
-
headerBytes.set(chunk, cursor);
|
|
1108
|
-
cursor += chunk.byteLength;
|
|
1109
|
-
});
|
|
1110
|
-
return parseProtocolV2OkppHeader(headerBytes);
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
1129
|
private async downloadRemoteProtocolV2Component(
|
|
1114
1130
|
key: string,
|
|
1115
1131
|
component: IProtocolV2FirmwareComponent
|
|
@@ -1198,123 +1214,177 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1198
1214
|
};
|
|
1199
1215
|
}
|
|
1200
1216
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1217
|
+
private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
|
|
1218
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1219
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
|
|
1220
|
+
throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
|
|
1221
|
+
}
|
|
1204
1222
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
);
|
|
1224
|
-
return {
|
|
1225
|
-
name: devicePath.split('/').pop() ?? devicePath,
|
|
1226
|
-
binary: file.binary,
|
|
1227
|
-
devicePath,
|
|
1228
|
-
};
|
|
1223
|
+
private async prepareProtocolV2BootResources(): Promise<
|
|
1224
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
1225
|
+
> {
|
|
1226
|
+
if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
|
|
1227
|
+
const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
|
|
1228
|
+
if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
|
|
1229
|
+
|
|
1230
|
+
const files: ProtocolV2ResourceBundleBinary[] = [];
|
|
1231
|
+
for (const file of resource.files) {
|
|
1232
|
+
Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
|
|
1233
|
+
const { binary } = await getSysResourceBinary(file.url);
|
|
1234
|
+
if (!isProtocolV2ResourceFileValid(binary, file)) {
|
|
1235
|
+
throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
|
|
1236
|
+
}
|
|
1237
|
+
files.push({
|
|
1238
|
+
name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
|
|
1239
|
+
binary,
|
|
1240
|
+
devicePath: file.devicePath,
|
|
1229
1241
|
});
|
|
1230
1242
|
}
|
|
1243
|
+
return files;
|
|
1244
|
+
}
|
|
1231
1245
|
|
|
1246
|
+
private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
1247
|
+
const files = this.params.resourceFiles ?? [];
|
|
1248
|
+
if (!files?.length) return undefined;
|
|
1249
|
+
|
|
1250
|
+
const prepared = files.map((file, index) => {
|
|
1251
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
1252
|
+
file.devicePath,
|
|
1253
|
+
`resourceFiles[${index}].devicePath`
|
|
1254
|
+
);
|
|
1255
|
+
const descriptor = file as typeof file &
|
|
1256
|
+
Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
|
|
1257
|
+
if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
|
|
1258
|
+
throw new Error(`resourceFiles[${index}] size mismatch`);
|
|
1259
|
+
}
|
|
1260
|
+
if (
|
|
1261
|
+
descriptor.fileHash &&
|
|
1262
|
+
!isProtocolV2ResourceFileValid(file.binary, {
|
|
1263
|
+
size: file.binary.byteLength,
|
|
1264
|
+
fileHash: descriptor.fileHash,
|
|
1265
|
+
})
|
|
1266
|
+
) {
|
|
1267
|
+
throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
|
|
1268
|
+
}
|
|
1269
|
+
return {
|
|
1270
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
1271
|
+
binary: file.binary,
|
|
1272
|
+
devicePath,
|
|
1273
|
+
};
|
|
1274
|
+
});
|
|
1275
|
+
if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
|
|
1276
|
+
throw new Error('resourceFiles contain duplicate devicePath values');
|
|
1277
|
+
}
|
|
1278
|
+
return prepared;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
1282
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
1283
|
+
> {
|
|
1232
1284
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
1233
1285
|
return undefined;
|
|
1234
1286
|
}
|
|
1235
1287
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
throw ERRORS.TypedError(
|
|
1263
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1264
|
-
`Missing Protocol V2 RESC bundle binary: ${bundle.name}`
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
if (
|
|
1268
|
-
this.params.artifactReader ||
|
|
1269
|
-
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
1270
|
-
) {
|
|
1271
|
-
throw ERRORS.TypedError(
|
|
1272
|
-
HardwareErrorCode.RuntimeError,
|
|
1273
|
-
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1274
|
-
{
|
|
1275
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1276
|
-
artifactName: bundle.name,
|
|
1277
|
-
}
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
1281
|
-
({ binary } = await getSysResourceBinary(bundle.url));
|
|
1282
|
-
bundle.binary = binary;
|
|
1283
|
-
downloadedFromRemote = true;
|
|
1284
|
-
}
|
|
1285
|
-
if (bundle.binary.byteLength === 0) {
|
|
1286
|
-
throw ERRORS.TypedError(
|
|
1287
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1288
|
-
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
if (downloadedFromRemote) {
|
|
1292
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1293
|
-
if (!header || header.type !== 'RESC') {
|
|
1294
|
-
throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
|
|
1295
|
-
}
|
|
1296
|
-
if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
|
|
1297
|
-
throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
|
|
1298
|
-
}
|
|
1299
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1300
|
-
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
|
|
1301
|
-
throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
|
|
1302
|
-
}
|
|
1303
|
-
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1304
|
-
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
|
|
1305
|
-
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1288
|
+
const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
|
|
1289
|
+
if (!resources?.length) {
|
|
1290
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const inventory = this.params.forcedUpdateRes
|
|
1294
|
+
? undefined
|
|
1295
|
+
: await readProtocolV2ResourceInventory({
|
|
1296
|
+
commands: this.device.getCommands(),
|
|
1297
|
+
resources,
|
|
1298
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1299
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1300
|
+
});
|
|
1301
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
1302
|
+
resources,
|
|
1303
|
+
inventory,
|
|
1304
|
+
mode: 'bootloader-recovery',
|
|
1305
|
+
forced: this.params.forcedUpdateRes,
|
|
1306
|
+
});
|
|
1307
|
+
Log.log(
|
|
1308
|
+
`[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
1312
|
+
for (const resource of plan.resources) {
|
|
1313
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
1308
1314
|
}
|
|
1309
1315
|
return bundles;
|
|
1310
1316
|
}
|
|
1311
1317
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1318
|
+
private async downloadProtocolV2Resource(
|
|
1319
|
+
resource: IProtocolV2Resource
|
|
1320
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
1321
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
1322
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
1323
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
1324
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
name: `${resource.type}.okpkg`,
|
|
1328
|
+
binary,
|
|
1329
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
1334
|
+
if (path.startsWith('vol')) return path;
|
|
1335
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1336
|
+
return `vol0:/${path}`;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1340
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1341
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1342
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1343
|
+
path: filePath,
|
|
1344
|
+
});
|
|
1345
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1346
|
+
if (
|
|
1347
|
+
!pathInfoRes.message?.exist ||
|
|
1348
|
+
pathInfoRes.message?.directory ||
|
|
1349
|
+
fileSize === undefined ||
|
|
1350
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1351
|
+
) {
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1356
|
+
const chunks: Uint8Array[] = [];
|
|
1357
|
+
let offset = 0;
|
|
1358
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1359
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1360
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1361
|
+
file: {
|
|
1362
|
+
path: filePath,
|
|
1363
|
+
offset,
|
|
1364
|
+
total_size: 0,
|
|
1365
|
+
},
|
|
1366
|
+
chunk_len: readLen,
|
|
1367
|
+
ui_percentage: undefined,
|
|
1368
|
+
});
|
|
1369
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
1370
|
+
if (data.byteLength === 0) return null;
|
|
1371
|
+
chunks.push(data);
|
|
1372
|
+
offset += data.byteLength;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const headerBytes = new Uint8Array(offset);
|
|
1376
|
+
let cursor = 0;
|
|
1377
|
+
chunks.forEach(chunk => {
|
|
1378
|
+
headerBytes.set(chunk, cursor);
|
|
1379
|
+
cursor += chunk.byteLength;
|
|
1380
|
+
});
|
|
1381
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/** Compare a prepared okpkg header when its manifest supplies version or hash metadata. */
|
|
1315
1385
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
1316
1386
|
bundle: Pick<
|
|
1317
|
-
|
|
1387
|
+
ProtocolV2ResourceBundleSource,
|
|
1318
1388
|
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1319
1389
|
>
|
|
1320
1390
|
): Promise<boolean> {
|
|
@@ -1346,7 +1416,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1346
1416
|
|
|
1347
1417
|
private isProtocolV2BootloaderMode() {
|
|
1348
1418
|
if (typeof this.device.isBootloader === 'function') {
|
|
1349
|
-
return this.device.isBootloader();
|
|
1419
|
+
return !!this.device.isBootloader();
|
|
1350
1420
|
}
|
|
1351
1421
|
return (
|
|
1352
1422
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -1355,7 +1425,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1355
1425
|
}
|
|
1356
1426
|
|
|
1357
1427
|
private isProtocolV2RomloaderMode() {
|
|
1358
|
-
|
|
1428
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1429
|
+
if (
|
|
1430
|
+
!this.device.isProtocolV2() ||
|
|
1431
|
+
(deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
|
|
1432
|
+
) {
|
|
1359
1433
|
return false;
|
|
1360
1434
|
}
|
|
1361
1435
|
if (typeof this.device.isRomloader === 'function') {
|
|
@@ -1375,16 +1449,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1375
1449
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1376
1450
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1377
1451
|
this.protocolV2ExecutionInLoader = true;
|
|
1452
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1378
1453
|
return false;
|
|
1379
1454
|
}
|
|
1380
1455
|
|
|
1381
1456
|
try {
|
|
1382
1457
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
1383
1458
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1384
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1385
1459
|
await wait(1000);
|
|
1386
1460
|
await this.waitForProtocolV2BootloaderMode();
|
|
1387
1461
|
this.protocolV2ExecutionInLoader = true;
|
|
1462
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1388
1463
|
return true;
|
|
1389
1464
|
} catch (error) {
|
|
1390
1465
|
if (error instanceof HardwareError) {
|
|
@@ -1401,13 +1476,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1401
1476
|
) {
|
|
1402
1477
|
const startTime = Date.now();
|
|
1403
1478
|
let lastError: unknown;
|
|
1479
|
+
let shouldReconnect = true;
|
|
1404
1480
|
|
|
1405
1481
|
while (Date.now() - startTime < timeout) {
|
|
1406
1482
|
try {
|
|
1407
|
-
|
|
1483
|
+
if (shouldReconnect) {
|
|
1484
|
+
await this.reconnectProtocolV2Device();
|
|
1485
|
+
shouldReconnect = false;
|
|
1486
|
+
}
|
|
1408
1487
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1409
1488
|
commands: this.device.getCommands(),
|
|
1410
|
-
timeoutMs:
|
|
1489
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1411
1490
|
});
|
|
1412
1491
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1413
1492
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1422,6 +1501,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1422
1501
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1423
1502
|
throw error;
|
|
1424
1503
|
}
|
|
1504
|
+
shouldReconnect = true;
|
|
1425
1505
|
lastError = error;
|
|
1426
1506
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
1427
1507
|
}
|
|
@@ -1597,9 +1677,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1597
1677
|
name: bundle.name,
|
|
1598
1678
|
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1599
1679
|
devicePath: bundle.devicePath,
|
|
1600
|
-
version: bundle.version,
|
|
1601
|
-
payloadHash: bundle.payloadHash,
|
|
1602
|
-
headerHash: bundle.headerHash,
|
|
1603
1680
|
}))
|
|
1604
1681
|
);
|
|
1605
1682
|
return await this.executeProtocolV2Phases({
|
|
@@ -1684,12 +1761,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1684
1761
|
let lastError: unknown;
|
|
1685
1762
|
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1686
1763
|
try {
|
|
1764
|
+
const transferStartedAt = Date.now();
|
|
1687
1765
|
await writeFirmwareByteSource({
|
|
1688
1766
|
source,
|
|
1689
1767
|
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1690
1768
|
write: async ({ data, sourceOffset, length, first }) => {
|
|
1691
1769
|
const chunkEnd = sourceOffset + length;
|
|
1692
|
-
const
|
|
1770
|
+
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
1693
1771
|
processedSize + sourceOffset,
|
|
1694
1772
|
processedSize + chunkEnd,
|
|
1695
1773
|
totalSize
|
|
@@ -1700,7 +1778,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1700
1778
|
sourceOffset,
|
|
1701
1779
|
data,
|
|
1702
1780
|
first,
|
|
1703
|
-
|
|
1781
|
+
deviceProgress
|
|
1704
1782
|
);
|
|
1705
1783
|
const rawProcessedByte = response.message.processed_byte;
|
|
1706
1784
|
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
@@ -1710,7 +1788,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1710
1788
|
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1711
1789
|
);
|
|
1712
1790
|
}
|
|
1713
|
-
|
|
1791
|
+
const transferredBytes = processedSize + chunkEnd;
|
|
1792
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
1793
|
+
this.postProgressMessage(
|
|
1794
|
+
Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
|
|
1795
|
+
'transferData',
|
|
1796
|
+
{
|
|
1797
|
+
transferredBytes,
|
|
1798
|
+
totalBytes: totalSize,
|
|
1799
|
+
rateBytesPerSecond:
|
|
1800
|
+
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
1801
|
+
elapsedMs,
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1714
1804
|
return length;
|
|
1715
1805
|
},
|
|
1716
1806
|
});
|
|
@@ -1850,6 +1940,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1850
1940
|
return false;
|
|
1851
1941
|
}
|
|
1852
1942
|
|
|
1943
|
+
private getProtocolV2MissingTargetIds(
|
|
1944
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1945
|
+
expectedTargetIds: Set<number>
|
|
1946
|
+
) {
|
|
1947
|
+
const reportedTargetIds = new Set<number>();
|
|
1948
|
+
statusTargets.forEach(target => {
|
|
1949
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1950
|
+
if (targetId !== undefined) {
|
|
1951
|
+
reportedTargetIds.add(targetId);
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1853
1957
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1854
1958
|
targets: Array<{ target_id: number; path: string }>
|
|
1855
1959
|
) {
|
|
@@ -1858,11 +1962,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1858
1962
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1859
1963
|
const startTime = Date.now();
|
|
1860
1964
|
let lastError: unknown;
|
|
1965
|
+
let shouldReconnect = true;
|
|
1966
|
+
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
1967
|
+
let missingTargetStatusSince: number | undefined;
|
|
1968
|
+
let missingTargetStatusKey: string | undefined;
|
|
1969
|
+
const resetMissingTargetStatusGrace = () => {
|
|
1970
|
+
missingTargetStatusSince = undefined;
|
|
1971
|
+
missingTargetStatusKey = undefined;
|
|
1972
|
+
};
|
|
1861
1973
|
|
|
1862
1974
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1863
1975
|
try {
|
|
1864
|
-
|
|
1865
|
-
|
|
1976
|
+
if (shouldReconnect) {
|
|
1977
|
+
await this.reconnectProtocolV2Device();
|
|
1978
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1979
|
+
shouldReconnect = false;
|
|
1980
|
+
}
|
|
1981
|
+
const currentDeviceInfo = deviceInfo;
|
|
1866
1982
|
try {
|
|
1867
1983
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1868
1984
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1874,36 +1990,87 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1874
1990
|
path: true,
|
|
1875
1991
|
},
|
|
1876
1992
|
},
|
|
1877
|
-
{ timeoutMs:
|
|
1993
|
+
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
1878
1994
|
);
|
|
1995
|
+
const statusTargets = (statusResponse.message.records ??
|
|
1996
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1997
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
1998
|
+
this.protocolV2FinalStatusVerified = true;
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1879
2002
|
if (
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
expectedPaths
|
|
1884
|
-
)
|
|
2003
|
+
statusTargets.length === 0 &&
|
|
2004
|
+
currentDeviceInfo &&
|
|
2005
|
+
(await this.probeProtocolV2NormalMode(currentDeviceInfo))
|
|
1885
2006
|
) {
|
|
1886
|
-
|
|
2007
|
+
Log.log(
|
|
2008
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2009
|
+
);
|
|
2010
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1887
2011
|
return;
|
|
1888
2012
|
}
|
|
1889
|
-
|
|
2013
|
+
|
|
2014
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2015
|
+
statusTargets,
|
|
2016
|
+
expectedTargetIds
|
|
2017
|
+
);
|
|
2018
|
+
if (missingTargetIds.length > 0) {
|
|
2019
|
+
const now = Date.now();
|
|
2020
|
+
const missingKey = missingTargetIds.join(',');
|
|
2021
|
+
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2022
|
+
missingTargetStatusSince = now;
|
|
2023
|
+
missingTargetStatusKey = missingKey;
|
|
2024
|
+
} else if (
|
|
2025
|
+
now - missingTargetStatusSince >=
|
|
2026
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2027
|
+
) {
|
|
2028
|
+
const reportedTargetIds = statusTargets
|
|
2029
|
+
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2030
|
+
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2031
|
+
throw ERRORS.TypedError(
|
|
2032
|
+
HardwareErrorCode.FirmwareError,
|
|
2033
|
+
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2034
|
+
','
|
|
2035
|
+
)}`
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
lastError = new Error(
|
|
2039
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2040
|
+
);
|
|
2041
|
+
} else {
|
|
2042
|
+
resetMissingTargetStatusGrace();
|
|
2043
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2044
|
+
}
|
|
1890
2045
|
} catch (error) {
|
|
1891
2046
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
1892
2047
|
throw error;
|
|
1893
2048
|
}
|
|
2049
|
+
// Only consecutive full status dumps can prove that a record is absent.
|
|
2050
|
+
// A reboot or transport interruption starts a fresh grace window.
|
|
2051
|
+
resetMissingTargetStatusGrace();
|
|
1894
2052
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
1895
2053
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
1896
2054
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1897
|
-
if (
|
|
2055
|
+
if (!currentDeviceInfo) {
|
|
2056
|
+
throw ERRORS.TypedError(
|
|
2057
|
+
HardwareErrorCode.RuntimeError,
|
|
2058
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
|
|
1898
2062
|
Log.log(
|
|
1899
2063
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
1900
2064
|
);
|
|
2065
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1901
2066
|
return;
|
|
1902
2067
|
}
|
|
1903
2068
|
lastError = new Error(
|
|
1904
2069
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
1905
2070
|
);
|
|
1906
2071
|
} else {
|
|
2072
|
+
shouldReconnect = true;
|
|
2073
|
+
deviceInfo = undefined;
|
|
1907
2074
|
lastError = error;
|
|
1908
2075
|
Log.log(
|
|
1909
2076
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -1925,6 +2092,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1925
2092
|
) {
|
|
1926
2093
|
throw error;
|
|
1927
2094
|
}
|
|
2095
|
+
shouldReconnect = true;
|
|
2096
|
+
deviceInfo = undefined;
|
|
2097
|
+
resetMissingTargetStatusGrace();
|
|
1928
2098
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1929
2099
|
}
|
|
1930
2100
|
await wait(1000);
|
|
@@ -1932,15 +2102,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1932
2102
|
|
|
1933
2103
|
throw ERRORS.TypedError(
|
|
1934
2104
|
HardwareErrorCode.RuntimeError,
|
|
1935
|
-
`Protocol V2 firmware update status
|
|
2105
|
+
`Protocol V2 firmware update status not complete within ${
|
|
2106
|
+
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2107
|
+
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1936
2108
|
);
|
|
1937
2109
|
}
|
|
1938
2110
|
|
|
1939
2111
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1940
2112
|
await this.reconnectProtocolV2Device();
|
|
1941
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
1942
|
-
|
|
1943
|
-
|
|
2113
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2114
|
+
try {
|
|
2115
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
2116
|
+
Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2121
|
+
}
|
|
1944
2122
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1945
2123
|
this.protocolV2ExecutionInLoader = false;
|
|
1946
2124
|
}
|
|
@@ -1958,6 +2136,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1958
2136
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1959
2137
|
);
|
|
1960
2138
|
|
|
2139
|
+
return this.getProtocolV2VersionResult(features);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1961
2143
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1962
2144
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1963
2145
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
@@ -1989,13 +2171,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1989
2171
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1990
2172
|
const startTime = Date.now();
|
|
1991
2173
|
let lastError: unknown;
|
|
2174
|
+
let shouldReconnect = true;
|
|
1992
2175
|
|
|
1993
2176
|
while (Date.now() - startTime < timeout) {
|
|
1994
2177
|
try {
|
|
1995
|
-
|
|
2178
|
+
if (shouldReconnect) {
|
|
2179
|
+
await this.reconnectProtocolV2Device();
|
|
2180
|
+
shouldReconnect = false;
|
|
2181
|
+
}
|
|
1996
2182
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1997
2183
|
commands: this.device.getCommands(),
|
|
1998
|
-
timeoutMs:
|
|
2184
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1999
2185
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
2000
2186
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
2001
2187
|
});
|
|
@@ -2004,21 +2190,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2004
2190
|
deviceInfo,
|
|
2005
2191
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2006
2192
|
);
|
|
2007
|
-
if (features.mode
|
|
2008
|
-
|
|
2009
|
-
HardwareErrorCode.DeviceNotFound,
|
|
2010
|
-
'Protocol V2 device is still in bootloader mode'
|
|
2011
|
-
);
|
|
2193
|
+
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
2194
|
+
return features;
|
|
2012
2195
|
}
|
|
2013
|
-
|
|
2196
|
+
lastError = ERRORS.TypedError(
|
|
2197
|
+
HardwareErrorCode.DeviceNotFound,
|
|
2198
|
+
'Protocol V2 device is still in bootloader mode'
|
|
2199
|
+
);
|
|
2014
2200
|
} catch (error) {
|
|
2015
2201
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2016
2202
|
throw error;
|
|
2017
2203
|
}
|
|
2204
|
+
shouldReconnect = true;
|
|
2018
2205
|
lastError = error;
|
|
2019
2206
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
2020
|
-
await wait(1000);
|
|
2021
2207
|
}
|
|
2208
|
+
await wait(1000);
|
|
2022
2209
|
}
|
|
2023
2210
|
|
|
2024
2211
|
throw ERRORS.TypedError(
|
|
@@ -2042,9 +2229,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2042
2229
|
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
2043
2230
|
devicesDescriptor.length === 1
|
|
2044
2231
|
) {
|
|
2232
|
+
const descriptor = devicesDescriptor[0];
|
|
2233
|
+
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2234
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2235
|
+
}
|
|
2045
2236
|
this.device.updateDescriptor(
|
|
2046
2237
|
{
|
|
2047
|
-
...
|
|
2238
|
+
...descriptor,
|
|
2048
2239
|
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2049
2240
|
},
|
|
2050
2241
|
true
|
|
@@ -2055,20 +2246,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2055
2246
|
return;
|
|
2056
2247
|
}
|
|
2057
2248
|
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2249
|
+
// Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
|
|
2250
|
+
// Enumeration order is not stable and must never decide the reconnect target.
|
|
2060
2251
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
2061
2252
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2062
2253
|
});
|
|
2063
|
-
|
|
2254
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
2255
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
2256
|
+
const reconnectDevice = deviceList.find(candidate => {
|
|
2257
|
+
const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
|
|
2258
|
+
if (expectedSerialNumber && candidateSerialNumber) {
|
|
2259
|
+
return candidateSerialNumber === expectedSerialNumber;
|
|
2260
|
+
}
|
|
2261
|
+
return !!expectedPath && candidate.getConnectId() === expectedPath;
|
|
2262
|
+
});
|
|
2263
|
+
if (!reconnectDevice) {
|
|
2064
2264
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2065
2265
|
}
|
|
2066
2266
|
|
|
2067
2267
|
Log.debug(
|
|
2068
|
-
'Protocol V2 firmware reconnect using
|
|
2069
|
-
|
|
2268
|
+
'Protocol V2 firmware reconnect using matched device:',
|
|
2269
|
+
reconnectDevice.getConnectId()
|
|
2070
2270
|
);
|
|
2071
|
-
this.device.updateFromCache(
|
|
2271
|
+
this.device.updateFromCache(reconnectDevice);
|
|
2072
2272
|
await this.ensureProtocolV2DeviceAcquired();
|
|
2073
2273
|
this.device.commands.disposed = false;
|
|
2074
2274
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -2101,17 +2301,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2101
2301
|
progress: number | null
|
|
2102
2302
|
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
2103
2303
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2104
|
-
const writeRes = await typedCall(
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2304
|
+
const writeRes = await typedCall(
|
|
2305
|
+
'FilesystemFileWrite',
|
|
2306
|
+
'FilesystemFile',
|
|
2307
|
+
{
|
|
2308
|
+
file: {
|
|
2309
|
+
path: filePath,
|
|
2310
|
+
offset,
|
|
2311
|
+
total_size: totalFileSize,
|
|
2312
|
+
data: chunk,
|
|
2313
|
+
},
|
|
2314
|
+
overwrite,
|
|
2315
|
+
append: false,
|
|
2316
|
+
ui_percentage: progress ?? undefined,
|
|
2110
2317
|
},
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
ui_percentage: progress ?? undefined,
|
|
2114
|
-
});
|
|
2318
|
+
{ writeWithResponse: true }
|
|
2319
|
+
);
|
|
2115
2320
|
if (writeRes.type !== 'FilesystemFile') {
|
|
2116
2321
|
if ((writeRes as any).type === 'CallMethodError') {
|
|
2117
2322
|
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|