@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.11
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 +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +86 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session.test.ts +781 -69
- package/__tests__/pro2HostAssetPackage.test.ts +165 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +925 -97
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.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/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2511 -1538
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.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/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +474 -83
- package/src/api/OpenWalletSession.ts +39 -12
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +164 -26
- package/src/data/messages/messages-protocol-v2.json +1589 -1375
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/logBlockEvent.ts +6 -75
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +223 -56
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +400 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EDeviceType,
|
|
3
|
+
ERRORS,
|
|
4
|
+
HardwareError,
|
|
5
|
+
HardwareErrorCode,
|
|
6
|
+
isBleStaleBondHardwareError,
|
|
7
|
+
wait,
|
|
8
|
+
} from '@onekeyfe/hd-shared';
|
|
2
9
|
import JSZip from 'jszip';
|
|
3
10
|
import {
|
|
4
11
|
DeviceRebootType,
|
|
@@ -44,6 +51,7 @@ import {
|
|
|
44
51
|
getProtocolV2UnknownErrorText,
|
|
45
52
|
isProtocolV2DeviceDisconnectedError,
|
|
46
53
|
} from './protocol-v2/helpers';
|
|
54
|
+
import { isProtocolV2ResponseTimeout } from './helpers/protocolV2FileWrite';
|
|
47
55
|
import {
|
|
48
56
|
openFirmwareByteSource,
|
|
49
57
|
readFirmwareByteSourceFully,
|
|
@@ -55,6 +63,7 @@ import {
|
|
|
55
63
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
56
64
|
validateFirmwareUpdatePreparedPlan,
|
|
57
65
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
66
|
+
|
|
58
67
|
import type {
|
|
59
68
|
FirmwareArtifactReference,
|
|
60
69
|
FirmwareUpdateV4Params,
|
|
@@ -84,6 +93,7 @@ const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
|
84
93
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
85
94
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
86
95
|
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
96
|
+
const PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS = 4;
|
|
87
97
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
88
98
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
89
99
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -182,6 +192,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
182
192
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
183
193
|
target_id: number | string;
|
|
184
194
|
status?: number | string;
|
|
195
|
+
progress_percent?: number;
|
|
196
|
+
phase_info?: {
|
|
197
|
+
phase: number | string;
|
|
198
|
+
progress_percent: number;
|
|
199
|
+
};
|
|
185
200
|
payload_version?: number;
|
|
186
201
|
path?: string;
|
|
187
202
|
};
|
|
@@ -359,12 +374,29 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
359
374
|
);
|
|
360
375
|
};
|
|
361
376
|
|
|
377
|
+
const isProtocolV2InstallResponseTimeout = (error: unknown) => {
|
|
378
|
+
if (!error || typeof error !== 'object') return false;
|
|
379
|
+
const { code } = error as { code?: number | string };
|
|
380
|
+
return (
|
|
381
|
+
code === 'response-timeout' ||
|
|
382
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const getProtocolV2ErrorCode = (error: unknown): number | string | undefined => {
|
|
387
|
+
if (!error || typeof error !== 'object') return undefined;
|
|
388
|
+
const { errorCode, code } = error as { errorCode?: unknown; code?: unknown };
|
|
389
|
+
const causeCode = errorCode ?? code;
|
|
390
|
+
return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
|
|
391
|
+
};
|
|
392
|
+
|
|
362
393
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
363
|
-
error
|
|
364
|
-
(error
|
|
365
|
-
error.errorCode === HardwareErrorCode.
|
|
366
|
-
|
|
367
|
-
|
|
394
|
+
isBleStaleBondHardwareError(error) ||
|
|
395
|
+
(error instanceof HardwareError &&
|
|
396
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
397
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
398
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
399
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE));
|
|
368
400
|
|
|
369
401
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
370
402
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -401,6 +433,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
401
433
|
return undefined;
|
|
402
434
|
};
|
|
403
435
|
|
|
436
|
+
const PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE = new Map<
|
|
437
|
+
number | string,
|
|
438
|
+
'prepare' | 'install' | 'verify'
|
|
439
|
+
>([
|
|
440
|
+
[0, 'prepare'],
|
|
441
|
+
[1, 'install'],
|
|
442
|
+
[2, 'verify'],
|
|
443
|
+
['FW_MGMT_UPDATER_PHASE_PREPARE', 'prepare'],
|
|
444
|
+
['FW_MGMT_UPDATER_PHASE_INSTALL', 'install'],
|
|
445
|
+
['FW_MGMT_UPDATER_PHASE_VERIFY', 'verify'],
|
|
446
|
+
]);
|
|
447
|
+
|
|
404
448
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
405
449
|
|
|
406
450
|
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
@@ -458,6 +502,11 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
|
458
502
|
return undefined;
|
|
459
503
|
};
|
|
460
504
|
|
|
505
|
+
const normalizeProtocolV2ProgressPercent = (value: unknown) => {
|
|
506
|
+
const progress = toProtocolV2FiniteNumber(value);
|
|
507
|
+
return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
|
|
508
|
+
};
|
|
509
|
+
|
|
461
510
|
export const isProtocolV2FirmwareFingerprintValid = (
|
|
462
511
|
binary: ArrayBuffer | Uint8Array,
|
|
463
512
|
fingerprint: string | undefined
|
|
@@ -530,12 +579,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
530
579
|
|
|
531
580
|
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
532
581
|
|
|
582
|
+
private protocolV2InstallNeedsReconnect = false;
|
|
583
|
+
|
|
584
|
+
private protocolV2InstallDisconnectObserved = false;
|
|
585
|
+
|
|
586
|
+
private protocolV2InstallTerminalSuccessObserved = false;
|
|
587
|
+
|
|
533
588
|
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
534
589
|
|
|
535
590
|
private protocolV2LastTransferProgress?: number;
|
|
536
591
|
|
|
537
592
|
private protocolV2LastTransferProgressAt = 0;
|
|
538
593
|
|
|
594
|
+
private protocolV2LastTransferredBytes = 0;
|
|
595
|
+
|
|
539
596
|
init() {
|
|
540
597
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
541
598
|
this.requireDeviceMode = [];
|
|
@@ -1808,6 +1865,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1808
1865
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
1809
1866
|
return true;
|
|
1810
1867
|
} catch (error) {
|
|
1868
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1869
|
+
throw error;
|
|
1870
|
+
}
|
|
1811
1871
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1812
1872
|
return false;
|
|
1813
1873
|
}
|
|
@@ -1901,7 +1961,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1901
1961
|
}
|
|
1902
1962
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
1903
1963
|
} catch (error) {
|
|
1904
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1964
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1905
1965
|
throw error;
|
|
1906
1966
|
}
|
|
1907
1967
|
shouldReconnect = true;
|
|
@@ -2063,6 +2123,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2063
2123
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2064
2124
|
this.protocolV2LastTransferProgress = undefined;
|
|
2065
2125
|
this.protocolV2LastTransferProgressAt = 0;
|
|
2126
|
+
this.protocolV2LastTransferredBytes = 0;
|
|
2127
|
+
const transferStartedAt = Date.now();
|
|
2066
2128
|
let processedSize = 0;
|
|
2067
2129
|
for (const resource of resourcesToSync) {
|
|
2068
2130
|
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
@@ -2073,6 +2135,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2073
2135
|
filePath: writePath,
|
|
2074
2136
|
processedSize,
|
|
2075
2137
|
totalSize,
|
|
2138
|
+
transferStartedAt,
|
|
2076
2139
|
});
|
|
2077
2140
|
await this.verifyProtocolV2StagedFile(writePath, resource.source.size);
|
|
2078
2141
|
if (isProtocolV2BootResourcePackagePath(resource.devicePath)) {
|
|
@@ -2088,6 +2151,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2088
2151
|
filePath,
|
|
2089
2152
|
processedSize,
|
|
2090
2153
|
totalSize,
|
|
2154
|
+
transferStartedAt,
|
|
2091
2155
|
});
|
|
2092
2156
|
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
2093
2157
|
stagedInstallTargets.push({
|
|
@@ -2097,7 +2161,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2097
2161
|
}
|
|
2098
2162
|
|
|
2099
2163
|
if (totalSize > 0) {
|
|
2100
|
-
|
|
2164
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
2165
|
+
this.postProgressMessage(100, 'transferData', {
|
|
2166
|
+
transferredBytes: totalSize,
|
|
2167
|
+
totalBytes: totalSize,
|
|
2168
|
+
rateBytesPerSecond: elapsedMs > 0 ? Math.round((totalSize / elapsedMs) * 1000) : undefined,
|
|
2169
|
+
elapsedMs,
|
|
2170
|
+
});
|
|
2101
2171
|
}
|
|
2102
2172
|
if (stagedInstallTargets.length === 0) {
|
|
2103
2173
|
return;
|
|
@@ -2122,20 +2192,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2122
2192
|
filePath,
|
|
2123
2193
|
processedSize,
|
|
2124
2194
|
totalSize,
|
|
2195
|
+
transferStartedAt = Date.now(),
|
|
2125
2196
|
}: {
|
|
2126
2197
|
source: FirmwareByteSource;
|
|
2127
2198
|
filePath: string;
|
|
2128
2199
|
processedSize: number;
|
|
2129
2200
|
totalSize: number;
|
|
2201
|
+
transferStartedAt?: number;
|
|
2130
2202
|
}) {
|
|
2131
2203
|
let lastError: unknown;
|
|
2132
2204
|
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
2205
|
+
this.throwIfAborted();
|
|
2133
2206
|
try {
|
|
2134
|
-
const transferStartedAt = Date.now();
|
|
2135
2207
|
await writeFirmwareByteSource({
|
|
2136
2208
|
source,
|
|
2137
2209
|
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
2138
2210
|
write: async ({ data, sourceOffset, length, first }) => {
|
|
2211
|
+
this.throwIfAborted();
|
|
2139
2212
|
const chunkEnd = sourceOffset + length;
|
|
2140
2213
|
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
2141
2214
|
processedSize + sourceOffset,
|
|
@@ -2163,18 +2236,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2163
2236
|
const elapsedMs = Math.max(now - transferStartedAt, 0);
|
|
2164
2237
|
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2165
2238
|
const shouldPostProgress =
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2239
|
+
transferredBytes >= this.protocolV2LastTransferredBytes &&
|
|
2240
|
+
(progress !== this.protocolV2LastTransferProgress ||
|
|
2241
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2242
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2243
|
+
chunkEnd === source.size);
|
|
2170
2244
|
if (shouldPostProgress) {
|
|
2171
2245
|
this.protocolV2LastTransferProgress = progress;
|
|
2172
2246
|
this.protocolV2LastTransferProgressAt = now;
|
|
2247
|
+
this.protocolV2LastTransferredBytes = transferredBytes;
|
|
2173
2248
|
this.postProgressMessage(progress, 'transferData', {
|
|
2174
2249
|
transferredBytes,
|
|
2175
2250
|
totalBytes: totalSize,
|
|
2176
2251
|
rateBytesPerSecond:
|
|
2177
|
-
elapsedMs > 0 ? Math.round((
|
|
2252
|
+
elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
|
|
2178
2253
|
elapsedMs,
|
|
2179
2254
|
});
|
|
2180
2255
|
}
|
|
@@ -2183,15 +2258,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2183
2258
|
});
|
|
2184
2259
|
return processedSize + source.size;
|
|
2185
2260
|
} catch (error) {
|
|
2261
|
+
this.throwIfAborted();
|
|
2262
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2263
|
+
throw error;
|
|
2264
|
+
}
|
|
2186
2265
|
lastError = error;
|
|
2187
2266
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2188
2267
|
await this.recoverProtocolV2FileTransfer();
|
|
2268
|
+
this.throwIfAborted();
|
|
2189
2269
|
}
|
|
2190
2270
|
}
|
|
2191
2271
|
}
|
|
2272
|
+
const causeCode = getProtocolV2ErrorCode(lastError);
|
|
2192
2273
|
throw ERRORS.TypedError(
|
|
2193
2274
|
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2194
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}
|
|
2275
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`,
|
|
2276
|
+
causeCode === undefined ? undefined : { causeCode }
|
|
2195
2277
|
);
|
|
2196
2278
|
}
|
|
2197
2279
|
|
|
@@ -2212,19 +2294,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2212
2294
|
}
|
|
2213
2295
|
}
|
|
2214
2296
|
|
|
2297
|
+
private collectProtocolV2LiveTargetIds(
|
|
2298
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2299
|
+
expectedTargetIds: Set<number>,
|
|
2300
|
+
liveTargetIds: Set<number>
|
|
2301
|
+
) {
|
|
2302
|
+
statusTargets.forEach(target => {
|
|
2303
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2304
|
+
if (
|
|
2305
|
+
targetId !== undefined &&
|
|
2306
|
+
expectedTargetIds.has(targetId) &&
|
|
2307
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
2308
|
+
) {
|
|
2309
|
+
liveTargetIds.add(targetId);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2215
2314
|
private assertProtocolV2TargetStatus(
|
|
2216
2315
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2217
2316
|
expectedTargetIds: Set<number>,
|
|
2218
|
-
expectedPaths
|
|
2317
|
+
expectedPaths?: Map<number, string>,
|
|
2318
|
+
liveTargetIds?: Set<number>
|
|
2219
2319
|
) {
|
|
2320
|
+
const resolvedExpectedPaths = expectedPaths ?? new Map<number, string>();
|
|
2220
2321
|
Log.log(
|
|
2221
2322
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2222
2323
|
);
|
|
2223
|
-
const failedTarget = statusTargets.find(
|
|
2224
|
-
target
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2324
|
+
const failedTarget = statusTargets.find(target => {
|
|
2325
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2326
|
+
return (
|
|
2327
|
+
expectedTargetIds.has(targetId) &&
|
|
2328
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2329
|
+
(!liveTargetIds || liveTargetIds.has(targetId))
|
|
2330
|
+
);
|
|
2331
|
+
});
|
|
2228
2332
|
if (failedTarget) {
|
|
2229
2333
|
const failedTargetDetails = JSON.stringify({
|
|
2230
2334
|
targetId: failedTarget.target_id,
|
|
@@ -2241,6 +2345,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2241
2345
|
}
|
|
2242
2346
|
);
|
|
2243
2347
|
}
|
|
2348
|
+
if (liveTargetIds) {
|
|
2349
|
+
const staleFailedTargets = statusTargets.filter(target => {
|
|
2350
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2351
|
+
return (
|
|
2352
|
+
expectedTargetIds.has(targetId) &&
|
|
2353
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2354
|
+
!liveTargetIds.has(targetId)
|
|
2355
|
+
);
|
|
2356
|
+
});
|
|
2357
|
+
if (staleFailedTargets.length > 0) {
|
|
2358
|
+
Log.log(
|
|
2359
|
+
`[FirmwareUpdateV4] ignoring stale failed firmware status until those targets start: ${JSON.stringify(
|
|
2360
|
+
staleFailedTargets.map(target => ({
|
|
2361
|
+
targetId: target.target_id,
|
|
2362
|
+
status: target.status,
|
|
2363
|
+
path: target.path,
|
|
2364
|
+
}))
|
|
2365
|
+
)}`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2244
2369
|
|
|
2245
2370
|
const matchingTargets = statusTargets.filter(target =>
|
|
2246
2371
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
@@ -2248,10 +2373,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2248
2373
|
const seenTargetIds = new Set<number>();
|
|
2249
2374
|
for (const target of matchingTargets) {
|
|
2250
2375
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2376
|
+
const pathConflictsWithCurrentInstall =
|
|
2377
|
+
targetId !== undefined &&
|
|
2378
|
+
Boolean(target.path) &&
|
|
2379
|
+
Boolean(resolvedExpectedPaths.get(targetId)) &&
|
|
2380
|
+
target.path !== resolvedExpectedPaths.get(targetId) &&
|
|
2381
|
+
(!liveTargetIds || liveTargetIds.has(targetId));
|
|
2251
2382
|
if (
|
|
2252
2383
|
targetId === undefined ||
|
|
2253
2384
|
seenTargetIds.has(targetId) ||
|
|
2254
|
-
|
|
2385
|
+
pathConflictsWithCurrentInstall
|
|
2255
2386
|
) {
|
|
2256
2387
|
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2257
2388
|
throw ERRORS.TypedError(
|
|
@@ -2264,9 +2395,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2264
2395
|
}
|
|
2265
2396
|
seenTargetIds.add(targetId);
|
|
2266
2397
|
}
|
|
2267
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2268
|
-
|
|
2269
|
-
|
|
2398
|
+
const completedTargets = matchingTargets.filter(target => {
|
|
2399
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2400
|
+
return (
|
|
2401
|
+
isProtocolV2TargetStatusFinished(target.status) &&
|
|
2402
|
+
(!liveTargetIds || (targetId !== undefined && liveTargetIds.has(targetId)))
|
|
2403
|
+
);
|
|
2404
|
+
});
|
|
2270
2405
|
const completedTargetIds = new Set<number>();
|
|
2271
2406
|
completedTargets.forEach(target => {
|
|
2272
2407
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
@@ -2303,16 +2438,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2303
2438
|
}
|
|
2304
2439
|
|
|
2305
2440
|
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2441
|
+
let detailedProgress = completedTargetIds.size * 100;
|
|
2442
|
+
let installProgressMetadata:
|
|
2443
|
+
| {
|
|
2444
|
+
installTargetId: number;
|
|
2445
|
+
installPhase: 'prepare' | 'install' | 'verify';
|
|
2446
|
+
installPhaseProgress: number;
|
|
2447
|
+
}
|
|
2448
|
+
| undefined;
|
|
2449
|
+
let hasInProgressTarget = false;
|
|
2450
|
+
matchingTargets.forEach(target => {
|
|
2451
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2452
|
+
if (
|
|
2453
|
+
targetId === undefined ||
|
|
2454
|
+
completedTargetIds.has(targetId) ||
|
|
2455
|
+
!isProtocolV2TargetStatusInProgress(target.status) ||
|
|
2456
|
+
(liveTargetIds && !liveTargetIds.has(targetId))
|
|
2457
|
+
) {
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
hasInProgressTarget = true;
|
|
2461
|
+
detailedProgress += normalizeProtocolV2ProgressPercent(target.progress_percent) ?? 0;
|
|
2462
|
+
const installPhase = target.phase_info
|
|
2463
|
+
? PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE.get(target.phase_info.phase)
|
|
2464
|
+
: undefined;
|
|
2465
|
+
const installPhaseProgress = normalizeProtocolV2ProgressPercent(
|
|
2466
|
+
target.phase_info?.progress_percent
|
|
2467
|
+
);
|
|
2468
|
+
if (installPhase && installPhaseProgress !== undefined && !installProgressMetadata) {
|
|
2469
|
+
installProgressMetadata = {
|
|
2470
|
+
installTargetId: targetId,
|
|
2471
|
+
installPhase,
|
|
2472
|
+
installPhaseProgress,
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2309
2476
|
const completedProgress = Math.floor(
|
|
2310
2477
|
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2311
2478
|
);
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2479
|
+
const reportedProgress = Math.floor(detailedProgress / expectedTargetIds.size);
|
|
2480
|
+
const progress = Math.min(
|
|
2481
|
+
99,
|
|
2482
|
+
Math.max(completedProgress, reportedProgress, hasInProgressTarget ? 1 : 0)
|
|
2483
|
+
);
|
|
2484
|
+
if (installProgressMetadata) {
|
|
2485
|
+
this.postProgressMessage(progress, 'installingFirmware', installProgressMetadata);
|
|
2486
|
+
} else {
|
|
2487
|
+
this.postProgressMessage(progress, 'installingFirmware');
|
|
2488
|
+
}
|
|
2316
2489
|
}
|
|
2317
2490
|
|
|
2318
2491
|
return false;
|
|
@@ -2383,14 +2556,37 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2383
2556
|
) {
|
|
2384
2557
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2385
2558
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2559
|
+
const isBleInstall = this.isBleReconnect();
|
|
2386
2560
|
const startTime = Date.now();
|
|
2387
2561
|
let lastError: unknown;
|
|
2388
|
-
//
|
|
2389
|
-
//
|
|
2390
|
-
let shouldReconnect =
|
|
2562
|
+
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
2563
|
+
// directly to status polling without generic Ping or DeviceInfo probes.
|
|
2564
|
+
let shouldReconnect = this.protocolV2InstallNeedsReconnect;
|
|
2565
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
2391
2566
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2392
|
-
let
|
|
2567
|
+
let bleInstallLinkReady = false;
|
|
2568
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
2569
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
2570
|
+
let installReconnectObserved = false;
|
|
2571
|
+
let finishedStatusSnapshotKey: string | undefined;
|
|
2572
|
+
let finishedStatusSnapshotPolls = 0;
|
|
2573
|
+
let fastFinishedInstallProgressReported = false;
|
|
2574
|
+
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2393
2575
|
let currentInstallStatusObserved = false;
|
|
2576
|
+
const liveTargetIds = new Set<number>();
|
|
2577
|
+
const recordBleInstallDisconnect = (error: unknown) => {
|
|
2578
|
+
if (
|
|
2579
|
+
!isBleInstall ||
|
|
2580
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
2581
|
+
isProtocolV2InstallResponseTimeout(error)
|
|
2582
|
+
) {
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
installStatusDisconnectObserved = true;
|
|
2586
|
+
installReconnectObserved = false;
|
|
2587
|
+
finishedStatusSnapshotKey = undefined;
|
|
2588
|
+
finishedStatusSnapshotPolls = 0;
|
|
2589
|
+
};
|
|
2394
2590
|
|
|
2395
2591
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2396
2592
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2398,43 +2594,40 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2398
2594
|
this.throwIfAborted();
|
|
2399
2595
|
try {
|
|
2400
2596
|
if (shouldReconnect) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2597
|
+
const isBleInstallReconnect = this.isBleReconnect();
|
|
2598
|
+
await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
2599
|
+
bleInstallLinkReady = isBleInstallReconnect;
|
|
2600
|
+
deviceInfo = isBleInstallReconnect
|
|
2601
|
+
? undefined
|
|
2602
|
+
: await this.verifyProtocolV2ReconnectIdentity();
|
|
2603
|
+
if (isBleInstallReconnect && installStatusDisconnectObserved) {
|
|
2604
|
+
installReconnectObserved = true;
|
|
2605
|
+
}
|
|
2403
2606
|
shouldReconnect = false;
|
|
2404
2607
|
}
|
|
2405
|
-
|
|
2608
|
+
let currentDeviceInfo = deviceInfo;
|
|
2406
2609
|
try {
|
|
2407
2610
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2408
2611
|
'DeviceFirmwareUpdateStatusGet',
|
|
2409
|
-
|
|
2612
|
+
'DeviceFirmwareUpdateStatus',
|
|
2410
2613
|
{
|
|
2411
2614
|
fields: {
|
|
2412
2615
|
status: true,
|
|
2616
|
+
progress_percent: true,
|
|
2617
|
+
phase_info: true,
|
|
2413
2618
|
payload_version: true,
|
|
2414
2619
|
path: true,
|
|
2415
2620
|
},
|
|
2416
2621
|
},
|
|
2417
2622
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2418
2623
|
);
|
|
2419
|
-
if (
|
|
2624
|
+
if (this.protocolV2InstallTerminalSuccessObserved) {
|
|
2420
2625
|
installEvidenceObserved = true;
|
|
2421
|
-
lastError = new Error(
|
|
2422
|
-
'Protocol V2 firmware install acknowledged; waiting for target status'
|
|
2423
|
-
);
|
|
2424
2626
|
}
|
|
2425
|
-
const statusTargets =
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
const hasInProgressTarget = statusTargets.some(target => {
|
|
2430
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2431
|
-
return (
|
|
2432
|
-
targetId !== undefined &&
|
|
2433
|
-
expectedTargetIds.has(targetId) &&
|
|
2434
|
-
isProtocolV2TargetStatusInProgress(target.status)
|
|
2435
|
-
);
|
|
2436
|
-
});
|
|
2437
|
-
if (hasInProgressTarget) {
|
|
2627
|
+
const statusTargets = (statusResponse.message.records ??
|
|
2628
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2629
|
+
this.collectProtocolV2LiveTargetIds(statusTargets, expectedTargetIds, liveTargetIds);
|
|
2630
|
+
if (liveTargetIds.size > 0) {
|
|
2438
2631
|
currentInstallStatusObserved = true;
|
|
2439
2632
|
}
|
|
2440
2633
|
const hasMatchingTargetStatus = statusTargets.some(target => {
|
|
@@ -2451,18 +2644,103 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2451
2644
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2452
2645
|
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2453
2646
|
});
|
|
2647
|
+
const matchingFinishedTargetIds = new Set<number>();
|
|
2648
|
+
const matchingFinishedStatus = matchingStatusTargets
|
|
2649
|
+
.map(target => {
|
|
2650
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2651
|
+
const expectedPath = targetId === undefined ? undefined : expectedPaths.get(targetId);
|
|
2652
|
+
const expectedVersion =
|
|
2653
|
+
targetId === undefined
|
|
2654
|
+
? undefined
|
|
2655
|
+
: this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2656
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
2657
|
+
if (
|
|
2658
|
+
targetId === undefined ||
|
|
2659
|
+
matchingFinishedTargetIds.has(targetId) ||
|
|
2660
|
+
!isProtocolV2TargetStatusFinished(target.status) ||
|
|
2661
|
+
(Boolean(target.path) && Boolean(expectedPath) && target.path !== expectedPath) ||
|
|
2662
|
+
(expectedVersion !== undefined &&
|
|
2663
|
+
payloadVersion !== undefined &&
|
|
2664
|
+
payloadVersion !== expectedVersion)
|
|
2665
|
+
) {
|
|
2666
|
+
return undefined;
|
|
2667
|
+
}
|
|
2668
|
+
matchingFinishedTargetIds.add(targetId);
|
|
2669
|
+
return [targetId, target.path ?? null, payloadVersion ?? null] as const;
|
|
2670
|
+
})
|
|
2671
|
+
.filter((target): target is readonly [number, string | null, number | null] => !!target)
|
|
2672
|
+
.sort(([leftTargetId], [rightTargetId]) => leftTargetId - rightTargetId);
|
|
2673
|
+
const hasCompleteMatchingFinishedStatus =
|
|
2674
|
+
matchingFinishedStatus.length === expectedTargetIds.size &&
|
|
2675
|
+
matchingFinishedTargetIds.size === expectedTargetIds.size &&
|
|
2676
|
+
expectedTargetIds.size > 0;
|
|
2677
|
+
if (
|
|
2678
|
+
requireCurrentInstallStatus &&
|
|
2679
|
+
installReconnectObserved &&
|
|
2680
|
+
hasCompleteMatchingFinishedStatus
|
|
2681
|
+
) {
|
|
2682
|
+
const nextSnapshotKey = JSON.stringify(matchingFinishedStatus);
|
|
2683
|
+
if (nextSnapshotKey === finishedStatusSnapshotKey) {
|
|
2684
|
+
finishedStatusSnapshotPolls += 1;
|
|
2685
|
+
} else {
|
|
2686
|
+
finishedStatusSnapshotKey = nextSnapshotKey;
|
|
2687
|
+
finishedStatusSnapshotPolls = 1;
|
|
2688
|
+
}
|
|
2689
|
+
} else {
|
|
2690
|
+
finishedStatusSnapshotKey = undefined;
|
|
2691
|
+
finishedStatusSnapshotPolls = 0;
|
|
2692
|
+
}
|
|
2693
|
+
if (
|
|
2694
|
+
requireCurrentInstallStatus &&
|
|
2695
|
+
installReconnectObserved &&
|
|
2696
|
+
hasCompleteMatchingFinishedStatus &&
|
|
2697
|
+
!currentInstallStatusObserved &&
|
|
2698
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2699
|
+
!fastFinishedInstallProgressReported
|
|
2700
|
+
) {
|
|
2701
|
+
// A fast BLE install may finish while disconnected, so IN_PROGRESS can be hidden.
|
|
2702
|
+
// A status response timeout alone is not evidence that the install was accepted.
|
|
2703
|
+
this.postProgressMessage(1, 'installingFirmware');
|
|
2704
|
+
fastFinishedInstallProgressReported = true;
|
|
2705
|
+
}
|
|
2706
|
+
const finishedAfterDisconnectObserved =
|
|
2707
|
+
finishedStatusSnapshotPolls >= PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS;
|
|
2708
|
+
const hasCurrentInstallEvidence =
|
|
2709
|
+
currentInstallStatusObserved ||
|
|
2710
|
+
this.protocolV2InstallTerminalSuccessObserved ||
|
|
2711
|
+
finishedAfterDisconnectObserved;
|
|
2712
|
+
if (hasCurrentInstallEvidence) {
|
|
2713
|
+
installEvidenceObserved = true;
|
|
2714
|
+
}
|
|
2454
2715
|
const shouldVerifyTargetCompletion =
|
|
2455
|
-
!requireCurrentInstallStatus ||
|
|
2716
|
+
!requireCurrentInstallStatus || hasCurrentInstallEvidence;
|
|
2456
2717
|
if (
|
|
2457
2718
|
shouldVerifyTargetCompletion &&
|
|
2458
|
-
this.assertProtocolV2TargetStatus(
|
|
2719
|
+
this.assertProtocolV2TargetStatus(
|
|
2720
|
+
statusTargets,
|
|
2721
|
+
expectedTargetIds,
|
|
2722
|
+
expectedPaths,
|
|
2723
|
+
requireCurrentInstallStatus &&
|
|
2724
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2725
|
+
!finishedAfterDisconnectObserved
|
|
2726
|
+
? liveTargetIds
|
|
2727
|
+
: undefined
|
|
2728
|
+
)
|
|
2459
2729
|
) {
|
|
2460
2730
|
return;
|
|
2461
2731
|
}
|
|
2462
2732
|
|
|
2733
|
+
if (!hasCurrentInstallEvidence) {
|
|
2734
|
+
// A successful status poll clears DeviceCommands' cancel action. Keep cancellation
|
|
2735
|
+
// available while UpdateRequest is still waiting for confirmation on the device.
|
|
2736
|
+
// Bootloader does not register the protocol Cancel message. Keep the
|
|
2737
|
+
// operation locally cancellable; interruptionFromUser() disposes the link.
|
|
2738
|
+
this.device.setCancelableAction(() => Promise.resolve());
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2463
2741
|
if (
|
|
2464
2742
|
requireCurrentInstallStatus &&
|
|
2465
|
-
!
|
|
2743
|
+
!hasCurrentInstallEvidence &&
|
|
2466
2744
|
matchingStatusTargets.length > 0
|
|
2467
2745
|
) {
|
|
2468
2746
|
lastError = new Error(
|
|
@@ -2470,6 +2748,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2470
2748
|
);
|
|
2471
2749
|
}
|
|
2472
2750
|
|
|
2751
|
+
if (
|
|
2752
|
+
statusTargets.length === 0 &&
|
|
2753
|
+
!currentDeviceInfo &&
|
|
2754
|
+
bleInstallLinkReady &&
|
|
2755
|
+
installEvidenceObserved
|
|
2756
|
+
) {
|
|
2757
|
+
// BLE install reconnect skips generic probes because loaders may not answer Ping.
|
|
2758
|
+
// Restore the verified identity only after current-install evidence disappears.
|
|
2759
|
+
currentDeviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2760
|
+
deviceInfo = currentDeviceInfo;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2473
2763
|
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2474
2764
|
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2475
2765
|
if (
|
|
@@ -2487,11 +2777,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2487
2777
|
}
|
|
2488
2778
|
|
|
2489
2779
|
if (statusTargets.length === 0) {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
);
|
|
2494
|
-
}
|
|
2780
|
+
lastError = new Error(
|
|
2781
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2782
|
+
);
|
|
2495
2783
|
} else {
|
|
2496
2784
|
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2497
2785
|
statusTargets,
|
|
@@ -2514,12 +2802,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2514
2802
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2515
2803
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2516
2804
|
if (!currentDeviceInfo) {
|
|
2805
|
+
if (!bleInstallLinkReady) {
|
|
2806
|
+
throw ERRORS.TypedError(
|
|
2807
|
+
HardwareErrorCode.RuntimeError,
|
|
2808
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2812
|
+
}
|
|
2813
|
+
const reconnectDeviceInfo = currentDeviceInfo ?? deviceInfo;
|
|
2814
|
+
if (!reconnectDeviceInfo) {
|
|
2517
2815
|
throw ERRORS.TypedError(
|
|
2518
2816
|
HardwareErrorCode.RuntimeError,
|
|
2519
2817
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2520
2818
|
);
|
|
2521
2819
|
}
|
|
2522
|
-
const isNormalMode = await this.probeProtocolV2NormalMode(
|
|
2820
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(reconnectDeviceInfo);
|
|
2523
2821
|
if (
|
|
2524
2822
|
isNormalMode &&
|
|
2525
2823
|
(installEvidenceObserved ||
|
|
@@ -2542,8 +2840,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2542
2840
|
);
|
|
2543
2841
|
}
|
|
2544
2842
|
} else {
|
|
2843
|
+
recordBleInstallDisconnect(error);
|
|
2545
2844
|
shouldReconnect = true;
|
|
2546
2845
|
deviceInfo = undefined;
|
|
2846
|
+
bleInstallLinkReady = false;
|
|
2547
2847
|
lastError = error;
|
|
2548
2848
|
Log.log(
|
|
2549
2849
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -2565,8 +2865,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2565
2865
|
) {
|
|
2566
2866
|
throw error;
|
|
2567
2867
|
}
|
|
2868
|
+
recordBleInstallDisconnect(error);
|
|
2568
2869
|
shouldReconnect = true;
|
|
2569
2870
|
deviceInfo = undefined;
|
|
2871
|
+
bleInstallLinkReady = false;
|
|
2570
2872
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2571
2873
|
}
|
|
2572
2874
|
await wait(1000);
|
|
@@ -2595,6 +2897,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2595
2897
|
return;
|
|
2596
2898
|
}
|
|
2597
2899
|
} catch (error) {
|
|
2900
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2901
|
+
throw error;
|
|
2902
|
+
}
|
|
2598
2903
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2599
2904
|
}
|
|
2600
2905
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -2686,7 +2991,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2686
2991
|
'Protocol V2 device is still in bootloader mode'
|
|
2687
2992
|
);
|
|
2688
2993
|
} catch (error) {
|
|
2689
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2994
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2690
2995
|
throw error;
|
|
2691
2996
|
}
|
|
2692
2997
|
shouldReconnect = true;
|
|
@@ -2704,19 +3009,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2704
3009
|
);
|
|
2705
3010
|
}
|
|
2706
3011
|
|
|
2707
|
-
private async reconnectProtocolV2Device() {
|
|
3012
|
+
private async reconnectProtocolV2Device(options?: { skipProtocolProbe?: boolean }) {
|
|
2708
3013
|
if (this.isBleReconnect()) {
|
|
2709
|
-
await this.acquireProtocolV2BleDevice();
|
|
3014
|
+
await this.acquireProtocolV2BleDevice(options?.skipProtocolProbe);
|
|
2710
3015
|
return;
|
|
2711
3016
|
}
|
|
2712
3017
|
|
|
2713
3018
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
2714
3019
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
3020
|
+
const env = DataManager.getSettings('env');
|
|
3021
|
+
const isWebUsb = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
3022
|
+
|
|
3023
|
+
if (isWebUsb && options?.skipProtocolProbe) {
|
|
3024
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
3025
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
3026
|
+
const reconnectDescriptor = devicesDescriptor.find(descriptor => {
|
|
3027
|
+
const descriptorPath = descriptor.path?.trim();
|
|
3028
|
+
return (
|
|
3029
|
+
(!!expectedSerialNumber && descriptorPath === expectedSerialNumber) ||
|
|
3030
|
+
(!!expectedPath && descriptorPath === expectedPath)
|
|
3031
|
+
);
|
|
3032
|
+
});
|
|
3033
|
+
if (!reconnectDescriptor?.path) {
|
|
3034
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
3035
|
+
}
|
|
3036
|
+
this.device.updateDescriptor(
|
|
3037
|
+
{
|
|
3038
|
+
...reconnectDescriptor,
|
|
3039
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3040
|
+
},
|
|
3041
|
+
true
|
|
3042
|
+
);
|
|
3043
|
+
const mainId = await this.device.deviceConnector?.acquire(
|
|
3044
|
+
reconnectDescriptor.path,
|
|
3045
|
+
null,
|
|
3046
|
+
undefined,
|
|
3047
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3048
|
+
undefined,
|
|
3049
|
+
undefined,
|
|
3050
|
+
true
|
|
3051
|
+
);
|
|
3052
|
+
this.device.mainId = typeof mainId === 'string' ? mainId : reconnectDescriptor.path;
|
|
3053
|
+
this.device.commands.disposed = false;
|
|
3054
|
+
this.device.getCommands().mainId = this.device.mainId;
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
2715
3057
|
|
|
2716
|
-
if (
|
|
2717
|
-
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
2718
|
-
devicesDescriptor.length === 1
|
|
2719
|
-
) {
|
|
3058
|
+
if (isWebUsb && devicesDescriptor.length === 1) {
|
|
2720
3059
|
const descriptor = devicesDescriptor[0];
|
|
2721
3060
|
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2722
3061
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
@@ -2834,13 +3173,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2834
3173
|
await wait(2000);
|
|
2835
3174
|
}
|
|
2836
3175
|
|
|
2837
|
-
private async acquireProtocolV2BleDevice() {
|
|
2838
|
-
|
|
2839
|
-
|
|
3176
|
+
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
|
3177
|
+
const connector = this.device.deviceConnector;
|
|
3178
|
+
const expectedId = this.device.originalDescriptor.id;
|
|
3179
|
+
if (!skipProtocolProbe) {
|
|
3180
|
+
await connector?.acquire(expectedId, null, true, PROTOCOL_V2_CONNECT_PROTOCOL);
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
await connector?.acquire(
|
|
3184
|
+
expectedId,
|
|
2840
3185
|
null,
|
|
2841
3186
|
true,
|
|
2842
|
-
PROTOCOL_V2_CONNECT_PROTOCOL
|
|
3187
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3188
|
+
undefined,
|
|
3189
|
+
undefined,
|
|
3190
|
+
skipProtocolProbe
|
|
2843
3191
|
);
|
|
3192
|
+
this.device.commands.disposed = false;
|
|
3193
|
+
this.device.getCommands().mainId = expectedId;
|
|
2844
3194
|
}
|
|
2845
3195
|
|
|
2846
3196
|
private async protocolV2StartFirmwareUpdate({
|
|
@@ -2849,9 +3199,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2849
3199
|
targets: Array<{ target_id: number; path: string }>;
|
|
2850
3200
|
}) {
|
|
2851
3201
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3202
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
3203
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
3204
|
+
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
2852
3205
|
const commands = this.device.getCommands();
|
|
2853
3206
|
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
2854
|
-
|
|
3207
|
+
// Bootloader does not register the protocol Cancel message. Keep the
|
|
3208
|
+
// operation locally cancellable; interruptionFromUser() disposes the link.
|
|
3209
|
+
this.device.setCancelableAction(() => Promise.resolve());
|
|
2855
3210
|
const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
2856
3211
|
this.postMessage(
|
|
2857
3212
|
createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
|
|
@@ -2864,19 +3219,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2864
3219
|
...(interaction ? { interaction } : {}),
|
|
2865
3220
|
})
|
|
2866
3221
|
);
|
|
2867
|
-
|
|
3222
|
+
|
|
3223
|
+
try {
|
|
3224
|
+
await commands.call(
|
|
3225
|
+
'DeviceFirmwareUpdateRequest',
|
|
3226
|
+
{},
|
|
3227
|
+
{
|
|
3228
|
+
returnAfterWrite: true,
|
|
3229
|
+
expectedTypes: ['Success'],
|
|
3230
|
+
onResponseAfterWrite: response => {
|
|
3231
|
+
if (response.type !== 'Success') return;
|
|
3232
|
+
this.protocolV2InstallTerminalSuccessObserved = true;
|
|
3233
|
+
this.device.clearCancelableAction();
|
|
3234
|
+
Log.log('[FirmwareUpdateV4] firmware install completed by device response');
|
|
3235
|
+
},
|
|
3236
|
+
}
|
|
3237
|
+
);
|
|
3238
|
+
Log.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
this.throwIfAborted();
|
|
3241
|
+
if (!isProtocolV2DeviceDisconnectedError(error)) {
|
|
3242
|
+
throw error;
|
|
3243
|
+
}
|
|
3244
|
+
Log.log(
|
|
3245
|
+
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3246
|
+
);
|
|
3247
|
+
this.protocolV2InstallNeedsReconnect = true;
|
|
3248
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
3249
|
+
}
|
|
2868
3250
|
}
|
|
2869
3251
|
|
|
2870
3252
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
2871
3253
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2872
3254
|
try {
|
|
2873
|
-
const res = await typedCall(
|
|
2874
|
-
|
|
2875
|
-
|
|
3255
|
+
const res = await typedCall(
|
|
3256
|
+
'DeviceReboot',
|
|
3257
|
+
'Success',
|
|
3258
|
+
{
|
|
3259
|
+
reboot_type: rebootType,
|
|
3260
|
+
},
|
|
3261
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
3262
|
+
);
|
|
2876
3263
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2877
3264
|
return res.message;
|
|
2878
3265
|
} catch (error) {
|
|
2879
|
-
if (
|
|
3266
|
+
if (
|
|
3267
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
3268
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
3269
|
+
isProtocolV2ResponseTimeout(error)
|
|
3270
|
+
) {
|
|
2880
3271
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2881
3272
|
return { message: 'Device rebooted successfully' };
|
|
2882
3273
|
}
|