@onekeyfe/hd-core 1.2.8-alpha.115 → 1.2.8-alpha.117

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.
@@ -8884,6 +8884,154 @@ describe('Protocol V2 firmware update targets', () => {
8884
8884
  expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
8885
8885
  });
8886
8886
 
8887
+ describe('RESC header check link recovery', () => {
8888
+ const currentHeader = {
8889
+ version: [1, 0, 0],
8890
+ payloadHash: '11'.repeat(64),
8891
+ headerHash: '22'.repeat(64),
8892
+ };
8893
+ const linkLost = () =>
8894
+ ERRORS.TypedError(
8895
+ HardwareErrorCode.RuntimeError,
8896
+ 'Device protocol has not been detected for E2:92:2E:16:47:B9'
8897
+ );
8898
+ const createResource = (name: string) => ({
8899
+ name,
8900
+ source: { size: 3, readAt: jest.fn(), close: jest.fn() },
8901
+ devicePath: `vol0:/bundles/${name}`,
8902
+ version: [1, 0, 0],
8903
+ payloadHash: '11'.repeat(64),
8904
+ headerHash: '22'.repeat(64),
8905
+ });
8906
+ const createMethod = () => {
8907
+ const method = new FirmwareUpdateV4({
8908
+ id: 1,
8909
+ payload: {
8910
+ method: 'firmwareUpdateV4',
8911
+ },
8912
+ });
8913
+ method.postTipMessage = jest.fn();
8914
+ method.postProgressMessage = jest.fn();
8915
+ (method as any).protocolV2SourceUpdateProcess = jest.fn(
8916
+ ({ processedSize }: { processedSize: number }) => Promise.resolve(processedSize + 3)
8917
+ );
8918
+ (method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
8919
+ (method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
8920
+ return method;
8921
+ };
8922
+
8923
+ test('reconnects and asks again when the link drops during a header check', async () => {
8924
+ const method = createMethod();
8925
+ (method as any).readProtocolV2DeviceFileHeader = jest
8926
+ .fn()
8927
+ .mockRejectedValueOnce(linkLost())
8928
+ .mockResolvedValue(currentHeader);
8929
+
8930
+ await (method as any).executeProtocolV2TransferPhase({
8931
+ installSources: [],
8932
+ resourceSources: [createResource('images.okpkg'), createResource('fonts.okpkg')],
8933
+ });
8934
+
8935
+ expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
8936
+ expect((method as any).readProtocolV2DeviceFileHeader).toHaveBeenCalledTimes(3);
8937
+ expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
8938
+ });
8939
+
8940
+ test.each([
8941
+ ['BLE disconnect', () => ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected)],
8942
+ ['BLE timeout', () => ERRORS.TypedError(HardwareErrorCode.BleTimeoutError)],
8943
+ [
8944
+ 'invalidated Protocol V2 link',
8945
+ () =>
8946
+ Object.assign(new Error('Protocol V2 link generation changed'), {
8947
+ name: 'ProtocolV2LinkError',
8948
+ code: 'generation',
8949
+ }),
8950
+ ],
8951
+ ['released transport', () => new Error('React Native BLE transport released')],
8952
+ ['cleaned Electron BLE state', () => new Error('Electron BLE device state cleaned')],
8953
+ [
8954
+ 'Electron BLE device that is no longer connected',
8955
+ () => ERRORS.TypedError(HardwareErrorCode.TransportNotFound, 'Device AA:BB not connected'),
8956
+ ],
8957
+ ])('rethrows a %s from the header check for recovery', async (_label, createError) => {
8958
+ const method = createMethod();
8959
+ (method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(createError());
8960
+
8961
+ await expect(
8962
+ (method as any).isProtocolV2ResourceBundleUpToDate(createResource('images.okpkg'))
8963
+ ).rejects.toBeDefined();
8964
+ });
8965
+
8966
+ test('treats a device-side header read failure as stale without reconnecting', async () => {
8967
+ const method = createMethod();
8968
+ (method as any).readProtocolV2DeviceFileHeader = jest
8969
+ .fn()
8970
+ .mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Failure: busy'));
8971
+
8972
+ await (method as any).executeProtocolV2TransferPhase({
8973
+ installSources: [],
8974
+ resourceSources: [createResource('images.okpkg')],
8975
+ });
8976
+
8977
+ expect((method as any).recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
8978
+ expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
8979
+ });
8980
+
8981
+ test('limits header-check reconnects per transfer phase', async () => {
8982
+ const method = createMethod();
8983
+ (method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
8984
+
8985
+ await (method as any).executeProtocolV2TransferPhase({
8986
+ installSources: [],
8987
+ resourceSources: ['a.okpkg', 'b.okpkg', 'c.okpkg'].map(createResource),
8988
+ });
8989
+
8990
+ expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(2);
8991
+ expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(3);
8992
+ });
8993
+
8994
+ test('resends without further reconnects when the header-check reconnect fails', async () => {
8995
+ const method = createMethod();
8996
+ (method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
8997
+ (method as any).recoverProtocolV2FileTransfer = jest
8998
+ .fn()
8999
+ .mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.DeviceNotFound));
9000
+
9001
+ await (method as any).executeProtocolV2TransferPhase({
9002
+ installSources: [],
9003
+ resourceSources: [createResource('images.okpkg'), createResource('fonts.okpkg')],
9004
+ });
9005
+
9006
+ expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
9007
+ expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(2);
9008
+ });
9009
+
9010
+ test.each(['mismatch: expected A, received B', 'unavailable: expected path A'])(
9011
+ 'aborts instead of resending when the reconnected device identity is %s',
9012
+ async identityFailure => {
9013
+ const method = createMethod();
9014
+ (method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
9015
+ (method as any).recoverProtocolV2FileTransfer = jest
9016
+ .fn()
9017
+ .mockRejectedValue(
9018
+ ERRORS.TypedError(
9019
+ HardwareErrorCode.DeviceNotFound,
9020
+ `Protocol V2 reconnect physical identity ${identityFailure}`
9021
+ )
9022
+ );
9023
+
9024
+ await expect(
9025
+ (method as any).executeProtocolV2TransferPhase({
9026
+ installSources: [],
9027
+ resourceSources: [createResource('images.okpkg')],
9028
+ })
9029
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceNotFound });
9030
+ expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
9031
+ }
9032
+ );
9033
+ });
9034
+
8887
9035
  test.each([
8888
9036
  ['1.0.3', '1.0.3', true],
8889
9037
  ['1.0.10', '1.0.3', true],
@@ -69,6 +69,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
69
69
  private getProtocolV2ResourceComparePath;
70
70
  private readProtocolV2DeviceFileHeader;
71
71
  private isProtocolV2ResourceBundleUpToDate;
72
+ private isProtocolV2ResourceBundleUpToDateWithRecovery;
72
73
  private isProtocolV2BootloaderMode;
73
74
  private isProtocolV2RomloaderMode;
74
75
  private rebootProtocolV2ToBootloader;
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkD,MAAM,qBAAqB,CAAC;AA4BlG,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAiC/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AA+ErC,wBAAgB,wCAAwC,CACtD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,sBAAsB,EAC9B,0BAA0B,UAAQ,QAiCnC;AAGD,wBAAgB,oCAAoC,CAClD,iBAAiB,EAAE,MAAM,EACzB,oBAAoB,qBAA2D,WAOhF;AA0UD,eAAO,MAAM,oCAAoC,WACvC,WAAW,GAAG,UAAU,eACnB,MAAM,GAAG,SAAS,YAKhC,CAAC;AAEF,eAAO,MAAM,iCAAiC,0BACrB,MAAM,uBACR,MAAM,iBACZ,MAAM,eACR,MAAM,SAsBpB,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,OAAO,CAAC,sBAAsB,CAAC,CAAS;IAExC,OAAO,CAAC,oCAAoC,CAAS;IAErD,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAAS;IAElD,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,4BAA4B,CAAqB;IAEzD,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,+BAA+B,CAAC,CAAuB;IAE/D,OAAO,CAAC,2BAA2B,CAAC,CAAuB;IAE3D,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,+BAA+B,CAAS;IAEhD,OAAO,CAAC,mCAAmC,CAAS;IAEpD,OAAO,CAAC,wCAAwC,CAAS;IAEzD,OAAO,CAAC,kCAAkC,CAAC,CAAW;IAEtD,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,OAAO,CAAC,gCAAgC,CAAK;IAE7C,OAAO,CAAC,8BAA8B,CAAK;IAE3C,IAAI;IA6LJ,OAAO,CAAC,8BAA8B;IAiChC,GAAG;;;;;YAKK,aAAa;YA4Ib,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqG/B,4BAA4B;YA+C5B,0CAA0C;YAkB1C,qCAAqC;YAoFrC,gCAAgC;YAgBhC,uCAAuC;YAmDvC,8BAA8B;IA8B5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;YA2D1B,2BAA2B;IAmBzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,oCAAoC;IAY5C,OAAO,CAAC,4BAA4B;IAUpC,OAAO,CAAC,kCAAkC;IAS1C,OAAO,CAAC,8BAA8B;YAMxB,iCAAiC;YAOjC,iCAAiC;YAoBjC,iCAAiC;IAU/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,mCAAmC;IAe3C,OAAO,CAAC,iCAAiC;IAWzC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;YAkBlB,iCAAiC;YA6CjC,uCAAuC;YA0CvC,+BAA+B;IAmF7C,OAAO,CAAC,6BAA6B;IAMrC,OAAO,CAAC,gCAAgC;YAM1B,8BAA8B;YAmD9B,kCAAkC;IA+BhD,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,yBAAyB;YAcnB,4BAA4B;IAkBpC,6BAA6B;YAkBrB,+BAA+B;IAkD7C,OAAO,CAAC,6BAA6B;YAkCvB,6BAA6B;YA0B7B,0CAA0C;YA6B1C,uBAAuB;YAiCvB,8BAA8B;YAgG9B,6BAA6B;IAqH3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,8BAA8B;IAiBtC,OAAO,CAAC,4BAA4B;IAoLpC,OAAO,CAAC,6BAA6B;IAcrC,OAAO,CAAC,qCAAqC;IAoC7C,OAAO,CAAC,kCAAkC;IAyC1C,OAAO,CAAC,8CAA8C;YAIxC,uCAAuC;YA8UvC,gCAAgC;YAehC,yBAAyB;IAUvC,OAAO,CAAC,2BAA2B;YAMrB,8BAA8B;IAQ5C,OAAO,CAAC,0BAA0B;YAkBpB,mCAAmC;YAWnC,qCAAqC;YAmDrC,yBAAyB;YAgGzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAQ5B,cAAc;IAyC5B,OAAO,CAAC,qCAAqC;YA6B/B,6BAA6B;YAuE7B,6BAA6B;YAW7B,0BAA0B;YAoB1B,6BAA6B;YAwD7B,gBAAgB;IA0B9B,OAAO,CAAC,qBAAqB;CAM9B"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -52634,6 +52634,7 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
52634
52634
  const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
52635
52635
  const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
52636
52636
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
52637
+ const PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT = 2;
52637
52638
  const PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION = '1.0.2';
52638
52639
  const PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS = 1000;
52639
52640
  const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
@@ -52796,6 +52797,21 @@ const getProtocolV2ErrorCode = (error) => {
52796
52797
  const causeCode = errorCode !== null && errorCode !== void 0 ? errorCode : code;
52797
52798
  return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
52798
52799
  };
52800
+ const isProtocolV2ResourceHeaderLinkError = (error) => {
52801
+ if (isProtocolV2DeviceDisconnectedError(error))
52802
+ return true;
52803
+ const errorCode = getProtocolV2ErrorCode(error);
52804
+ if (errorCode === hdShared.HardwareErrorCode.BleTimeoutError)
52805
+ return true;
52806
+ const message = getProtocolV2UnknownErrorText(error);
52807
+ if (errorCode === hdShared.HardwareErrorCode.TransportNotFound && /not connected/i.test(message)) {
52808
+ return true;
52809
+ }
52810
+ return (message.includes('ProtocolV2LinkError') ||
52811
+ message.includes('Device protocol has not been detected') ||
52812
+ message.includes('Electron BLE device state cleaned') ||
52813
+ message.includes('Electron BLE transport released'));
52814
+ };
52799
52815
  const isProtocolV2TerminalInstallStatusError = (error) => {
52800
52816
  var _a;
52801
52817
  return error instanceof hdShared.HardwareError &&
@@ -53899,11 +53915,44 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53899
53915
  return true;
53900
53916
  }
53901
53917
  catch (error) {
53918
+ if (isProtocolV2ResourceHeaderLinkError(error))
53919
+ throw error;
53902
53920
  Log$6.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
53903
53921
  return false;
53904
53922
  }
53905
53923
  });
53906
53924
  }
53925
+ isProtocolV2ResourceBundleUpToDateWithRecovery(bundle, recovery) {
53926
+ return __awaiter(this, void 0, void 0, function* () {
53927
+ for (;;) {
53928
+ try {
53929
+ return yield this.isProtocolV2ResourceBundleUpToDate(bundle);
53930
+ }
53931
+ catch (error) {
53932
+ this.throwIfAborted();
53933
+ if (recovery.remaining <= 0) {
53934
+ Log$6.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
53935
+ return false;
53936
+ }
53937
+ recovery.remaining -= 1;
53938
+ Log$6.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnecting: `, getProtocolV2UnknownErrorText(error));
53939
+ try {
53940
+ yield this.recoverProtocolV2FileTransfer();
53941
+ }
53942
+ catch (recoverError) {
53943
+ this.throwIfAborted();
53944
+ if (this.normalizeErrorMessage(recoverError).includes('Protocol V2 reconnect physical identity')) {
53945
+ throw recoverError;
53946
+ }
53947
+ recovery.remaining = 0;
53948
+ Log$6.log('[FirmwareUpdateV4] RESC header check reconnect failed: ', recoverError);
53949
+ return false;
53950
+ }
53951
+ this.throwIfAborted();
53952
+ }
53953
+ }
53954
+ });
53955
+ }
53907
53956
  isProtocolV2BootloaderMode() {
53908
53957
  var _a, _b, _c;
53909
53958
  if (typeof this.device.isBootloader === 'function') {
@@ -54082,8 +54131,9 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
54082
54131
  return __awaiter(this, void 0, void 0, function* () {
54083
54132
  let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
54084
54133
  const resourcesToSync = [];
54134
+ const headerRecovery = { remaining: PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT };
54085
54135
  for (const resource of resourceSources) {
54086
- if (yield this.isProtocolV2ResourceBundleUpToDate(resource)) {
54136
+ if (yield this.isProtocolV2ResourceBundleUpToDateWithRecovery(resource, headerRecovery)) {
54087
54137
  Log$6.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
54088
54138
  }
54089
54139
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.8-alpha.115",
3
+ "version": "1.2.8-alpha.117",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.8-alpha.115",
29
- "@onekeyfe/hd-transport": "1.2.8-alpha.115",
28
+ "@onekeyfe/hd-shared": "1.2.8-alpha.117",
29
+ "@onekeyfe/hd-transport": "1.2.8-alpha.117",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "4f30138a51d564cd664fa9886f30229bebe469a5"
49
+ "gitHead": "610f1a979c682817acf9b4d195365dc5ed806138"
50
50
  }
@@ -103,6 +103,7 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
103
103
  const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
104
104
  const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
105
105
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
106
+ const PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT = 2;
106
107
  // Bootloaders with the 2048-byte BLE UART FIFO have no room beside one waiting FileWrite
107
108
  // frame, so a second in-flight write can overflow it and lose a chunk. Firmware has not yet
108
109
  // confirmed which bootloader version ships the 4096-byte FIFO, nor that the BLE coprocessor
@@ -404,6 +405,23 @@ const getProtocolV2ErrorCode = (error: unknown): number | string | undefined =>
404
405
  return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
405
406
  };
406
407
 
408
+ /** Errors that mean the link is gone, so the read says nothing about the installed bundle. */
409
+ const isProtocolV2ResourceHeaderLinkError = (error: unknown) => {
410
+ if (isProtocolV2DeviceDisconnectedError(error)) return true;
411
+ const errorCode = getProtocolV2ErrorCode(error);
412
+ if (errorCode === HardwareErrorCode.BleTimeoutError) return true;
413
+ const message = getProtocolV2UnknownErrorText(error);
414
+ if (errorCode === HardwareErrorCode.TransportNotFound && /not connected/i.test(message)) {
415
+ return true;
416
+ }
417
+ return (
418
+ message.includes('ProtocolV2LinkError') ||
419
+ message.includes('Device protocol has not been detected') ||
420
+ message.includes('Electron BLE device state cleaned') ||
421
+ message.includes('Electron BLE transport released')
422
+ );
423
+ };
424
+
407
425
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
408
426
  error instanceof HardwareError &&
409
427
  (error.errorCode === HardwareErrorCode.ActionCancelled ||
@@ -1900,11 +1918,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1900
1918
  if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
1901
1919
  return true;
1902
1920
  } catch (error) {
1921
+ if (isProtocolV2ResourceHeaderLinkError(error)) throw error;
1903
1922
  Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
1904
1923
  return false;
1905
1924
  }
1906
1925
  }
1907
1926
 
1927
+ /**
1928
+ * A lost link would fail every remaining header check and resend bundles that are already
1929
+ * installed, so reconnect (a bounded number of times per transfer phase) and ask again.
1930
+ * Header reads have no side effects. If reconnecting fails or the budget is used up, the bundle
1931
+ * is resent as before; a failed identity check after reconnecting aborts the update.
1932
+ */
1933
+ private async isProtocolV2ResourceBundleUpToDateWithRecovery(
1934
+ bundle: ProtocolV2ResourceBundleSource,
1935
+ recovery: { remaining: number }
1936
+ ): Promise<boolean> {
1937
+ for (;;) {
1938
+ try {
1939
+ return await this.isProtocolV2ResourceBundleUpToDate(bundle);
1940
+ } catch (error) {
1941
+ this.throwIfAborted();
1942
+ if (recovery.remaining <= 0) {
1943
+ Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
1944
+ return false;
1945
+ }
1946
+ recovery.remaining -= 1;
1947
+ Log.log(
1948
+ `[FirmwareUpdateV4] RESC bundle ${bundle.name} header check lost the link; reconnecting: `,
1949
+ getProtocolV2UnknownErrorText(error)
1950
+ );
1951
+ try {
1952
+ await this.recoverProtocolV2FileTransfer();
1953
+ } catch (recoverError) {
1954
+ this.throwIfAborted();
1955
+ // Mismatch or unavailable: never keep writing to a device that was not re-verified.
1956
+ if (
1957
+ this.normalizeErrorMessage(recoverError).includes(
1958
+ 'Protocol V2 reconnect physical identity'
1959
+ )
1960
+ ) {
1961
+ throw recoverError;
1962
+ }
1963
+ recovery.remaining = 0;
1964
+ Log.log('[FirmwareUpdateV4] RESC header check reconnect failed: ', recoverError);
1965
+ return false;
1966
+ }
1967
+ this.throwIfAborted();
1968
+ }
1969
+ }
1970
+ }
1971
+
1908
1972
  private isProtocolV2BootloaderMode() {
1909
1973
  if (typeof this.device.isBootloader === 'function') {
1910
1974
  return !!this.device.isBootloader();
@@ -2143,8 +2207,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2143
2207
  }: ProtocolV2TransferBatch) {
2144
2208
  let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
2145
2209
  const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
2210
+ const headerRecovery = { remaining: PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT };
2146
2211
  for (const resource of resourceSources) {
2147
- if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
2212
+ if (await this.isProtocolV2ResourceBundleUpToDateWithRecovery(resource, headerRecovery)) {
2148
2213
  Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
2149
2214
  } else {
2150
2215
  resourcesToSync.push(resource);