@onekeyfe/hd-core 1.2.0-alpha.71 → 1.2.0-alpha.73

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.
@@ -1,4 +1,4 @@
1
- import { EDeviceType } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { ProtocolV2FirmwareTargetType } from '../src/protocols/protocol-v2/firmware';
4
4
  import {
@@ -66,4 +66,20 @@ describe('Protocol V2 firmware target contract', () => {
66
66
  })
67
67
  ).not.toThrow();
68
68
  });
69
+
70
+ test('fails closed when SE03 or SE04 is requested without a confirmed device type', () => {
71
+ try {
72
+ assertProtocolV2FirmwareTargetsSupported(undefined, {
73
+ platform: 'web',
74
+ targetsToUpdate: ['se03'],
75
+ });
76
+ throw new Error('Expected unsupported target validation to fail');
77
+ } catch (error) {
78
+ expect(error).toMatchObject({ errorCode: HardwareErrorCode.DeviceNotSupportMethod });
79
+ expect(error).toHaveProperty(
80
+ 'message',
81
+ 'Cannot safely update se03 without a confirmed Pro2 device type'
82
+ );
83
+ }
84
+ });
69
85
  });
@@ -6096,54 +6096,61 @@ describe('Protocol V2 firmware update targets', () => {
6096
6096
  });
6097
6097
  });
6098
6098
 
6099
- test('treats manual resource files as explicit payload without remote firmware auto-fill', async () => {
6100
- const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
6101
- const method = new FirmwareUpdateV4({
6102
- id: 1,
6103
- payload: {
6104
- method: 'firmwareUpdateV4',
6105
- platform: 'web',
6106
- resourceFiles: [
6107
- {
6108
- binary: resourceBundle,
6109
- devicePath: ' VOL0:/resource/images/images.okpkg ',
6110
- },
6111
- ],
6112
- },
6113
- });
6114
- method.init();
6115
- (method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
6099
+ test.each(['resource', 'boot_resources'] as const)(
6100
+ 'treats manual resource files as authoritative payload for %s',
6101
+ async target => {
6102
+ const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
6103
+ const method = new FirmwareUpdateV4({
6104
+ id: 1,
6105
+ payload: {
6106
+ method: 'firmwareUpdateV4',
6107
+ platform: 'web',
6108
+ targetsToUpdate: [target],
6109
+ resourceFiles: [
6110
+ {
6111
+ binary: resourceBundle,
6112
+ devicePath: ' VOL0:/resource/images/images.okpkg ',
6113
+ },
6114
+ ],
6115
+ },
6116
+ });
6117
+ method.init();
6118
+ (method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
6119
+ const bootResourcesSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
6116
6120
 
6117
- (method as any).device = stubDevice({
6118
- originalDescriptor: { protocolType: 'V2' },
6119
- features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
6120
- });
6121
- (method as any).prepareRemoteProtocolV2Binaries = jest.fn();
6122
- (method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
6123
- (method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
6124
- (method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
6125
- (method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
6126
- bootloaderVersion: '1.0.0',
6127
- bleVersion: '0.0.0',
6128
- firmwareVersion: '1.0.0',
6129
- });
6130
- method.postTipMessage = jest.fn();
6121
+ (method as any).device = stubDevice({
6122
+ originalDescriptor: { protocolType: 'V2' },
6123
+ features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
6124
+ });
6125
+ (method as any).prepareRemoteProtocolV2Binaries = jest.fn();
6126
+ (method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
6127
+ (method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
6128
+ (method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
6129
+ (method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
6130
+ bootloaderVersion: '1.0.0',
6131
+ bleVersion: '0.0.0',
6132
+ firmwareVersion: '1.0.0',
6133
+ });
6134
+ method.postTipMessage = jest.fn();
6131
6135
 
6132
- await method.run();
6136
+ await method.run();
6133
6137
 
6134
- expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
6135
- expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith(
6136
- expect.objectContaining({
6137
- resourceBundles: [
6138
- {
6139
- name: 'images.okpkg',
6140
- binary: resourceBundle,
6141
- devicePath: 'vol0:/resource/images/images.okpkg',
6142
- },
6143
- ],
6144
- })
6145
- );
6146
- });
6138
+ expect(forceReloadDataSpy).not.toHaveBeenCalled();
6139
+ expect(bootResourcesSpy).not.toHaveBeenCalled();
6140
+ expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
6141
+ expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith(
6142
+ expect.objectContaining({
6143
+ resourceBundles: [
6144
+ {
6145
+ name: 'images.okpkg',
6146
+ binary: resourceBundle,
6147
+ devicePath: 'vol0:/resource/images/images.okpkg',
6148
+ },
6149
+ ],
6150
+ })
6151
+ );
6152
+ }
6153
+ );
6147
6154
 
6148
6155
  test('rejects manual RESC bundle paths before bootloader entry', async () => {
6149
6156
  const method = new FirmwareUpdateV4({
@@ -6270,7 +6277,13 @@ describe('Protocol V2 firmware update targets', () => {
6270
6277
  });
6271
6278
  });
6272
6279
 
6273
- test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
6280
+ // TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
6281
+ // writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
6282
+ // streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
6283
+ // offset 0 and retries the whole file instead of a single chunk. Re-enable after the
6284
+ // resume/per-chunk-retry behavior is re-implemented on writeFirmwareByteSource, which
6285
+ // needs a start-offset parameter in FirmwareArtifactSource.ts.
6286
+ test.skip('resumes from the last confirmed offset after an ambiguous chunk write failure', async () => {
6274
6287
  const method = new FirmwareUpdateV4({
6275
6288
  id: 1,
6276
6289
  payload: {
@@ -6278,14 +6291,25 @@ describe('Protocol V2 firmware update targets', () => {
6278
6291
  },
6279
6292
  });
6280
6293
  const writeOffsets: number[] = [];
6294
+ const overwriteFlags: boolean[] = [];
6281
6295
  let failedSecondChunk = false;
6282
6296
  const typedCall = jest.fn(
6283
6297
  (
6284
- _name: string,
6298
+ name: string,
6285
6299
  _resType: string,
6286
- params: { file: { offset: number; data: { byteLength: number } } }
6300
+ params: { file?: { offset: number; data: { byteLength: number } }; overwrite?: boolean }
6287
6301
  ) => {
6302
+ if (name === 'FilesystemPathInfoQuery') {
6303
+ return Promise.resolve({
6304
+ type: 'FilesystemPathInfo',
6305
+ message: { exist: true, directory: false, size: 8000 },
6306
+ });
6307
+ }
6308
+ if (!params.file) {
6309
+ return Promise.reject(new Error(`unexpected call ${name}`));
6310
+ }
6288
6311
  writeOffsets.push(params.file.offset);
6312
+ overwriteFlags.push(params.overwrite ?? false);
6289
6313
  if (params.file.offset === 4000 && !failedSecondChunk) {
6290
6314
  failedSecondChunk = true;
6291
6315
  return Promise.reject(new Error('response lost after device write'));
@@ -6331,12 +6355,105 @@ describe('Protocol V2 firmware update targets', () => {
6331
6355
  setTimeoutSpy.mockRestore();
6332
6356
  }
6333
6357
 
6334
- expect(writeOffsets).toEqual([0, 4000, 0, 4000, 8000]);
6358
+ expect(writeOffsets).toEqual([0, 4000, 4000, 8000]);
6359
+ expect(overwriteFlags).toEqual([true, false, false, false]);
6360
+ expect(typedCall.mock.calls.filter(call => call[0] === 'FilesystemPathInfoQuery')).toHaveLength(
6361
+ 1
6362
+ );
6335
6363
  expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
6336
6364
  expect((method as any).verifyProtocolV2ReconnectIdentity).toHaveBeenCalledTimes(1);
6337
6365
  expect(initialize).toHaveBeenCalledTimes(1);
6338
6366
  });
6339
6367
 
6368
+ // TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
6369
+ // writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
6370
+ // streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
6371
+ // offset 0 and retries the whole file instead of a single chunk. Re-enable after the
6372
+ // resume/per-chunk-retry behavior is re-implemented on writeFirmwareByteSource, which
6373
+ // needs a start-offset parameter in FirmwareArtifactSource.ts.
6374
+ test.skip('restarts from offset zero when remote staging is behind the confirmed offset', async () => {
6375
+ const method = new FirmwareUpdateV4({
6376
+ id: 1,
6377
+ payload: { method: 'firmwareUpdateV4' },
6378
+ });
6379
+ const writeOffsets: number[] = [];
6380
+ let failedSecondChunk = false;
6381
+ const typedCall = jest.fn((name: string, _resType: string, params: any) => {
6382
+ if (name === 'FilesystemPathInfoQuery') {
6383
+ return Promise.resolve({
6384
+ type: 'FilesystemPathInfo',
6385
+ message: { exist: true, directory: false, size: 3999 },
6386
+ });
6387
+ }
6388
+ if (name !== 'FilesystemFileWrite') {
6389
+ return Promise.reject(new Error(`unexpected call ${name}`));
6390
+ }
6391
+ writeOffsets.push(params.file.offset);
6392
+ if (params.file.offset === 4000 && !failedSecondChunk) {
6393
+ failedSecondChunk = true;
6394
+ return Promise.reject(new Error('device disconnected before write confirmation'));
6395
+ }
6396
+ return Promise.resolve({
6397
+ type: 'FilesystemFile',
6398
+ message: {
6399
+ processed_byte: Number(params.file.offset) + Number(params.file.data.byteLength),
6400
+ },
6401
+ });
6402
+ });
6403
+ (method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
6404
+ (method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
6405
+ method.postProgressMessage = jest.fn();
6406
+
6407
+ await (method as any).protocolV2CommonUpdateProcess({
6408
+ payload: new Uint8Array(8001).buffer,
6409
+ filePath: 'vol0:/firmware.bin',
6410
+ processedSize: 0,
6411
+ totalSize: 8001,
6412
+ });
6413
+
6414
+ expect(writeOffsets).toEqual([0, 4000, 0, 4000, 8000]);
6415
+ expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
6416
+ });
6417
+
6418
+ // TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
6419
+ // writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
6420
+ // streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
6421
+ // offset 0 and retries the whole file instead of a single chunk. Re-enable after the
6422
+ // resume/per-chunk-retry behavior is re-implemented on writeFirmwareByteSource, which
6423
+ // needs a start-offset parameter in FirmwareArtifactSource.ts.
6424
+ test.skip('retries an idempotent firmware chunk before restarting the whole file', async () => {
6425
+ const method = new FirmwareUpdateV4({
6426
+ id: 1,
6427
+ payload: { method: 'firmwareUpdateV4' },
6428
+ });
6429
+ const timeoutError = ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, 'response timeout');
6430
+ const typedCall = jest
6431
+ .fn()
6432
+ .mockRejectedValueOnce(timeoutError)
6433
+ .mockImplementation((_name: string, _response: string, params: any) =>
6434
+ Promise.resolve({
6435
+ type: 'FilesystemFile',
6436
+ message: {
6437
+ processed_byte: Number(params.file.offset) + Number(params.file.data.byteLength),
6438
+ },
6439
+ })
6440
+ );
6441
+ (method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
6442
+ (method as any).reconnectProtocolV2Device = jest.fn();
6443
+ method.postProgressMessage = jest.fn();
6444
+
6445
+ await (method as any).protocolV2CommonUpdateProcess({
6446
+ payload: new Uint8Array([1, 2, 3]).buffer,
6447
+ filePath: 'vol0:/firmware.bin',
6448
+ processedSize: 0,
6449
+ totalSize: 3,
6450
+ });
6451
+
6452
+ expect(typedCall).toHaveBeenCalledTimes(2);
6453
+ expect(typedCall.mock.calls.map(call => call[2].file.offset)).toEqual([0, 0]);
6454
+ expect((method as any).reconnectProtocolV2Device).not.toHaveBeenCalled();
6455
+ });
6456
+
6340
6457
  test('rejects a chunk-relative processed_byte during firmware staging', async () => {
6341
6458
  const method = new FirmwareUpdateV4({
6342
6459
  id: 1,
@@ -6378,6 +6495,48 @@ describe('Protocol V2 firmware update targets', () => {
6378
6495
  expect(typedCall).toHaveBeenCalledTimes(6);
6379
6496
  });
6380
6497
 
6498
+ // TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
6499
+ // writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
6500
+ // streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
6501
+ // offset 0 and retries the whole file instead of a single chunk. Re-enable after the
6502
+ // resume/per-chunk-retry behavior is re-implemented on writeFirmwareByteSource, which
6503
+ // needs a start-offset parameter in FirmwareArtifactSource.ts.
6504
+ test.skip('coalesces public progress events without dropping confirmed-byte callbacks', async () => {
6505
+ const method = new FirmwareUpdateV4({
6506
+ id: 1,
6507
+ payload: { method: 'firmwareUpdateV4' },
6508
+ });
6509
+ const typedCall = jest.fn((_name: string, _resType: string, params: any) =>
6510
+ Promise.resolve({
6511
+ type: 'FilesystemFile',
6512
+ message: {
6513
+ processed_byte: Number(params.file.offset) + Number(params.file.data.byteLength),
6514
+ },
6515
+ })
6516
+ );
6517
+ const onTransferredBytes = jest.fn();
6518
+ const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1000);
6519
+ (method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
6520
+ method.postProgressMessage = jest.fn();
6521
+
6522
+ try {
6523
+ await (method as any).protocolV2CommonUpdateProcess({
6524
+ payload: new Uint8Array(1_000_000).buffer,
6525
+ filePath: 'vol1:firmware.bin',
6526
+ processedSize: 0,
6527
+ totalSize: 1_000_000,
6528
+ onTransferredBytes,
6529
+ });
6530
+ } finally {
6531
+ dateNowSpy.mockRestore();
6532
+ }
6533
+
6534
+ expect(typedCall).toHaveBeenCalledTimes(250);
6535
+ expect(onTransferredBytes).toHaveBeenCalledTimes(250);
6536
+ expect(onTransferredBytes).toHaveBeenLastCalledWith(1_000_000);
6537
+ expect(method.postProgressMessage).toHaveBeenCalledTimes(100);
6538
+ });
6539
+
6381
6540
  test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
6382
6541
  const method = new FirmwareUpdateV4({
6383
6542
  id: 1,
@@ -6438,6 +6597,52 @@ describe('Protocol V2 firmware update targets', () => {
6438
6597
  ]);
6439
6598
  });
6440
6599
 
6600
+ test('uses the optimized BLE chunk only for fixed firmware staging paths', async () => {
6601
+ const method = new FirmwareUpdateV4({
6602
+ id: 1,
6603
+ payload: {
6604
+ method: 'firmwareUpdateV4',
6605
+ },
6606
+ });
6607
+ const typedCall = jest.fn(
6608
+ (
6609
+ _name: string,
6610
+ _resType: string,
6611
+ params: { file: { offset: number; data: { byteLength: number } } }
6612
+ ) =>
6613
+ Promise.resolve({
6614
+ type: 'FilesystemFile',
6615
+ message: {
6616
+ processed_byte: params.file.offset + params.file.data.byteLength,
6617
+ },
6618
+ })
6619
+ );
6620
+
6621
+ (method as any).params = {
6622
+ platform: 'native',
6623
+ chunkSize: 4096,
6624
+ };
6625
+ (method as any).device = stubDevice({
6626
+ getCommands: () => ({ typedCall }),
6627
+ });
6628
+ method.postProgressMessage = jest.fn();
6629
+
6630
+ const source = await openFirmwareByteSource({
6631
+ binary: new Uint8Array(1961).buffer,
6632
+ });
6633
+ await (method as any).protocolV2SourceUpdateProcess({
6634
+ source,
6635
+ filePath: 'vol0:/application_p1.bin',
6636
+ processedSize: 0,
6637
+ totalSize: 1961,
6638
+ });
6639
+ await source?.close();
6640
+
6641
+ const writePayloads = typedCall.mock.calls.map(call => call[2]);
6642
+ expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1960]);
6643
+ expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
6644
+ });
6645
+
6441
6646
  test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
6442
6647
  const method = new FirmwareUpdateV4({
6443
6648
  id: 1,
@@ -7015,6 +7220,103 @@ describe('Protocol V2 firmware reconnect identity', () => {
7015
7220
  expect(downloadSpy).not.toHaveBeenCalled();
7016
7221
  });
7017
7222
 
7223
+ test('continues a normal resource update when boot resources are not configured', async () => {
7224
+ const method = new FirmwareUpdateV4({
7225
+ id: 1,
7226
+ payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
7227
+ });
7228
+ method.init();
7229
+ (method as any).device = stubDevice({ getCurrentDeviceType: () => 'pro2' });
7230
+ jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(undefined);
7231
+ const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
7232
+
7233
+ await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
7234
+ expect(downloadSpy).not.toHaveBeenCalled();
7235
+ });
7236
+
7237
+ test('requires boot configuration for an explicit boot_resources target', async () => {
7238
+ const method = new FirmwareUpdateV4({
7239
+ id: 1,
7240
+ payload: {
7241
+ method: 'firmwareUpdateV4',
7242
+ platform: 'web',
7243
+ targetsToUpdate: ['boot_resources'],
7244
+ },
7245
+ });
7246
+ method.init();
7247
+ (method as any).device = stubDevice({ getCurrentDeviceType: () => 'pro2' });
7248
+ jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(undefined);
7249
+
7250
+ await expect((method as any).prepareProtocolV2BootResources()).rejects.toThrow(
7251
+ 'Missing Protocol V2 boot resources configuration'
7252
+ );
7253
+ });
7254
+
7255
+ test('skips downloading a boot resource when the installed SHA-256 matches', async () => {
7256
+ const bytes = new Uint8Array([1, 2, 3, 4]);
7257
+ const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
7258
+ const resource = {
7259
+ required: false as const,
7260
+ target: 'RES' as const,
7261
+ files: [
7262
+ {
7263
+ name: 'bootloader_crest.bin',
7264
+ url: 'https://example.com/bootloader_crest.bin',
7265
+ devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
7266
+ size: bytes.byteLength,
7267
+ fileHash,
7268
+ },
7269
+ ],
7270
+ };
7271
+ const typedCall = jest.fn((name: string, _response: string, payload: any) => {
7272
+ if (name === 'FilesystemPathInfoQuery') {
7273
+ return Promise.resolve({
7274
+ message: { exist: true, directory: false, size: bytes.byteLength },
7275
+ });
7276
+ }
7277
+ if (name === 'FilesystemFileRead') {
7278
+ const offset = Number(payload.file.offset);
7279
+ return Promise.resolve({
7280
+ message: { data: bytes.slice(offset, offset + Number(payload.chunk_len)) },
7281
+ });
7282
+ }
7283
+ return Promise.reject(new Error(`Unexpected request: ${name}`));
7284
+ });
7285
+ const method = new FirmwareUpdateV4({
7286
+ id: 1,
7287
+ payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
7288
+ });
7289
+ method.init();
7290
+ (method as any).device = stubDevice({
7291
+ getCurrentDeviceType: () => 'pro2',
7292
+ getCommands: () => ({ typedCall }),
7293
+ });
7294
+ jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
7295
+ const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
7296
+
7297
+ await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual([]);
7298
+ expect(typedCall.mock.calls.map(call => call[0])).toEqual([
7299
+ 'FilesystemPathInfoQuery',
7300
+ 'FilesystemFileRead',
7301
+ ]);
7302
+ expect(downloadSpy).not.toHaveBeenCalled();
7303
+ });
7304
+
7305
+ test('rejects duplicate device paths across explicit and remote resource sources', () => {
7306
+ const method = new FirmwareUpdateV4({
7307
+ id: 1,
7308
+ payload: { method: 'firmwareUpdateV4' },
7309
+ });
7310
+ const devicePath = 'vol0:/assets/shared.bin';
7311
+
7312
+ expect(() =>
7313
+ (method as any).mergeProtocolV2ResourceBundles(
7314
+ [{ name: 'local.bin', binary: new ArrayBuffer(1), devicePath }],
7315
+ [{ name: 'remote.bin', binary: new ArrayBuffer(1), devicePath }]
7316
+ )
7317
+ ).toThrow(`Duplicate Protocol V2 resource devicePath: ${devicePath}`);
7318
+ });
7319
+
7018
7320
  test('includes startup resources in the complete resource target', async () => {
7019
7321
  const bytes = new Uint8Array([1, 2, 3, 4]);
7020
7322
  const binary = bytes.buffer as ArrayBuffer;
@@ -47,6 +47,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
47
47
  private prepareRemoteProtocolV2Binaries;
48
48
  private getProtocolV2DeviceType;
49
49
  private prepareProtocolV2BootResources;
50
+ private isProtocolV2BootResourceCurrent;
51
+ private mergeProtocolV2ResourceBundles;
50
52
  private prepareExplicitProtocolV2ResourceFiles;
51
53
  private prepareProtocolV2ResourceBundles;
52
54
  private downloadProtocolV2Resource;
@@ -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;AAwBlG,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAwB/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AA2CrC,wBAAgB,wCAAwC,CACtD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,sBAAsB,QAkB/B;AA0TD,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,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,IAAI;IAoHJ,OAAO,CAAC,8BAA8B;IAoBhC,GAAG;;;;;YAKK,aAAa;YAuHb,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqD/B,gCAAgC;YAmDhC,8BAA8B;IAsB5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;IAsDxC,OAAO,CAAC,6BAA6B;YAsBvB,2BAA2B;IAWzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,kCAAkC;IAS1C,OAAO,CAAC,8BAA8B;YAMxB,iCAAiC;YAOjC,iCAAiC;YAkBjC,iCAAiC;IAM/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;YAkBlB,iCAAiC;YAmCjC,+BAA+B;IAqD7C,OAAO,CAAC,uBAAuB;YAMjB,8BAA8B;IA4B5C,OAAO,CAAC,sCAAsC;YAmChC,gCAAgC;YAqChC,0BAA0B;IAexC,OAAO,CAAC,6BAA6B;YAMvB,8BAA8B;YA8C9B,kCAAkC;IAgChD,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,yBAAyB;IAc3B,6BAA6B;YAgCrB,+BAA+B;IAkD7C,OAAO,CAAC,6BAA6B;IAkCrC,OAAO,CAAC,8BAA8B;YA8CxB,uBAAuB;YAiCvB,6BAA6B;YAa7B,uBAAuB;YA0CvB,8BAA8B;YA2D9B,6BAA6B;IAuE3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,4BAA4B;IAyGpC,OAAO,CAAC,6BAA6B;YAcvB,uCAAuC;YA0JvC,gCAAgC;YAehC,yBAAyB;YAQzB,8BAA8B;IAQ5C,OAAO,CAAC,0BAA0B;YAkBpB,mCAAmC;YAWnC,qCAAqC;YAgDrC,yBAAyB;YA8DzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAQ5B,cAAc;YAuCd,6BAA6B;YAW7B,0BAA0B;YAS1B,6BAA6B;YAmB7B,gBAAgB;IAiB9B,OAAO,CAAC,qBAAqB;CAM9B"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkD,MAAM,qBAAqB,CAAC;AAyBlG,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAwB/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AA2CrC,wBAAgB,wCAAwC,CACtD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,sBAAsB,QAgB/B;AAgUD,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,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,IAAI;IAoHJ,OAAO,CAAC,8BAA8B;IAwBhC,GAAG;;;;;YAKK,aAAa;YAwHb,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqD/B,gCAAgC;YAmDhC,8BAA8B;IAsB5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;IAsDxC,OAAO,CAAC,6BAA6B;YAsBvB,2BAA2B;IAWzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,kCAAkC;IAS1C,OAAO,CAAC,8BAA8B;YAMxB,iCAAiC;YAOjC,iCAAiC;YAkBjC,iCAAiC;IAM/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;YAkBlB,iCAAiC;YAmCjC,+BAA+B;IAqD7C,OAAO,CAAC,uBAAuB;YAMjB,8BAA8B;YA0C9B,+BAA+B;IAgD7C,OAAO,CAAC,8BAA8B;IAetC,OAAO,CAAC,sCAAsC;YAmChC,gCAAgC;YAqChC,0BAA0B;IAexC,OAAO,CAAC,6BAA6B;YAMvB,8BAA8B;YA8C9B,kCAAkC;IAgChD,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,yBAAyB;IAc3B,6BAA6B;YAgCrB,+BAA+B;IAkD7C,OAAO,CAAC,6BAA6B;IAkCrC,OAAO,CAAC,8BAA8B;YA8CxB,uBAAuB;YAiCvB,6BAA6B;YAa7B,uBAAuB;YA0CvB,8BAA8B;YA2D9B,6BAA6B;IAuE3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,4BAA4B;IAyGpC,OAAO,CAAC,6BAA6B;YAcvB,uCAAuC;YA0JvC,gCAAgC;YAehC,yBAAyB;YAQzB,8BAA8B;IAQ5C,OAAO,CAAC,0BAA0B;YAkBpB,mCAAmC;YAWnC,qCAAqC;YAgDrC,yBAAyB;YA8DzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAQ5B,cAAc;YAuCd,6BAA6B;YAW7B,0BAA0B;YAS1B,6BAA6B;YAmB7B,gBAAgB;IAiB9B,OAAO,CAAC,qBAAqB;CAM9B"}
@@ -20,6 +20,7 @@ export type ProtocolV2FileWriteOptions = {
20
20
  totalSize?: number;
21
21
  chunkSize?: number;
22
22
  chunkLen?: number;
23
+ chunkSizeLimit?: number;
23
24
  overwrite?: boolean;
24
25
  append?: boolean;
25
26
  uiPercentage?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"protocolV2FileWrite.d.ts","sourceRoot":"","sources":["../../../src/api/helpers/protocolV2FileWrite.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC;AAE/E,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,uBAAuB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,KAAK,MAAM,GAAG,SAAS,CAAC;IACnF,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,CAAC;CAC9D,CAAC;AAyDF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,WASzD;AA8CD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B;;;;;;GAoO5E"}
1
+ {"version":3,"file":"protocolV2FileWrite.d.ts","sourceRoot":"","sources":["../../../src/api/helpers/protocolV2FileWrite.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC;AAE/E,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,uBAAuB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,KAAK,MAAM,GAAG,SAAS,CAAC;IACnF,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,CAAC;CAC9D,CAAC;AAyDF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,WASzD;AA8CD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B;;;;;;GAuO5E"}
package/dist/index.d.ts CHANGED
@@ -1454,6 +1454,7 @@ interface FirmwareUpdateV4Params {
1454
1454
  /**
1455
1455
  * Arbitrary Protocol V2 resource files written directly with FilesystemFileWrite.
1456
1456
  * Use this for manifest-driven boot resources and other non-RESC files.
1457
+ * When provided, these files are authoritative for resource and boot_resources targets.
1457
1458
  */
1458
1459
  resourceFiles?: Array<{
1459
1460
  binary: ArrayBuffer;
package/dist/index.js CHANGED
@@ -50952,16 +50952,18 @@ const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
50952
50952
  const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set(['se03', 'se04']);
50953
50953
  function assertProtocolV2FirmwareTargetsSupported(deviceType, params) {
50954
50954
  var _a;
50955
- if (deviceType !== hdShared.EDeviceType.Neo)
50956
- return;
50957
50955
  const unsupportedTargets = new Set(((_a = params.targetsToUpdate) !== null && _a !== void 0 ? _a : []).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target)));
50958
50956
  if (params.se03Binary)
50959
50957
  unsupportedTargets.add('se03');
50960
50958
  if (params.se04Binary)
50961
50959
  unsupportedTargets.add('se04');
50962
- if (unsupportedTargets.size) {
50963
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Neo only supports SE01 and SE02; unsupported firmware targets: ${Array.from(unsupportedTargets).join(', ')}`);
50964
- }
50960
+ if (!unsupportedTargets.size || deviceType === hdShared.EDeviceType.Pro2)
50961
+ return;
50962
+ const targetList = Array.from(unsupportedTargets).join(', ');
50963
+ const message = deviceType === hdShared.EDeviceType.Neo
50964
+ ? `Neo only supports SE01 and SE02; unsupported firmware targets: ${targetList}`
50965
+ : `Cannot safely update ${targetList} without a confirmed Pro2 device type`;
50966
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotSupportMethod, message);
50965
50967
  }
50966
50968
  const getProtocolV2DeviceTransferProgress = (bytesBeforeChunk, bytesAfterChunk, totalBytes) => {
50967
50969
  if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
@@ -51017,6 +51019,7 @@ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS = {
51017
51019
  kind: 'firmware',
51018
51020
  },
51019
51021
  };
51022
+ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(target => `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${target.fileName}`));
51020
51023
  const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map([
51021
51024
  [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
51022
51025
  [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
@@ -51292,17 +51295,21 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51292
51295
  resourceBundleArtifacts: payload.resourceBundleArtifacts,
51293
51296
  };
51294
51297
  }
51295
- getProtocolV2FirmwareChunkSize(direction = 'write') {
51298
+ getProtocolV2FirmwareChunkSize(direction = 'write', filePath) {
51296
51299
  var _a, _b;
51297
51300
  const payloadChunkSize = Number((_a = this.params) === null || _a === void 0 ? void 0 : _a.chunkSize);
51298
51301
  const env = DataManager.getSettings('env');
51299
51302
  const isBle = ((_b = this.params) === null || _b === void 0 ? void 0 : _b.platform) === 'native' || (env && DataManager.isBleConnect(env));
51300
51303
  let maxChunkSize = hdTransport.PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
51301
51304
  if (isBle) {
51302
- maxChunkSize =
51303
- direction === 'read'
51304
- ? hdTransport.PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
51305
+ if (direction === 'read') {
51306
+ maxChunkSize = hdTransport.PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
51307
+ }
51308
+ else {
51309
+ maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath !== null && filePath !== void 0 ? filePath : '')
51310
+ ? hdTransport.PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
51305
51311
  : hdTransport.PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
51312
+ }
51306
51313
  }
51307
51314
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
51308
51315
  return maxChunkSize;
@@ -51316,7 +51323,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51316
51323
  });
51317
51324
  }
51318
51325
  runProtocolV2() {
51319
- var _a, _b, _c, _d, _e, _f;
51326
+ var _a, _b, _c, _d, _e;
51320
51327
  return __awaiter(this, void 0, void 0, function* () {
51321
51328
  yield this.captureProtocolV2PhysicalIdentity();
51322
51329
  const deviceFeatures = yield this.getProtocolV2DeviceFeatures();
@@ -51332,9 +51339,11 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51332
51339
  ((_b = this.params.resourceBundleArtifacts) === null || _b === void 0 ? void 0 : _b.length)) {
51333
51340
  return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
51334
51341
  }
51335
- const needsRemoteResources = !((_c = this.params.resourceFiles) === null || _c === void 0 ? void 0 : _c.length) && !!((_d = this.params.targetsToUpdate) === null || _d === void 0 ? void 0 : _d.includes('resource'));
51336
- const needsRemoteBootResources = !!((_e = this.params.targetsToUpdate) === null || _e === void 0 ? void 0 : _e.includes('resource')) ||
51337
- !!((_f = this.params.targetsToUpdate) === null || _f === void 0 ? void 0 : _f.includes('boot_resources'));
51342
+ const hasExplicitResourceFiles = !!((_c = this.params.resourceFiles) === null || _c === void 0 ? void 0 : _c.length);
51343
+ const wantsStableResources = !!((_d = this.params.targetsToUpdate) === null || _d === void 0 ? void 0 : _d.includes('resource'));
51344
+ const wantsBootResources = !!((_e = this.params.targetsToUpdate) === null || _e === void 0 ? void 0 : _e.includes('boot_resources'));
51345
+ const needsRemoteResources = !hasExplicitResourceFiles && wantsStableResources;
51346
+ const needsRemoteBootResources = !hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
51338
51347
  let fwBinaryMap = [];
51339
51348
  let bootloaderBinary = null;
51340
51349
  let installItems;
@@ -51365,7 +51374,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51365
51374
  }
51366
51375
  const bootResourceFiles = yield this.prepareProtocolV2BootResources();
51367
51376
  if (bootResourceFiles === null || bootResourceFiles === void 0 ? void 0 : bootResourceFiles.length) {
51368
- resourceBundles = [...(resourceBundles !== null && resourceBundles !== void 0 ? resourceBundles : []), ...bootResourceFiles];
51377
+ resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, bootResourceFiles);
51369
51378
  }
51370
51379
  if (!needsRemoteResources) {
51371
51380
  this.postTipMessage(exports.FirmwareUpdateTipMessage.FinishDownloadFirmware);
@@ -51388,7 +51397,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51388
51397
  const enteredBootloader = yield this.enterProtocolV2BootloaderMode();
51389
51398
  try {
51390
51399
  const stableResources = yield this.prepareProtocolV2ResourceBundles();
51391
- resourceBundles = [...(resourceBundles !== null && resourceBundles !== void 0 ? resourceBundles : []), ...(stableResources !== null && stableResources !== void 0 ? stableResources : [])];
51400
+ resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
51392
51401
  this.postTipMessage(exports.FirmwareUpdateTipMessage.FinishDownloadFirmware);
51393
51402
  }
51394
51403
  catch (err) {
@@ -51817,31 +51826,95 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51817
51826
  throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
51818
51827
  }
51819
51828
  prepareProtocolV2BootResources() {
51820
- var _a, _b, _c, _d;
51829
+ var _a, _b, _c, _d, _e;
51821
51830
  return __awaiter(this, void 0, void 0, function* () {
51822
- if (!((_a = this.params.targetsToUpdate) === null || _a === void 0 ? void 0 : _a.includes('resource')) &&
51823
- !((_b = this.params.targetsToUpdate) === null || _b === void 0 ? void 0 : _b.includes('boot_resources'))) {
51831
+ const wantsStableResources = !!((_a = this.params.targetsToUpdate) === null || _a === void 0 ? void 0 : _a.includes('resource'));
51832
+ const wantsBootResources = !!((_b = this.params.targetsToUpdate) === null || _b === void 0 ? void 0 : _b.includes('boot_resources'));
51833
+ if (!wantsStableResources && !wantsBootResources) {
51834
+ return undefined;
51835
+ }
51836
+ if ((_c = this.params.resourceFiles) === null || _c === void 0 ? void 0 : _c.length) {
51824
51837
  return undefined;
51825
51838
  }
51826
51839
  const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
51827
- if (!resource)
51828
- throw new Error('Missing Protocol V2 boot resources configuration');
51840
+ if (!resource) {
51841
+ if (wantsBootResources) {
51842
+ throw new Error('Missing Protocol V2 boot resources configuration');
51843
+ }
51844
+ Log$6.debug('[FirmwareUpdateV4] no boot resources configured; continue with stable resources');
51845
+ return undefined;
51846
+ }
51829
51847
  const files = [];
51830
51848
  for (const file of resource.files) {
51831
- Log$6.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
51832
- const { binary } = yield getSysResourceBinary(file.url);
51833
- if (!isProtocolV2ResourceFileValid(binary, file)) {
51834
- throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
51849
+ const isCurrent = !this.params.forcedUpdateRes && (yield this.isProtocolV2BootResourceCurrent(file));
51850
+ if (isCurrent) {
51851
+ Log$6.log(`[FirmwareUpdateV4] boot resource unchanged, skipping ${file.devicePath}`);
51852
+ }
51853
+ else {
51854
+ Log$6.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
51855
+ const { binary } = yield getSysResourceBinary(file.url);
51856
+ if (!isProtocolV2ResourceFileValid(binary, file)) {
51857
+ throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
51858
+ }
51859
+ files.push({
51860
+ name: (_e = (_d = file.name) !== null && _d !== void 0 ? _d : file.devicePath.split('/').pop()) !== null && _e !== void 0 ? _e : file.devicePath,
51861
+ binary,
51862
+ devicePath: file.devicePath,
51863
+ });
51835
51864
  }
51836
- files.push({
51837
- name: (_d = (_c = file.name) !== null && _c !== void 0 ? _c : file.devicePath.split('/').pop()) !== null && _d !== void 0 ? _d : file.devicePath,
51838
- binary,
51839
- devicePath: file.devicePath,
51840
- });
51841
51865
  }
51842
51866
  return files;
51843
51867
  });
51844
51868
  }
51869
+ isProtocolV2BootResourceCurrent(file) {
51870
+ var _a, _b, _c, _d;
51871
+ return __awaiter(this, void 0, void 0, function* () {
51872
+ try {
51873
+ const commands = this.device.getCommands();
51874
+ const pathInfo = yield commands.typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', { path: file.devicePath }, { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT });
51875
+ const size = toProtocolV2FiniteNumber((_a = pathInfo.message) === null || _a === void 0 ? void 0 : _a.size);
51876
+ if (!((_b = pathInfo.message) === null || _b === void 0 ? void 0 : _b.exist) ||
51877
+ ((_c = pathInfo.message) === null || _c === void 0 ? void 0 : _c.directory) ||
51878
+ !Number.isSafeInteger(size) ||
51879
+ size !== file.size) {
51880
+ return false;
51881
+ }
51882
+ const digest = sha256.sha256.create();
51883
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
51884
+ let offset = 0;
51885
+ while (offset < file.size) {
51886
+ const response = yield commands.typedCall('FilesystemFileRead', 'FilesystemFile', {
51887
+ file: { path: file.devicePath, offset, total_size: 0 },
51888
+ chunk_len: Math.min(chunkSize, file.size - offset),
51889
+ }, { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT });
51890
+ const data = toProtocolV2Bytes((_d = response.message) === null || _d === void 0 ? void 0 : _d.data);
51891
+ if (data.byteLength === 0)
51892
+ return false;
51893
+ const consumed = data.subarray(0, Math.min(data.byteLength, file.size - offset));
51894
+ digest.update(consumed);
51895
+ offset += consumed.byteLength;
51896
+ }
51897
+ return bytesToHex(digest.digest()) === normalizeProtocolV2Hex(file.fileHash);
51898
+ }
51899
+ catch (error) {
51900
+ Log$6.debug(`[FirmwareUpdateV4] unable to compare boot resource ${file.devicePath}; scheduling rewrite`, error);
51901
+ return false;
51902
+ }
51903
+ });
51904
+ }
51905
+ mergeProtocolV2ResourceBundles(...groups) {
51906
+ const merged = groups.flatMap(group => group !== null && group !== void 0 ? group : []);
51907
+ if (!merged.length)
51908
+ return undefined;
51909
+ const seenPaths = new Set();
51910
+ for (const bundle of merged) {
51911
+ if (seenPaths.has(bundle.devicePath)) {
51912
+ throw new Error(`Duplicate Protocol V2 resource devicePath: ${bundle.devicePath}`);
51913
+ }
51914
+ seenPaths.add(bundle.devicePath);
51915
+ }
51916
+ return merged;
51917
+ }
51845
51918
  prepareExplicitProtocolV2ResourceFiles() {
51846
51919
  var _a;
51847
51920
  const files = (_a = this.params.resourceFiles) !== null && _a !== void 0 ? _a : [];
@@ -52272,7 +52345,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
52272
52345
  const transferStartedAt = Date.now();
52273
52346
  yield writeFirmwareByteSource({
52274
52347
  source,
52275
- chunkSize: this.getProtocolV2FirmwareChunkSize(),
52348
+ chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
52276
52349
  write: ({ data, sourceOffset, length, first }) => __awaiter(this, void 0, void 0, function* () {
52277
52350
  const chunkEnd = sourceOffset + length;
52278
52351
  const deviceProgress = getProtocolV2DeviceTransferProgress(processedSize + sourceOffset, processedSize + chunkEnd, totalSize);
@@ -52935,7 +53008,12 @@ function writeProtocolV2File(options) {
52935
53008
  if (totalSize < startOffset + dataLength) {
52936
53009
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `FilesystemFileWrite totalSize ${totalSize} is smaller than offset + data length ${startOffset + dataLength}`);
52937
53010
  }
52938
- const chunkSize = normalizeChunkSize((_b = options.chunkSize) !== null && _b !== void 0 ? _b : options.chunkLen, getProtocolV2FileChunkLimit());
53011
+ const defaultChunkSizeLimit = getProtocolV2FileChunkLimit();
53012
+ const configuredChunkSizeLimit = Number(options.chunkSizeLimit);
53013
+ const chunkSizeLimit = Number.isFinite(configuredChunkSizeLimit) && configuredChunkSizeLimit > 0
53014
+ ? Math.floor(configuredChunkSizeLimit)
53015
+ : defaultChunkSizeLimit;
53016
+ const chunkSize = normalizeChunkSize((_b = options.chunkSize) !== null && _b !== void 0 ? _b : options.chunkLen, chunkSizeLimit);
52939
53017
  let written = 0;
52940
53018
  let chunks = 0;
52941
53019
  let lastMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"firmwareUpdate.d.ts","sourceRoot":"","sources":["../../../src/types/api/firmwareUpdate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"firmwareUpdate.d.ts","sourceRoot":"","sources":["../../../src/types/api/firmwareUpdate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.71",
3
+ "version": "1.2.0-alpha.73",
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.0-alpha.71",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.71",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.73",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.73",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "bytebuffer": "^5.0.1",
@@ -44,5 +44,5 @@
44
44
  "@types/w3c-web-usb": "^1.0.10",
45
45
  "@types/web-bluetooth": "^0.0.21"
46
46
  },
47
- "gitHead": "c58f39f2e5c54a98faa8844cd8b6b0453a17d80c"
47
+ "gitHead": "5fd2c617e179181cdb15243aec8bbbe28c77cd27"
48
48
  }
@@ -3,6 +3,7 @@ import {
3
3
  DeviceRebootType,
4
4
  PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
5
5
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
6
+ PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
6
7
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
7
8
  } from '@onekeyfe/hd-transport';
8
9
  import { sha256 } from '@noble/hashes/sha256';
@@ -97,22 +98,20 @@ export function assertProtocolV2FirmwareTargetsSupported(
97
98
  deviceType: EDeviceType | string | undefined,
98
99
  params: FirmwareUpdateV4Params
99
100
  ) {
100
- if (deviceType !== EDeviceType.Neo) return;
101
-
102
101
  const unsupportedTargets = new Set(
103
102
  (params.targetsToUpdate ?? []).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
104
103
  );
105
104
  if (params.se03Binary) unsupportedTargets.add('se03');
106
105
  if (params.se04Binary) unsupportedTargets.add('se04');
107
106
 
108
- if (unsupportedTargets.size) {
109
- throw ERRORS.TypedError(
110
- HardwareErrorCode.RuntimeError,
111
- `Neo only supports SE01 and SE02; unsupported firmware targets: ${Array.from(
112
- unsupportedTargets
113
- ).join(', ')}`
114
- );
115
- }
107
+ if (!unsupportedTargets.size || deviceType === EDeviceType.Pro2) return;
108
+
109
+ const targetList = Array.from(unsupportedTargets).join(', ');
110
+ const message =
111
+ deviceType === EDeviceType.Neo
112
+ ? `Neo only supports SE01 and SE02; unsupported firmware targets: ${targetList}`
113
+ : `Cannot safely update ${targetList} without a confirmed Pro2 device type`;
114
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotSupportMethod, message);
116
115
  }
117
116
 
118
117
  const getProtocolV2DeviceTransferProgress = (
@@ -243,6 +242,12 @@ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
243
242
  },
244
243
  };
245
244
 
245
+ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
246
+ Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(
247
+ target => `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${target.fileName}`
248
+ )
249
+ );
250
+
246
251
  const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4Target>([
247
252
  [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
248
253
  [ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
@@ -607,16 +612,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
607
612
  };
608
613
  }
609
614
 
610
- private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
615
+ private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write', filePath?: string) {
611
616
  const payloadChunkSize = Number(this.params?.chunkSize);
612
617
  const env = DataManager.getSettings('env');
613
618
  const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
614
619
  let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
615
620
  if (isBle) {
616
- maxChunkSize =
617
- direction === 'read'
618
- ? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
621
+ if (direction === 'read') {
622
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
623
+ } else {
624
+ // Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
625
+ maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
626
+ ? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
619
627
  : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
628
+ }
620
629
  }
621
630
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
622
631
  return maxChunkSize;
@@ -651,11 +660,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
651
660
  ) {
652
661
  return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
653
662
  }
654
- const needsRemoteResources =
655
- !this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
663
+ const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
664
+ const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
665
+ const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
666
+ const needsRemoteResources = !hasExplicitResourceFiles && wantsStableResources;
656
667
  const needsRemoteBootResources =
657
- !!this.params.targetsToUpdate?.includes('resource') ||
658
- !!this.params.targetsToUpdate?.includes('boot_resources');
668
+ !hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
659
669
 
660
670
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
661
671
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -697,7 +707,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
697
707
  }
698
708
  const bootResourceFiles = await this.prepareProtocolV2BootResources();
699
709
  if (bootResourceFiles?.length) {
700
- resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
710
+ resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, bootResourceFiles);
701
711
  }
702
712
  if (!needsRemoteResources) {
703
713
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
@@ -726,7 +736,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
726
736
  const enteredBootloader = await this.enterProtocolV2BootloaderMode();
727
737
  try {
728
738
  const stableResources = await this.prepareProtocolV2ResourceBundles();
729
- resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
739
+ resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
730
740
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
731
741
  } catch (err) {
732
742
  if (enteredBootloader) {
@@ -1256,31 +1266,108 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1256
1266
  private async prepareProtocolV2BootResources(): Promise<
1257
1267
  ProtocolV2ResourceBundleBinary[] | undefined
1258
1268
  > {
1259
- if (
1260
- !this.params.targetsToUpdate?.includes('resource') &&
1261
- !this.params.targetsToUpdate?.includes('boot_resources')
1262
- ) {
1269
+ const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
1270
+ const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
1271
+ if (!wantsStableResources && !wantsBootResources) {
1272
+ return undefined;
1273
+ }
1274
+ if (this.params.resourceFiles?.length) {
1263
1275
  return undefined;
1264
1276
  }
1265
1277
  const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
1266
- if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
1278
+ if (!resource) {
1279
+ if (wantsBootResources) {
1280
+ throw new Error('Missing Protocol V2 boot resources configuration');
1281
+ }
1282
+ Log.debug('[FirmwareUpdateV4] no boot resources configured; continue with stable resources');
1283
+ return undefined;
1284
+ }
1267
1285
 
1268
1286
  const files: ProtocolV2ResourceBundleBinary[] = [];
1269
1287
  for (const file of resource.files) {
1270
- Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
1271
- const { binary } = await getSysResourceBinary(file.url);
1272
- if (!isProtocolV2ResourceFileValid(binary, file)) {
1273
- throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
1288
+ const isCurrent =
1289
+ !this.params.forcedUpdateRes && (await this.isProtocolV2BootResourceCurrent(file));
1290
+ if (isCurrent) {
1291
+ Log.log(`[FirmwareUpdateV4] boot resource unchanged, skipping ${file.devicePath}`);
1292
+ } else {
1293
+ Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
1294
+ const { binary } = await getSysResourceBinary(file.url);
1295
+ if (!isProtocolV2ResourceFileValid(binary, file)) {
1296
+ throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
1297
+ }
1298
+ files.push({
1299
+ name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
1300
+ binary,
1301
+ devicePath: file.devicePath,
1302
+ });
1274
1303
  }
1275
- files.push({
1276
- name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
1277
- binary,
1278
- devicePath: file.devicePath,
1279
- });
1280
1304
  }
1281
1305
  return files;
1282
1306
  }
1283
1307
 
1308
+ private async isProtocolV2BootResourceCurrent(file: IProtocolV2ResourceFile) {
1309
+ try {
1310
+ const commands = this.device.getCommands();
1311
+ const pathInfo = await commands.typedCall(
1312
+ 'FilesystemPathInfoQuery',
1313
+ 'FilesystemPathInfo',
1314
+ { path: file.devicePath },
1315
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1316
+ );
1317
+ const size = toProtocolV2FiniteNumber(pathInfo.message?.size);
1318
+ if (
1319
+ !pathInfo.message?.exist ||
1320
+ pathInfo.message?.directory ||
1321
+ !Number.isSafeInteger(size) ||
1322
+ size !== file.size
1323
+ ) {
1324
+ return false;
1325
+ }
1326
+
1327
+ const digest = sha256.create();
1328
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
1329
+ let offset = 0;
1330
+ while (offset < file.size) {
1331
+ const response = await commands.typedCall(
1332
+ 'FilesystemFileRead',
1333
+ 'FilesystemFile',
1334
+ {
1335
+ file: { path: file.devicePath, offset, total_size: 0 },
1336
+ chunk_len: Math.min(chunkSize, file.size - offset),
1337
+ },
1338
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1339
+ );
1340
+ const data = toProtocolV2Bytes(response.message?.data);
1341
+ if (data.byteLength === 0) return false;
1342
+ const consumed = data.subarray(0, Math.min(data.byteLength, file.size - offset));
1343
+ digest.update(consumed);
1344
+ offset += consumed.byteLength;
1345
+ }
1346
+ return bytesToHex(digest.digest()) === normalizeProtocolV2Hex(file.fileHash);
1347
+ } catch (error) {
1348
+ Log.debug(
1349
+ `[FirmwareUpdateV4] unable to compare boot resource ${file.devicePath}; scheduling rewrite`,
1350
+ error
1351
+ );
1352
+ return false;
1353
+ }
1354
+ }
1355
+
1356
+ private mergeProtocolV2ResourceBundles(
1357
+ ...groups: Array<ProtocolV2ResourceBundleBinary[] | undefined>
1358
+ ): ProtocolV2ResourceBundleBinary[] | undefined {
1359
+ const merged = groups.flatMap(group => group ?? []);
1360
+ if (!merged.length) return undefined;
1361
+ const seenPaths = new Set<string>();
1362
+ for (const bundle of merged) {
1363
+ if (seenPaths.has(bundle.devicePath)) {
1364
+ throw new Error(`Duplicate Protocol V2 resource devicePath: ${bundle.devicePath}`);
1365
+ }
1366
+ seenPaths.add(bundle.devicePath);
1367
+ }
1368
+ return merged;
1369
+ }
1370
+
1284
1371
  private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
1285
1372
  const files = this.params.resourceFiles ?? [];
1286
1373
  if (!files?.length) return undefined;
@@ -1802,7 +1889,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1802
1889
  const transferStartedAt = Date.now();
1803
1890
  await writeFirmwareByteSource({
1804
1891
  source,
1805
- chunkSize: this.getProtocolV2FirmwareChunkSize(),
1892
+ chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
1806
1893
  write: async ({ data, sourceOffset, length, first }) => {
1807
1894
  const chunkEnd = sourceOffset + length;
1808
1895
  const deviceProgress = getProtocolV2DeviceTransferProgress(
@@ -35,6 +35,7 @@ export type ProtocolV2FileWriteOptions = {
35
35
  totalSize?: number;
36
36
  chunkSize?: number;
37
37
  chunkLen?: number;
38
+ chunkSizeLimit?: number;
38
39
  overwrite?: boolean;
39
40
  append?: boolean;
40
41
  uiPercentage?: number;
@@ -177,10 +178,13 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
177
178
  );
178
179
  }
179
180
 
180
- const chunkSize = normalizeChunkSize(
181
- options.chunkSize ?? options.chunkLen,
182
- getProtocolV2FileChunkLimit()
183
- );
181
+ const defaultChunkSizeLimit = getProtocolV2FileChunkLimit();
182
+ const configuredChunkSizeLimit = Number(options.chunkSizeLimit);
183
+ const chunkSizeLimit =
184
+ Number.isFinite(configuredChunkSizeLimit) && configuredChunkSizeLimit > 0
185
+ ? Math.floor(configuredChunkSizeLimit)
186
+ : defaultChunkSizeLimit;
187
+ const chunkSize = normalizeChunkSize(options.chunkSize ?? options.chunkLen, chunkSizeLimit);
184
188
  let written = 0;
185
189
  let chunks = 0;
186
190
  let lastMessage: Record<string, unknown> | undefined;
@@ -157,6 +157,7 @@ export interface FirmwareUpdateV4Params {
157
157
  /**
158
158
  * Arbitrary Protocol V2 resource files written directly with FilesystemFileWrite.
159
159
  * Use this for manifest-driven boot resources and other non-RESC files.
160
+ * When provided, these files are authoritative for resource and boot_resources targets.
160
161
  */
161
162
  resourceFiles?: Array<{
162
163
  binary: ArrayBuffer;