@onekeyfe/hd-core 1.2.8 → 1.2.10-alpha.1
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/README.md +27 -0
- package/__tests__/DeviceCommands.test.ts +66 -13
- package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +78 -0
- package/__tests__/btc-derive-context-hash.test.ts +200 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +55 -3
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +191 -27
- package/__tests__/device-pool-state.test.ts +50 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +331 -9
- package/__tests__/logger.test.ts +18 -0
- package/__tests__/monero-get-address.test.ts +147 -0
- package/__tests__/monero-key-image-sync.test.ts +272 -0
- package/__tests__/monero-sign-transaction.test.ts +436 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
- package/__tests__/protocol-v2-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +841 -46
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/sol-sign-offchain-message.test.ts +8 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +141 -0
- package/__tests__/zcash-sign-pczt.test.ts +446 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
- package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/helpers/pathUtils.d.ts +1 -0
- package/dist/api/helpers/pathUtils.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
- package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
- package/dist/api/monero/helpers.d.ts +5 -0
- package/dist/api/monero/helpers.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
- package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +10 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +196 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4363 -2792
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/allNetworkGetAddress.d.ts +5 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
- package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
- package/dist/types/api/export.d.ts +7 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +14 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/moneroGetAddress.d.ts +15 -0
- package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
- package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
- package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
- package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
- package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
- package/dist/types/api/moneroSignTransaction.d.ts +32 -0
- package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
- package/dist/types/api/zcashGetAddress.d.ts +22 -0
- package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
- package/dist/types/api/zcashSignPczt.d.ts +9 -0
- package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
- package/dist/types/device.d.ts +10 -0
- 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/utils/logger.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +2 -0
- package/src/api/FileWrite.ts +7 -1
- package/src/api/FirmwareUpdateV4.ts +366 -99
- package/src/api/GetDeviceState.ts +2 -5
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +38 -1
- package/src/api/btc/BTCDeriveContextHash.ts +90 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
- package/src/api/helpers/pathUtils.ts +23 -0
- package/src/api/helpers/protocolV2FileWrite.ts +382 -111
- package/src/api/index.ts +8 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +69 -0
- package/src/api/monero/MoneroKeyImageSync.ts +164 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -0
- package/src/api/protocol-v2/DeviceReboot.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
- package/src/api/zcash/ZcashGetAddress.ts +119 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/core/index.ts +52 -24
- package/src/data/messages/messages-protocol-v2.json +138 -1
- package/src/data/messages/messages.json +51 -0
- package/src/device/Device.ts +32 -4
- package/src/device/DeviceCommands.ts +15 -1
- package/src/device/DeviceConnector.ts +4 -4
- package/src/device/DevicePool.ts +12 -2
- package/src/events/core.ts +2 -2
- package/src/events/logBlockEvent.ts +57 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +15 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/types/api/allNetworkGetAddress.ts +9 -1
- package/src/types/api/btcDeriveContextHash.ts +15 -0
- package/src/types/api/export.ts +16 -0
- package/src/types/api/index.ts +22 -0
- package/src/types/api/moneroGetAddress.ts +27 -0
- package/src/types/api/moneroGetWatchKey.ts +28 -0
- package/src/types/api/moneroKeyImageSync.ts +45 -0
- package/src/types/api/moneroSignTransaction.ts +54 -0
- package/src/types/api/zcashGetAddress.ts +35 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/types/params.ts +1 -1
- package/src/utils/logger.ts +4 -0
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EDeviceType,
|
|
3
|
-
ERRORS,
|
|
4
|
-
HardwareError,
|
|
5
|
-
HardwareErrorCode,
|
|
6
|
-
isBleStaleBondHardwareError,
|
|
7
|
-
wait,
|
|
8
|
-
} from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
9
2
|
import JSZip from 'jszip';
|
|
3
|
+
import semver from 'semver';
|
|
10
4
|
import {
|
|
11
5
|
DeviceRebootType,
|
|
12
6
|
DeviceSessionPinType,
|
|
13
7
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
14
8
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
15
9
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
10
|
+
PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW,
|
|
16
11
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
17
12
|
} from '@onekeyfe/hd-transport';
|
|
18
13
|
import { sha256 } from '@noble/hashes/sha256';
|
|
@@ -41,6 +36,7 @@ import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
|
41
36
|
import {
|
|
42
37
|
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH,
|
|
43
38
|
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
|
|
39
|
+
PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE,
|
|
44
40
|
PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE,
|
|
45
41
|
type ProtocolV2ResourcePackageHeader,
|
|
46
42
|
isProtocolV2ResourceArchiveEntryName,
|
|
@@ -82,6 +78,7 @@ import type {
|
|
|
82
78
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
83
79
|
|
|
84
80
|
const Log = getLogger(LoggerNames.Method);
|
|
81
|
+
const TransferLog = getLogger(LoggerNames.Core);
|
|
85
82
|
|
|
86
83
|
// Restored after a rebase dropped the declaration while keeping its use in
|
|
87
84
|
// fileWriteChunk; without it that error branch throws ReferenceError instead of
|
|
@@ -108,7 +105,16 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
108
105
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
109
106
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
110
107
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
108
|
+
const PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT = 2;
|
|
109
|
+
// Minimum running bootloader that receives two in-flight BLE FilesystemFileWrite frames.
|
|
110
|
+
// Bootloaders with the 2048-byte BLE UART FIFO have no room beside one waiting FileWrite frame,
|
|
111
|
+
// so a second frame can overflow it. Released bootloaders report 1.0.2 with that FIFO and stay
|
|
112
|
+
// serial. Firmware has been asked to ship the 4096-byte FIFO from 1.0.3 but has not confirmed it,
|
|
113
|
+
// nor the BLE coprocessor margin for two frames; any bootloader reporting 1.0.3 or later gets the
|
|
114
|
+
// window. Set it to undefined to keep every transfer serial.
|
|
115
|
+
const PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION: string | undefined = '1.0.3';
|
|
111
116
|
const PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS = 1000;
|
|
117
|
+
const PROTOCOL_V2_TRANSFER_LOG_INTERVAL_MS = 10_000;
|
|
112
118
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
113
119
|
const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
|
|
114
120
|
const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
|
|
@@ -177,6 +183,18 @@ export function assertProtocolV2FirmwareTargetsSupported(
|
|
|
177
183
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotSupportMethod, message);
|
|
178
184
|
}
|
|
179
185
|
|
|
186
|
+
/** Whether the running bootloader accepts a second in-flight BLE FilesystemFileWrite. */
|
|
187
|
+
export function supportsProtocolV2BleFileWriteWindow(
|
|
188
|
+
bootloaderVersion: string,
|
|
189
|
+
minBootloaderVersion = PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION
|
|
190
|
+
) {
|
|
191
|
+
return (
|
|
192
|
+
!!minBootloaderVersion &&
|
|
193
|
+
!!semver.valid(bootloaderVersion) &&
|
|
194
|
+
semver.gte(bootloaderVersion, minBootloaderVersion)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
180
198
|
const getProtocolV2DeviceTransferProgress = (
|
|
181
199
|
bytesBeforeChunk: number,
|
|
182
200
|
bytesAfterChunk: number,
|
|
@@ -191,6 +209,12 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
191
209
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
192
210
|
};
|
|
193
211
|
|
|
212
|
+
const getProtocolV2TransferRate = (bytes: number, elapsedMs: number) =>
|
|
213
|
+
elapsedMs > 0 ? Math.round((bytes / elapsedMs) * 1000) : undefined;
|
|
214
|
+
|
|
215
|
+
const getProtocolV2ElapsedMs = (startedAt: number, now = Date.now()) =>
|
|
216
|
+
Math.max(now - startedAt, 0);
|
|
217
|
+
|
|
194
218
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
195
219
|
target_id: number | string;
|
|
196
220
|
status?: number | string;
|
|
@@ -359,13 +383,10 @@ const PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME = new Map<string, number>([
|
|
|
359
383
|
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS', 10],
|
|
360
384
|
]);
|
|
361
385
|
|
|
362
|
-
const isProtocolV2ReconnectProbeError = (error: unknown) =>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
message.includes('did not respond to expected protocol')
|
|
367
|
-
);
|
|
368
|
-
};
|
|
386
|
+
const isProtocolV2ReconnectProbeError = (error: unknown) =>
|
|
387
|
+
getProtocolV2ErrorCode(error) === HardwareErrorCode.TransportProtocolProbeFailed &&
|
|
388
|
+
(error as { params?: { expectedProtocol?: unknown } } | undefined)?.params?.expectedProtocol ===
|
|
389
|
+
'V2';
|
|
369
390
|
|
|
370
391
|
const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
371
392
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
@@ -392,13 +413,29 @@ const getProtocolV2ErrorCode = (error: unknown): number | string | undefined =>
|
|
|
392
413
|
return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
|
|
393
414
|
};
|
|
394
415
|
|
|
416
|
+
/** Errors that mean the link is gone, so the read says nothing about the installed bundle. */
|
|
417
|
+
const isProtocolV2ResourceHeaderLinkError = (error: unknown) => {
|
|
418
|
+
if (isProtocolV2DeviceDisconnectedError(error)) return true;
|
|
419
|
+
const errorCode = getProtocolV2ErrorCode(error);
|
|
420
|
+
if (errorCode === HardwareErrorCode.BleTimeoutError) return true;
|
|
421
|
+
const message = getProtocolV2UnknownErrorText(error);
|
|
422
|
+
if (errorCode === HardwareErrorCode.TransportNotFound && /not connected/i.test(message)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return (
|
|
426
|
+
message.includes('ProtocolV2LinkError') ||
|
|
427
|
+
message.includes('Device protocol has not been detected') ||
|
|
428
|
+
message.includes('Electron BLE device state cleaned') ||
|
|
429
|
+
message.includes('Electron BLE transport released')
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
|
|
395
433
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
396
|
-
|
|
397
|
-
(error
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE));
|
|
434
|
+
error instanceof HardwareError &&
|
|
435
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
436
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
437
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
438
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
402
439
|
|
|
403
440
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
404
441
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -597,6 +634,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
597
634
|
|
|
598
635
|
private protocolV2LastTransferredBytes = 0;
|
|
599
636
|
|
|
637
|
+
private protocolV2FileWriteWindowDisabled = false;
|
|
638
|
+
|
|
600
639
|
init() {
|
|
601
640
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
602
641
|
this.requireDeviceMode = [];
|
|
@@ -786,7 +825,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
786
825
|
};
|
|
787
826
|
}
|
|
788
827
|
|
|
789
|
-
private getProtocolV2FirmwareChunkSize(
|
|
828
|
+
private getProtocolV2FirmwareChunkSize(
|
|
829
|
+
direction: 'read' | 'write',
|
|
830
|
+
filePath?: string,
|
|
831
|
+
fileWriteWindow = false
|
|
832
|
+
) {
|
|
790
833
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
791
834
|
const env = DataManager.getSettings('env');
|
|
792
835
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
@@ -794,6 +837,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
794
837
|
if (isBle) {
|
|
795
838
|
if (direction === 'read') {
|
|
796
839
|
maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
|
|
840
|
+
} else if (fileWriteWindow) {
|
|
841
|
+
// Skip the larger staging chunk so two in-flight frames fit the UART FIFO; see
|
|
842
|
+
// PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW.
|
|
843
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
797
844
|
} else {
|
|
798
845
|
// Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
|
|
799
846
|
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
@@ -1823,8 +1870,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1823
1870
|
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1824
1871
|
const chunks: Uint8Array[] = [];
|
|
1825
1872
|
let offset = 0;
|
|
1826
|
-
while (offset <
|
|
1827
|
-
const readLen = Math.min(chunkSize,
|
|
1873
|
+
while (offset < PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE) {
|
|
1874
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE - offset);
|
|
1828
1875
|
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1829
1876
|
file: {
|
|
1830
1877
|
path: filePath,
|
|
@@ -1835,7 +1882,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1835
1882
|
ui_percentage: undefined,
|
|
1836
1883
|
});
|
|
1837
1884
|
const data = toProtocolV2Bytes(res.message?.data);
|
|
1838
|
-
if (data.byteLength === 0) return null;
|
|
1885
|
+
if (data.byteLength === 0 || data.byteLength > readLen) return null;
|
|
1839
1886
|
chunks.push(data);
|
|
1840
1887
|
offset += data.byteLength;
|
|
1841
1888
|
}
|
|
@@ -1880,14 +1927,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1880
1927
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
1881
1928
|
return true;
|
|
1882
1929
|
} catch (error) {
|
|
1883
|
-
if (
|
|
1884
|
-
throw error;
|
|
1885
|
-
}
|
|
1930
|
+
if (isProtocolV2ResourceHeaderLinkError(error)) throw error;
|
|
1886
1931
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1887
1932
|
return false;
|
|
1888
1933
|
}
|
|
1889
1934
|
}
|
|
1890
1935
|
|
|
1936
|
+
/**
|
|
1937
|
+
* A lost link would fail every remaining header check and resend bundles that are already
|
|
1938
|
+
* installed, so reconnect (a bounded number of times per transfer phase) and ask again.
|
|
1939
|
+
* Header reads have no side effects. Once the budget is used up the bundle is resent as before.
|
|
1940
|
+
* A failed reconnect aborts the update like a failed file-transfer recovery: reconnecting
|
|
1941
|
+
* replaces the session, and nothing may be written to a device whose identity was not
|
|
1942
|
+
* re-verified.
|
|
1943
|
+
*/
|
|
1944
|
+
private async isProtocolV2ResourceBundleUpToDateWithRecovery(
|
|
1945
|
+
bundle: ProtocolV2ResourceBundleSource,
|
|
1946
|
+
recovery: { remaining: number }
|
|
1947
|
+
): Promise<boolean> {
|
|
1948
|
+
for (;;) {
|
|
1949
|
+
try {
|
|
1950
|
+
return await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
this.throwIfAborted();
|
|
1953
|
+
if (recovery.remaining <= 0) {
|
|
1954
|
+
Log.log(
|
|
1955
|
+
`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnect budget used up: `,
|
|
1956
|
+
getProtocolV2UnknownErrorText(error)
|
|
1957
|
+
);
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
recovery.remaining -= 1;
|
|
1961
|
+
Log.log(
|
|
1962
|
+
`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnecting: `,
|
|
1963
|
+
getProtocolV2UnknownErrorText(error)
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1967
|
+
this.throwIfAborted();
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1891
1971
|
private isProtocolV2BootloaderMode() {
|
|
1892
1972
|
if (typeof this.device.isBootloader === 'function') {
|
|
1893
1973
|
return !!this.device.isBootloader();
|
|
@@ -1917,6 +1997,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1917
1997
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
1918
1998
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1919
1999
|
await wait(1000);
|
|
2000
|
+
if (this.isBleReconnect()) {
|
|
2001
|
+
await this.device.deviceConnector?.disconnect(this.device.originalDescriptor.id);
|
|
2002
|
+
}
|
|
1920
2003
|
await this.waitForProtocolV2BootloaderMode();
|
|
1921
2004
|
this.protocolV2ExecutionInLoader = true;
|
|
1922
2005
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
@@ -1959,12 +2042,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1959
2042
|
while (Date.now() - startTime < timeout) {
|
|
1960
2043
|
try {
|
|
1961
2044
|
if (shouldReconnect) {
|
|
1962
|
-
|
|
2045
|
+
if (this.isBleReconnect()) {
|
|
2046
|
+
await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
2047
|
+
} else {
|
|
2048
|
+
await this.reconnectProtocolV2Device();
|
|
2049
|
+
}
|
|
1963
2050
|
shouldReconnect = false;
|
|
1964
2051
|
}
|
|
1965
2052
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1966
2053
|
commands: this.device.getCommands(),
|
|
1967
|
-
timeoutMs:
|
|
2054
|
+
timeoutMs: Math.min(
|
|
2055
|
+
this.getProtocolV2DeviceInfoTimeout(),
|
|
2056
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2057
|
+
),
|
|
1968
2058
|
});
|
|
1969
2059
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1970
2060
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1976,7 +2066,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1976
2066
|
}
|
|
1977
2067
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
1978
2068
|
} catch (error) {
|
|
1979
|
-
if (
|
|
2069
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1980
2070
|
throw error;
|
|
1981
2071
|
}
|
|
1982
2072
|
shouldReconnect = true;
|
|
@@ -2126,8 +2216,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2126
2216
|
}: ProtocolV2TransferBatch) {
|
|
2127
2217
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2128
2218
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2219
|
+
const headerRecovery = { remaining: PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT };
|
|
2129
2220
|
for (const resource of resourceSources) {
|
|
2130
|
-
if (await this.
|
|
2221
|
+
if (await this.isProtocolV2ResourceBundleUpToDateWithRecovery(resource, headerRecovery)) {
|
|
2131
2222
|
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2132
2223
|
} else {
|
|
2133
2224
|
resourcesToSync.push(resource);
|
|
@@ -2176,12 +2267,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2176
2267
|
}
|
|
2177
2268
|
|
|
2178
2269
|
if (totalSize > 0) {
|
|
2179
|
-
const elapsedMs =
|
|
2270
|
+
const elapsedMs = getProtocolV2ElapsedMs(transferStartedAt);
|
|
2271
|
+
const rateBytesPerSecond = getProtocolV2TransferRate(totalSize, elapsedMs);
|
|
2180
2272
|
this.postProgressMessage(100, 'transferData', {
|
|
2181
2273
|
transferredBytes: totalSize,
|
|
2182
2274
|
totalBytes: totalSize,
|
|
2183
|
-
rateBytesPerSecond
|
|
2275
|
+
rateBytesPerSecond,
|
|
2276
|
+
elapsedMs,
|
|
2277
|
+
});
|
|
2278
|
+
Log.debug('[FirmwareUpdateV4] Protocol V2 transfer completed', {
|
|
2279
|
+
files: resourcesToSync.length + installSources.length,
|
|
2280
|
+
totalBytes: totalSize,
|
|
2184
2281
|
elapsedMs,
|
|
2282
|
+
rateBytesPerSecond,
|
|
2185
2283
|
});
|
|
2186
2284
|
}
|
|
2187
2285
|
if (stagedInstallTargets.length === 0) {
|
|
@@ -2193,10 +2291,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2193
2291
|
target_id: item.targetId,
|
|
2194
2292
|
path: item.path,
|
|
2195
2293
|
}));
|
|
2294
|
+
// Includes the wait for on-device confirmation.
|
|
2295
|
+
const installStartedAt = Date.now();
|
|
2196
2296
|
try {
|
|
2197
2297
|
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2198
2298
|
await wait(PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY);
|
|
2199
2299
|
await this.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
2300
|
+
Log.debug('[FirmwareUpdateV4] Protocol V2 install completed', {
|
|
2301
|
+
targets: targets.length,
|
|
2302
|
+
elapsedMs: getProtocolV2ElapsedMs(installStartedAt),
|
|
2303
|
+
});
|
|
2200
2304
|
} finally {
|
|
2201
2305
|
this.device?.clearCancelableAction();
|
|
2202
2306
|
}
|
|
@@ -2215,69 +2319,121 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2215
2319
|
totalSize: number;
|
|
2216
2320
|
transferStartedAt?: number;
|
|
2217
2321
|
}) {
|
|
2322
|
+
const reportConfirmedChunk = (chunkEnd: number) => {
|
|
2323
|
+
const transferredBytes = processedSize + chunkEnd;
|
|
2324
|
+
const now = Date.now();
|
|
2325
|
+
const elapsedMs = getProtocolV2ElapsedMs(transferStartedAt, now);
|
|
2326
|
+
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2327
|
+
const shouldPostProgress =
|
|
2328
|
+
transferredBytes >= this.protocolV2LastTransferredBytes &&
|
|
2329
|
+
(progress !== this.protocolV2LastTransferProgress ||
|
|
2330
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2331
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2332
|
+
chunkEnd === source.size);
|
|
2333
|
+
if (shouldPostProgress) {
|
|
2334
|
+
this.protocolV2LastTransferProgress = progress;
|
|
2335
|
+
this.protocolV2LastTransferProgressAt = now;
|
|
2336
|
+
this.protocolV2LastTransferredBytes = transferredBytes;
|
|
2337
|
+
this.postProgressMessage(progress, 'transferData', {
|
|
2338
|
+
transferredBytes,
|
|
2339
|
+
totalBytes: totalSize,
|
|
2340
|
+
rateBytesPerSecond: getProtocolV2TransferRate(transferredBytes, elapsedMs),
|
|
2341
|
+
elapsedMs,
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2218
2345
|
let lastError: unknown;
|
|
2219
2346
|
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
2220
2347
|
this.throwIfAborted();
|
|
2348
|
+
const attemptStartedAt = Date.now();
|
|
2349
|
+
let windowed = false;
|
|
2350
|
+
let chunkSize = 0;
|
|
2351
|
+
let confirmedBytes = 0;
|
|
2352
|
+
let lastMetricsAt = attemptStartedAt;
|
|
2353
|
+
const logTransfer = (status: 'started' | 'progress' | 'completed' | 'failed') => {
|
|
2354
|
+
const elapsedMs = getProtocolV2ElapsedMs(attemptStartedAt);
|
|
2355
|
+
TransferLog.log('[FirmwareUpdateV4] FileWrite metrics', {
|
|
2356
|
+
status,
|
|
2357
|
+
path: filePath,
|
|
2358
|
+
fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
|
|
2359
|
+
chunkSize,
|
|
2360
|
+
attempt,
|
|
2361
|
+
retryCount: attempt - 1,
|
|
2362
|
+
transferredBytes: confirmedBytes,
|
|
2363
|
+
totalBytes: source.size,
|
|
2364
|
+
elapsedMs,
|
|
2365
|
+
rateBytesPerSecond: getProtocolV2TransferRate(confirmedBytes, elapsedMs),
|
|
2366
|
+
});
|
|
2367
|
+
};
|
|
2368
|
+
const reportAttemptChunk = (chunkEnd: number) => {
|
|
2369
|
+
confirmedBytes = chunkEnd;
|
|
2370
|
+
reportConfirmedChunk(chunkEnd);
|
|
2371
|
+
const now = Date.now();
|
|
2372
|
+
if (now - lastMetricsAt >= PROTOCOL_V2_TRANSFER_LOG_INTERVAL_MS && chunkEnd < source.size) {
|
|
2373
|
+
logTransfer('progress');
|
|
2374
|
+
lastMetricsAt = now;
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2221
2377
|
try {
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
2378
|
+
windowed =
|
|
2379
|
+
!this.protocolV2FileWriteWindowDisabled && this.shouldUseProtocolV2BleFileWriteWindow();
|
|
2380
|
+
chunkSize = this.getProtocolV2FirmwareChunkSize('write', filePath, windowed);
|
|
2381
|
+
logTransfer('started');
|
|
2382
|
+
if (windowed) {
|
|
2383
|
+
await this.writeProtocolV2SourceWindowed({
|
|
2384
|
+
source,
|
|
2385
|
+
filePath,
|
|
2386
|
+
chunkSize,
|
|
2387
|
+
processedSize,
|
|
2388
|
+
totalSize,
|
|
2389
|
+
onChunkConfirmed: reportAttemptChunk,
|
|
2390
|
+
});
|
|
2391
|
+
} else {
|
|
2392
|
+
await writeFirmwareByteSource({
|
|
2393
|
+
source,
|
|
2394
|
+
chunkSize,
|
|
2395
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
2396
|
+
this.throwIfAborted();
|
|
2397
|
+
const chunkEnd = sourceOffset + length;
|
|
2398
|
+
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
2399
|
+
processedSize + sourceOffset,
|
|
2400
|
+
processedSize + chunkEnd,
|
|
2401
|
+
totalSize
|
|
2247
2402
|
);
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
return length;
|
|
2272
|
-
},
|
|
2273
|
-
});
|
|
2403
|
+
const response = await this.fileWriteChunk(
|
|
2404
|
+
filePath,
|
|
2405
|
+
source.size,
|
|
2406
|
+
sourceOffset,
|
|
2407
|
+
data,
|
|
2408
|
+
first,
|
|
2409
|
+
deviceProgress
|
|
2410
|
+
);
|
|
2411
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
2412
|
+
const nextOffset =
|
|
2413
|
+
rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
2414
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
2415
|
+
throw ERRORS.TypedError(
|
|
2416
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2417
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
reportAttemptChunk(chunkEnd);
|
|
2421
|
+
return length;
|
|
2422
|
+
},
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
logTransfer('completed');
|
|
2274
2426
|
return processedSize + source.size;
|
|
2275
2427
|
} catch (error) {
|
|
2276
2428
|
this.throwIfAborted();
|
|
2277
|
-
if (isBleStaleBondHardwareError(error)) {
|
|
2278
|
-
throw error;
|
|
2279
|
-
}
|
|
2280
2429
|
lastError = error;
|
|
2430
|
+
logTransfer('failed');
|
|
2431
|
+
TransferLog.debug('[FirmwareUpdateV4] Protocol V2 file transfer attempt failed', {
|
|
2432
|
+
path: filePath,
|
|
2433
|
+
attempt,
|
|
2434
|
+
fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
|
|
2435
|
+
error: getProtocolV2UnknownErrorText(error),
|
|
2436
|
+
});
|
|
2281
2437
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2282
2438
|
await this.recoverProtocolV2FileTransfer();
|
|
2283
2439
|
this.throwIfAborted();
|
|
@@ -2944,9 +3100,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2944
3100
|
return;
|
|
2945
3101
|
}
|
|
2946
3102
|
} catch (error) {
|
|
2947
|
-
if (isBleStaleBondHardwareError(error)) {
|
|
2948
|
-
throw error;
|
|
2949
|
-
}
|
|
2950
3103
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2951
3104
|
}
|
|
2952
3105
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -3039,7 +3192,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3039
3192
|
'Protocol V2 device is still in bootloader mode'
|
|
3040
3193
|
);
|
|
3041
3194
|
} catch (error) {
|
|
3042
|
-
if (
|
|
3195
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
3043
3196
|
throw error;
|
|
3044
3197
|
}
|
|
3045
3198
|
shouldReconnect = true;
|
|
@@ -3212,15 +3365,129 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3212
3365
|
return writeRes;
|
|
3213
3366
|
}
|
|
3214
3367
|
|
|
3215
|
-
private
|
|
3368
|
+
private shouldUseProtocolV2BleFileWriteWindow(
|
|
3369
|
+
minBootloaderVersion = PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION
|
|
3370
|
+
) {
|
|
3216
3371
|
const env = DataManager.getSettings('env');
|
|
3217
|
-
|
|
3218
|
-
|
|
3372
|
+
// These adapters queue frames that arrive while no read is pending. LowLevel plugins are not
|
|
3373
|
+
// required to keep notifications between receive() calls, so they stay serial.
|
|
3374
|
+
if (
|
|
3375
|
+
(env !== 'react-native' && env !== 'desktop-web-ble') ||
|
|
3376
|
+
!this.isProtocolV2BootloaderMode()
|
|
3377
|
+
) {
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
// The transfer runs in the device's current bootloader, not the one being installed. The
|
|
3381
|
+
// parsed version keeps major.minor.patch only, so a 1.0.3 prerelease counts as 1.0.3.
|
|
3382
|
+
const bootloaderVersion = getDeviceBootloaderVersion(this.device.features).join('.');
|
|
3383
|
+
const enabled = supportsProtocolV2BleFileWriteWindow(bootloaderVersion, minBootloaderVersion);
|
|
3384
|
+
TransferLog.debug('[FirmwareUpdateV4] BLE FileWrite window', {
|
|
3385
|
+
enabled,
|
|
3386
|
+
bootloaderVersion,
|
|
3387
|
+
bootloaderBuildId: this.device.features?.verify?.bootloaderBuildId,
|
|
3388
|
+
});
|
|
3389
|
+
return enabled;
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
/**
|
|
3393
|
+
* Streams one staged file as a single ordered FilesystemFileWrite pipeline. Every chunk is
|
|
3394
|
+
* still confirmed by the device; a failure restarts the whole file like the serial path.
|
|
3395
|
+
* Cancellation and source errors only stop the pipeline and are raised after the transport
|
|
3396
|
+
* call returns: an error thrown inside it would invalidate the BLE link.
|
|
3397
|
+
*/
|
|
3398
|
+
private async writeProtocolV2SourceWindowed({
|
|
3399
|
+
source,
|
|
3400
|
+
filePath,
|
|
3401
|
+
chunkSize,
|
|
3402
|
+
processedSize,
|
|
3403
|
+
totalSize,
|
|
3404
|
+
onChunkConfirmed,
|
|
3405
|
+
}: {
|
|
3406
|
+
source: FirmwareByteSource;
|
|
3407
|
+
filePath: string;
|
|
3408
|
+
chunkSize: number;
|
|
3409
|
+
processedSize: number;
|
|
3410
|
+
totalSize: number;
|
|
3411
|
+
onChunkConfirmed: (chunkEnd: number) => void;
|
|
3412
|
+
}) {
|
|
3413
|
+
let sendOffset = 0;
|
|
3414
|
+
let sourceError: Error | undefined;
|
|
3415
|
+
const readRequest = async () => {
|
|
3416
|
+
const offset = sendOffset;
|
|
3417
|
+
const length = Math.min(chunkSize, source.size - offset);
|
|
3418
|
+
const data = await source.readAt(offset, length);
|
|
3419
|
+
sendOffset += length;
|
|
3420
|
+
return {
|
|
3421
|
+
file: { path: filePath, offset, total_size: source.size, data },
|
|
3422
|
+
overwrite: offset === 0,
|
|
3423
|
+
append: false,
|
|
3424
|
+
ui_percentage: getProtocolV2DeviceTransferProgress(
|
|
3425
|
+
processedSize + offset,
|
|
3426
|
+
processedSize + sendOffset,
|
|
3427
|
+
totalSize
|
|
3428
|
+
),
|
|
3429
|
+
};
|
|
3430
|
+
};
|
|
3431
|
+
const nextRequest = async () => {
|
|
3432
|
+
if (this.abortSignal?.aborted || sendOffset >= source.size) return undefined;
|
|
3433
|
+
try {
|
|
3434
|
+
return await readRequest();
|
|
3435
|
+
} catch (error) {
|
|
3436
|
+
sourceError = error;
|
|
3437
|
+
return undefined;
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
this.throwIfAborted();
|
|
3441
|
+
if (source.size === 0) return;
|
|
3442
|
+
const firstRequest = await readRequest();
|
|
3443
|
+
|
|
3444
|
+
// A failed exchange disables the window for the rest of this update. Source read
|
|
3445
|
+
// failures are raised separately and do not change the bootloader capability decision.
|
|
3446
|
+
const response = await this.device
|
|
3447
|
+
.getCommands()
|
|
3448
|
+
.typedCall('FilesystemFileWrite', 'FilesystemFile', firstRequest, {
|
|
3449
|
+
// Both windowed transports (React Native and Electron BLE) invalidate a timed-out link,
|
|
3450
|
+
// so a lost chunk is retried after 10 s instead of the 5-minute default.
|
|
3451
|
+
timeoutMs: PROTOCOL_V2_FILE_WRITE_RESPONSE_TIMEOUT,
|
|
3452
|
+
writeWithResponse: false,
|
|
3453
|
+
fileWritePipeline: {
|
|
3454
|
+
next: nextRequest,
|
|
3455
|
+
onResponse: confirmed => {
|
|
3456
|
+
onChunkConfirmed(Number(confirmed.message.processed_byte));
|
|
3457
|
+
},
|
|
3458
|
+
},
|
|
3459
|
+
})
|
|
3460
|
+
.catch((error: unknown) => {
|
|
3461
|
+
this.protocolV2FileWriteWindowDisabled = true;
|
|
3462
|
+
throw error;
|
|
3463
|
+
});
|
|
3464
|
+
this.throwIfAborted();
|
|
3465
|
+
if (sourceError) throw sourceError;
|
|
3466
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
3467
|
+
if (Number(rawProcessedByte) !== source.size) {
|
|
3468
|
+
this.protocolV2FileWriteWindowDisabled = true;
|
|
3469
|
+
throw ERRORS.TypedError(
|
|
3470
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
3471
|
+
`invalid processed_byte ${rawProcessedByte} for file size ${source.size}`
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
private async recoverProtocolV2FileTransfer() {
|
|
3477
|
+
try {
|
|
3478
|
+
const env = DataManager.getSettings('env');
|
|
3479
|
+
if (DataManager.isBleConnect(env)) {
|
|
3480
|
+
await wait(3000);
|
|
3481
|
+
}
|
|
3482
|
+
await this.reconnectProtocolV2Device();
|
|
3483
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
3484
|
+
await this.device.initialize();
|
|
3485
|
+
await wait(2000);
|
|
3486
|
+
} catch (error) {
|
|
3487
|
+
// A cancel disposes the link mid-recovery; report the cancel rather than its side effect.
|
|
3488
|
+
this.throwIfAborted();
|
|
3489
|
+
throw error;
|
|
3219
3490
|
}
|
|
3220
|
-
await this.reconnectProtocolV2Device();
|
|
3221
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
3222
|
-
await this.device.initialize();
|
|
3223
|
-
await wait(2000);
|
|
3224
3491
|
}
|
|
3225
3492
|
|
|
3226
3493
|
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ERRORS, HardwareErrorCode
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import { UI_REQUEST } from '../constants/ui-request';
|
|
4
4
|
import { BaseMethod } from './BaseMethod';
|
|
@@ -49,10 +49,7 @@ export default class GetDeviceState extends BaseMethod<
|
|
|
49
49
|
this.device.isProtocolV2() &&
|
|
50
50
|
isLoaderMode(state.status.mode)
|
|
51
51
|
) {
|
|
52
|
-
throw
|
|
53
|
-
`${this.name}:${this.params.scope}`,
|
|
54
|
-
this.device.getCurrentFirmwareType()
|
|
55
|
-
);
|
|
52
|
+
throw ERRORS.TypedError(HardwareErrorCode.NotAllowInBootloaderMode);
|
|
56
53
|
}
|
|
57
54
|
return state;
|
|
58
55
|
}
|