@onekeyfe/hd-core 1.2.8-alpha.120 → 1.2.8-alpha.121
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__/protocol-v2.test.ts +0 -542
- package/dist/api/FirmwareUpdateV4.d.ts +0 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/index.js +46 -225
- package/dist/utils/logger.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +61 -314
- package/src/utils/logger.ts +0 -4
- package/__tests__/logger.test.ts +0 -18
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
|
-
import semver from 'semver';
|
|
4
3
|
import {
|
|
5
4
|
DeviceRebootType,
|
|
6
5
|
DeviceSessionPinType,
|
|
7
6
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
8
7
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
9
8
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
10
|
-
PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW,
|
|
11
9
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
12
10
|
} from '@onekeyfe/hd-transport';
|
|
13
11
|
import { sha256 } from '@noble/hashes/sha256';
|
|
@@ -103,14 +101,6 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
103
101
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
104
102
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
105
103
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
106
|
-
const PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT = 2;
|
|
107
|
-
// Minimum running bootloader that receives two in-flight BLE FilesystemFileWrite frames.
|
|
108
|
-
// Bootloaders with the 2048-byte BLE UART FIFO have no room beside one waiting FileWrite frame,
|
|
109
|
-
// so a second frame can overflow it. Released bootloaders (1.0.2 with that FIFO, and 1.0.3) stay
|
|
110
|
-
// serial. Firmware has been asked to ship the 4096-byte FIFO from 1.0.4 but has not confirmed it,
|
|
111
|
-
// nor the BLE coprocessor margin for two frames; any bootloader reporting 1.0.4 or later gets the
|
|
112
|
-
// window. Set it to undefined to keep every transfer serial.
|
|
113
|
-
const PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION: string | undefined = '1.0.4';
|
|
114
104
|
const PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS = 1000;
|
|
115
105
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
116
106
|
const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
|
|
@@ -180,18 +170,6 @@ export function assertProtocolV2FirmwareTargetsSupported(
|
|
|
180
170
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotSupportMethod, message);
|
|
181
171
|
}
|
|
182
172
|
|
|
183
|
-
/** Whether the running bootloader accepts a second in-flight BLE FilesystemFileWrite. */
|
|
184
|
-
export function supportsProtocolV2BleFileWriteWindow(
|
|
185
|
-
bootloaderVersion: string,
|
|
186
|
-
minBootloaderVersion = PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION
|
|
187
|
-
) {
|
|
188
|
-
return (
|
|
189
|
-
!!minBootloaderVersion &&
|
|
190
|
-
!!semver.valid(bootloaderVersion) &&
|
|
191
|
-
semver.gte(bootloaderVersion, minBootloaderVersion)
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
173
|
const getProtocolV2DeviceTransferProgress = (
|
|
196
174
|
bytesBeforeChunk: number,
|
|
197
175
|
bytesAfterChunk: number,
|
|
@@ -206,12 +184,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
206
184
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
207
185
|
};
|
|
208
186
|
|
|
209
|
-
const getProtocolV2TransferRate = (bytes: number, elapsedMs: number) =>
|
|
210
|
-
elapsedMs > 0 ? Math.round((bytes / elapsedMs) * 1000) : undefined;
|
|
211
|
-
|
|
212
|
-
const getProtocolV2ElapsedMs = (startedAt: number, now = Date.now()) =>
|
|
213
|
-
Math.max(now - startedAt, 0);
|
|
214
|
-
|
|
215
187
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
216
188
|
target_id: number | string;
|
|
217
189
|
status?: number | string;
|
|
@@ -410,23 +382,6 @@ const getProtocolV2ErrorCode = (error: unknown): number | string | undefined =>
|
|
|
410
382
|
return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
|
|
411
383
|
};
|
|
412
384
|
|
|
413
|
-
/** Errors that mean the link is gone, so the read says nothing about the installed bundle. */
|
|
414
|
-
const isProtocolV2ResourceHeaderLinkError = (error: unknown) => {
|
|
415
|
-
if (isProtocolV2DeviceDisconnectedError(error)) return true;
|
|
416
|
-
const errorCode = getProtocolV2ErrorCode(error);
|
|
417
|
-
if (errorCode === HardwareErrorCode.BleTimeoutError) return true;
|
|
418
|
-
const message = getProtocolV2UnknownErrorText(error);
|
|
419
|
-
if (errorCode === HardwareErrorCode.TransportNotFound && /not connected/i.test(message)) {
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
return (
|
|
423
|
-
message.includes('ProtocolV2LinkError') ||
|
|
424
|
-
message.includes('Device protocol has not been detected') ||
|
|
425
|
-
message.includes('Electron BLE device state cleaned') ||
|
|
426
|
-
message.includes('Electron BLE transport released')
|
|
427
|
-
);
|
|
428
|
-
};
|
|
429
|
-
|
|
430
385
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
431
386
|
error instanceof HardwareError &&
|
|
432
387
|
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
@@ -631,9 +586,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
631
586
|
|
|
632
587
|
private protocolV2LastTransferredBytes = 0;
|
|
633
588
|
|
|
634
|
-
/** Set when a windowed FileWrite attempt fails: the rest of this update writes serially. */
|
|
635
|
-
private protocolV2FileWriteWindowDisabled = false;
|
|
636
|
-
|
|
637
589
|
init() {
|
|
638
590
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
639
591
|
this.requireDeviceMode = [];
|
|
@@ -823,11 +775,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
823
775
|
};
|
|
824
776
|
}
|
|
825
777
|
|
|
826
|
-
private getProtocolV2FirmwareChunkSize(
|
|
827
|
-
direction: 'read' | 'write',
|
|
828
|
-
filePath?: string,
|
|
829
|
-
fileWriteWindow = false
|
|
830
|
-
) {
|
|
778
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write', filePath?: string) {
|
|
831
779
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
832
780
|
const env = DataManager.getSettings('env');
|
|
833
781
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
@@ -835,10 +783,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
835
783
|
if (isBle) {
|
|
836
784
|
if (direction === 'read') {
|
|
837
785
|
maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
|
|
838
|
-
} else if (fileWriteWindow) {
|
|
839
|
-
// Skip the larger staging chunk so two in-flight frames fit the UART FIFO; see
|
|
840
|
-
// PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW.
|
|
841
|
-
maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
842
786
|
} else {
|
|
843
787
|
// Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
|
|
844
788
|
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
@@ -1925,47 +1869,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1925
1869
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
1926
1870
|
return true;
|
|
1927
1871
|
} catch (error) {
|
|
1928
|
-
if (isProtocolV2ResourceHeaderLinkError(error)) throw error;
|
|
1929
1872
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1930
1873
|
return false;
|
|
1931
1874
|
}
|
|
1932
1875
|
}
|
|
1933
1876
|
|
|
1934
|
-
/**
|
|
1935
|
-
* A lost link would fail every remaining header check and resend bundles that are already
|
|
1936
|
-
* installed, so reconnect (a bounded number of times per transfer phase) and ask again.
|
|
1937
|
-
* Header reads have no side effects. Once the budget is used up the bundle is resent as before.
|
|
1938
|
-
* A failed reconnect aborts the update like a failed file-transfer recovery: reconnecting
|
|
1939
|
-
* replaces the session, and nothing may be written to a device whose identity was not
|
|
1940
|
-
* re-verified.
|
|
1941
|
-
*/
|
|
1942
|
-
private async isProtocolV2ResourceBundleUpToDateWithRecovery(
|
|
1943
|
-
bundle: ProtocolV2ResourceBundleSource,
|
|
1944
|
-
recovery: { remaining: number }
|
|
1945
|
-
): Promise<boolean> {
|
|
1946
|
-
for (;;) {
|
|
1947
|
-
try {
|
|
1948
|
-
return await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
1949
|
-
} catch (error) {
|
|
1950
|
-
this.throwIfAborted();
|
|
1951
|
-
if (recovery.remaining <= 0) {
|
|
1952
|
-
Log.log(
|
|
1953
|
-
`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnect budget used up: `,
|
|
1954
|
-
getProtocolV2UnknownErrorText(error)
|
|
1955
|
-
);
|
|
1956
|
-
return false;
|
|
1957
|
-
}
|
|
1958
|
-
recovery.remaining -= 1;
|
|
1959
|
-
Log.log(
|
|
1960
|
-
`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnecting: `,
|
|
1961
|
-
getProtocolV2UnknownErrorText(error)
|
|
1962
|
-
);
|
|
1963
|
-
}
|
|
1964
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1965
|
-
this.throwIfAborted();
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
1877
|
private isProtocolV2BootloaderMode() {
|
|
1970
1878
|
if (typeof this.device.isBootloader === 'function') {
|
|
1971
1879
|
return !!this.device.isBootloader();
|
|
@@ -2204,9 +2112,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2204
2112
|
}: ProtocolV2TransferBatch) {
|
|
2205
2113
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2206
2114
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2207
|
-
const headerRecovery = { remaining: PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT };
|
|
2208
2115
|
for (const resource of resourceSources) {
|
|
2209
|
-
if (await this.
|
|
2116
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
2210
2117
|
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2211
2118
|
} else {
|
|
2212
2119
|
resourcesToSync.push(resource);
|
|
@@ -2255,19 +2162,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2255
2162
|
}
|
|
2256
2163
|
|
|
2257
2164
|
if (totalSize > 0) {
|
|
2258
|
-
const elapsedMs =
|
|
2259
|
-
const rateBytesPerSecond = getProtocolV2TransferRate(totalSize, elapsedMs);
|
|
2165
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
2260
2166
|
this.postProgressMessage(100, 'transferData', {
|
|
2261
2167
|
transferredBytes: totalSize,
|
|
2262
2168
|
totalBytes: totalSize,
|
|
2263
|
-
rateBytesPerSecond,
|
|
2264
|
-
elapsedMs,
|
|
2265
|
-
});
|
|
2266
|
-
Log.debug('[FirmwareUpdateV4] Protocol V2 transfer completed', {
|
|
2267
|
-
files: resourcesToSync.length + installSources.length,
|
|
2268
|
-
totalBytes: totalSize,
|
|
2169
|
+
rateBytesPerSecond: elapsedMs > 0 ? Math.round((totalSize / elapsedMs) * 1000) : undefined,
|
|
2269
2170
|
elapsedMs,
|
|
2270
|
-
rateBytesPerSecond,
|
|
2271
2171
|
});
|
|
2272
2172
|
}
|
|
2273
2173
|
if (stagedInstallTargets.length === 0) {
|
|
@@ -2279,16 +2179,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2279
2179
|
target_id: item.targetId,
|
|
2280
2180
|
path: item.path,
|
|
2281
2181
|
}));
|
|
2282
|
-
// Includes the wait for on-device confirmation.
|
|
2283
|
-
const installStartedAt = Date.now();
|
|
2284
2182
|
try {
|
|
2285
2183
|
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2286
2184
|
await wait(PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY);
|
|
2287
2185
|
await this.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
2288
|
-
Log.debug('[FirmwareUpdateV4] Protocol V2 install completed', {
|
|
2289
|
-
targets: targets.length,
|
|
2290
|
-
elapsedMs: getProtocolV2ElapsedMs(installStartedAt),
|
|
2291
|
-
});
|
|
2292
2186
|
} finally {
|
|
2293
2187
|
this.device?.clearCancelableAction();
|
|
2294
2188
|
}
|
|
@@ -2307,98 +2201,66 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2307
2201
|
totalSize: number;
|
|
2308
2202
|
transferStartedAt?: number;
|
|
2309
2203
|
}) {
|
|
2310
|
-
const reportConfirmedChunk = (chunkEnd: number) => {
|
|
2311
|
-
const transferredBytes = processedSize + chunkEnd;
|
|
2312
|
-
const now = Date.now();
|
|
2313
|
-
const elapsedMs = getProtocolV2ElapsedMs(transferStartedAt, now);
|
|
2314
|
-
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2315
|
-
const shouldPostProgress =
|
|
2316
|
-
transferredBytes >= this.protocolV2LastTransferredBytes &&
|
|
2317
|
-
(progress !== this.protocolV2LastTransferProgress ||
|
|
2318
|
-
now - this.protocolV2LastTransferProgressAt >=
|
|
2319
|
-
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2320
|
-
chunkEnd === source.size);
|
|
2321
|
-
if (shouldPostProgress) {
|
|
2322
|
-
this.protocolV2LastTransferProgress = progress;
|
|
2323
|
-
this.protocolV2LastTransferProgressAt = now;
|
|
2324
|
-
this.protocolV2LastTransferredBytes = transferredBytes;
|
|
2325
|
-
this.postProgressMessage(progress, 'transferData', {
|
|
2326
|
-
transferredBytes,
|
|
2327
|
-
totalBytes: totalSize,
|
|
2328
|
-
rateBytesPerSecond: getProtocolV2TransferRate(transferredBytes, elapsedMs),
|
|
2329
|
-
elapsedMs,
|
|
2330
|
-
});
|
|
2331
|
-
}
|
|
2332
|
-
};
|
|
2333
2204
|
let lastError: unknown;
|
|
2334
2205
|
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
2335
2206
|
this.throwIfAborted();
|
|
2336
|
-
const attemptStartedAt = Date.now();
|
|
2337
|
-
let windowed = false;
|
|
2338
2207
|
try {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
filePath,
|
|
2365
|
-
source.size,
|
|
2366
|
-
sourceOffset,
|
|
2367
|
-
data,
|
|
2368
|
-
first,
|
|
2369
|
-
deviceProgress
|
|
2208
|
+
await writeFirmwareByteSource({
|
|
2209
|
+
source,
|
|
2210
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
2211
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
2212
|
+
this.throwIfAborted();
|
|
2213
|
+
const chunkEnd = sourceOffset + length;
|
|
2214
|
+
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
2215
|
+
processedSize + sourceOffset,
|
|
2216
|
+
processedSize + chunkEnd,
|
|
2217
|
+
totalSize
|
|
2218
|
+
);
|
|
2219
|
+
const response = await this.fileWriteChunk(
|
|
2220
|
+
filePath,
|
|
2221
|
+
source.size,
|
|
2222
|
+
sourceOffset,
|
|
2223
|
+
data,
|
|
2224
|
+
first,
|
|
2225
|
+
deviceProgress
|
|
2226
|
+
);
|
|
2227
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
2228
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
2229
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
2230
|
+
throw ERRORS.TypedError(
|
|
2231
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2232
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
2370
2233
|
);
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2234
|
+
}
|
|
2235
|
+
const transferredBytes = processedSize + chunkEnd;
|
|
2236
|
+
const now = Date.now();
|
|
2237
|
+
const elapsedMs = Math.max(now - transferStartedAt, 0);
|
|
2238
|
+
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2239
|
+
const shouldPostProgress =
|
|
2240
|
+
transferredBytes >= this.protocolV2LastTransferredBytes &&
|
|
2241
|
+
(progress !== this.protocolV2LastTransferProgress ||
|
|
2242
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2243
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2244
|
+
chunkEnd === source.size);
|
|
2245
|
+
if (shouldPostProgress) {
|
|
2246
|
+
this.protocolV2LastTransferProgress = progress;
|
|
2247
|
+
this.protocolV2LastTransferProgressAt = now;
|
|
2248
|
+
this.protocolV2LastTransferredBytes = transferredBytes;
|
|
2249
|
+
this.postProgressMessage(progress, 'transferData', {
|
|
2250
|
+
transferredBytes,
|
|
2251
|
+
totalBytes: totalSize,
|
|
2252
|
+
rateBytesPerSecond:
|
|
2253
|
+
elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
|
|
2254
|
+
elapsedMs,
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
return length;
|
|
2258
|
+
},
|
|
2391
2259
|
});
|
|
2392
2260
|
return processedSize + source.size;
|
|
2393
2261
|
} catch (error) {
|
|
2394
2262
|
this.throwIfAborted();
|
|
2395
2263
|
lastError = error;
|
|
2396
|
-
Log.debug('[FirmwareUpdateV4] Protocol V2 file transfer attempt failed', {
|
|
2397
|
-
path: filePath,
|
|
2398
|
-
attempt,
|
|
2399
|
-
fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
|
|
2400
|
-
error: getProtocolV2UnknownErrorText(error),
|
|
2401
|
-
});
|
|
2402
2264
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2403
2265
|
await this.recoverProtocolV2FileTransfer();
|
|
2404
2266
|
this.throwIfAborted();
|
|
@@ -3330,130 +3192,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3330
3192
|
return writeRes;
|
|
3331
3193
|
}
|
|
3332
3194
|
|
|
3333
|
-
private shouldUseProtocolV2BleFileWriteWindow(
|
|
3334
|
-
minBootloaderVersion = PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION
|
|
3335
|
-
) {
|
|
3336
|
-
const env = DataManager.getSettings('env');
|
|
3337
|
-
// These adapters queue frames that arrive while no read is pending. LowLevel plugins are not
|
|
3338
|
-
// required to keep notifications between receive() calls, so they stay serial.
|
|
3339
|
-
if (
|
|
3340
|
-
(env !== 'react-native' && env !== 'desktop-web-ble') ||
|
|
3341
|
-
!this.isProtocolV2BootloaderMode()
|
|
3342
|
-
) {
|
|
3343
|
-
return false;
|
|
3344
|
-
}
|
|
3345
|
-
// The transfer runs in the device's current bootloader, not the one being installed. The
|
|
3346
|
-
// parsed version keeps major.minor.patch only, so a 1.0.4 prerelease counts as 1.0.4.
|
|
3347
|
-
const bootloaderVersion = getDeviceBootloaderVersion(this.device.features).join('.');
|
|
3348
|
-
const enabled = supportsProtocolV2BleFileWriteWindow(bootloaderVersion, minBootloaderVersion);
|
|
3349
|
-
Log.debug('[FirmwareUpdateV4] BLE FileWrite window', {
|
|
3350
|
-
enabled,
|
|
3351
|
-
bootloaderVersion,
|
|
3352
|
-
bootloaderBuildId: this.device.features?.verify?.bootloaderBuildId,
|
|
3353
|
-
});
|
|
3354
|
-
return enabled;
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
/**
|
|
3358
|
-
* Streams one staged file as a single ordered FilesystemFileWrite pipeline. Every chunk is
|
|
3359
|
-
* still confirmed by the device; a failure restarts the whole file like the serial path.
|
|
3360
|
-
* Cancellation and source errors only stop the pipeline and are raised after the transport
|
|
3361
|
-
* call returns: an error thrown inside it would invalidate the BLE link.
|
|
3362
|
-
*/
|
|
3363
|
-
private async writeProtocolV2SourceWindowed({
|
|
3364
|
-
source,
|
|
3365
|
-
filePath,
|
|
3366
|
-
chunkSize,
|
|
3367
|
-
processedSize,
|
|
3368
|
-
totalSize,
|
|
3369
|
-
onChunkConfirmed,
|
|
3370
|
-
}: {
|
|
3371
|
-
source: FirmwareByteSource;
|
|
3372
|
-
filePath: string;
|
|
3373
|
-
chunkSize: number;
|
|
3374
|
-
processedSize: number;
|
|
3375
|
-
totalSize: number;
|
|
3376
|
-
onChunkConfirmed: (chunkEnd: number) => void;
|
|
3377
|
-
}) {
|
|
3378
|
-
let sendOffset = 0;
|
|
3379
|
-
let sourceError: Error | undefined;
|
|
3380
|
-
const readRequest = async () => {
|
|
3381
|
-
const offset = sendOffset;
|
|
3382
|
-
const length = Math.min(chunkSize, source.size - offset);
|
|
3383
|
-
const data = await source.readAt(offset, length);
|
|
3384
|
-
sendOffset += length;
|
|
3385
|
-
return {
|
|
3386
|
-
file: { path: filePath, offset, total_size: source.size, data },
|
|
3387
|
-
overwrite: offset === 0,
|
|
3388
|
-
append: false,
|
|
3389
|
-
ui_percentage: getProtocolV2DeviceTransferProgress(
|
|
3390
|
-
processedSize + offset,
|
|
3391
|
-
processedSize + sendOffset,
|
|
3392
|
-
totalSize
|
|
3393
|
-
),
|
|
3394
|
-
};
|
|
3395
|
-
};
|
|
3396
|
-
const nextRequest = async () => {
|
|
3397
|
-
if (this.abortSignal?.aborted || sendOffset >= source.size) return undefined;
|
|
3398
|
-
try {
|
|
3399
|
-
return await readRequest();
|
|
3400
|
-
} catch (error) {
|
|
3401
|
-
sourceError = error;
|
|
3402
|
-
return undefined;
|
|
3403
|
-
}
|
|
3404
|
-
};
|
|
3405
|
-
this.throwIfAborted();
|
|
3406
|
-
if (source.size === 0) return;
|
|
3407
|
-
const firstRequest = await readRequest();
|
|
3408
|
-
|
|
3409
|
-
// A failed exchange may mean the running bootloader cannot hold two in-flight frames, so the
|
|
3410
|
-
// retry and every later file of this update are written serially. Source read errors say
|
|
3411
|
-
// nothing about the bootloader and keep the window.
|
|
3412
|
-
const response = await this.device
|
|
3413
|
-
.getCommands()
|
|
3414
|
-
.typedCall('FilesystemFileWrite', 'FilesystemFile', firstRequest, {
|
|
3415
|
-
// Both windowed transports (React Native and Electron BLE) invalidate a timed-out link,
|
|
3416
|
-
// so a lost chunk is retried after 10 s instead of the 5-minute default.
|
|
3417
|
-
timeoutMs: PROTOCOL_V2_FILE_WRITE_RESPONSE_TIMEOUT,
|
|
3418
|
-
writeWithResponse: false,
|
|
3419
|
-
fileWritePipeline: {
|
|
3420
|
-
next: nextRequest,
|
|
3421
|
-
onResponse: confirmed => {
|
|
3422
|
-
onChunkConfirmed(Number(confirmed.message.processed_byte));
|
|
3423
|
-
},
|
|
3424
|
-
},
|
|
3425
|
-
})
|
|
3426
|
-
.catch((error: unknown) => {
|
|
3427
|
-
this.protocolV2FileWriteWindowDisabled = true;
|
|
3428
|
-
throw error;
|
|
3429
|
-
});
|
|
3430
|
-
this.throwIfAborted();
|
|
3431
|
-
if (sourceError) throw sourceError;
|
|
3432
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
3433
|
-
if (Number(rawProcessedByte) !== source.size) {
|
|
3434
|
-
this.protocolV2FileWriteWindowDisabled = true;
|
|
3435
|
-
throw ERRORS.TypedError(
|
|
3436
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
3437
|
-
`invalid processed_byte ${rawProcessedByte} for file size ${source.size}`
|
|
3438
|
-
);
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
|
|
3442
3195
|
private async recoverProtocolV2FileTransfer() {
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
await wait(3000);
|
|
3447
|
-
}
|
|
3448
|
-
await this.reconnectProtocolV2Device();
|
|
3449
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
3450
|
-
await this.device.initialize();
|
|
3451
|
-
await wait(2000);
|
|
3452
|
-
} catch (error) {
|
|
3453
|
-
// A cancel disposes the link mid-recovery; report the cancel rather than its side effect.
|
|
3454
|
-
this.throwIfAborted();
|
|
3455
|
-
throw error;
|
|
3196
|
+
const env = DataManager.getSettings('env');
|
|
3197
|
+
if (DataManager.isBleConnect(env)) {
|
|
3198
|
+
await wait(3000);
|
|
3456
3199
|
}
|
|
3200
|
+
await this.reconnectProtocolV2Device();
|
|
3201
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
3202
|
+
await this.device.initialize();
|
|
3203
|
+
await wait(2000);
|
|
3457
3204
|
}
|
|
3458
3205
|
|
|
3459
3206
|
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
package/src/utils/logger.ts
CHANGED
|
@@ -78,10 +78,6 @@ class Log {
|
|
|
78
78
|
const _logs: { [k: string]: Log } = {};
|
|
79
79
|
|
|
80
80
|
export const initLog = (prefix: string, enabled?: boolean) => {
|
|
81
|
-
// Loggers that share a prefix must share one instance; otherwise enableLog only reaches the
|
|
82
|
-
// last one registered and the others never print.
|
|
83
|
-
const existing = _logs[prefix];
|
|
84
|
-
if (existing) return existing;
|
|
85
81
|
const instance = new Log(prefix, !!enabled);
|
|
86
82
|
_logs[prefix] = instance;
|
|
87
83
|
return instance;
|
package/__tests__/logger.test.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { LoggerNames, enableLog, getLogger } from '../src/utils/logger';
|
|
2
|
-
|
|
3
|
-
describe('logger', () => {
|
|
4
|
-
afterEach(() => {
|
|
5
|
-
enableLog(false);
|
|
6
|
-
jest.restoreAllMocks();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
test('enables method logs that share a prefix with common-connect-sdk', () => {
|
|
10
|
-
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
11
|
-
enableLog(true);
|
|
12
|
-
|
|
13
|
-
expect(getLogger(LoggerNames.Method)).toBe(getLogger(LoggerNames.HdCommonConnectSdk));
|
|
14
|
-
getLogger(LoggerNames.Method).debug('shared prefix message');
|
|
15
|
-
|
|
16
|
-
expect(consoleSpy).toHaveBeenCalledWith(LoggerNames.Method, 'shared prefix message');
|
|
17
|
-
});
|
|
18
|
-
});
|