@onekeyfe/hd-core 1.2.0-alpha.96 → 1.2.0-alpha.97

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.
Files changed (89) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +84 -404
  2. package/__tests__/firmware-update/device-update-bootloader.test.ts +2 -6
  3. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -9
  4. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +112 -0
  5. package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -131
  6. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +1 -53
  7. package/__tests__/protocol-v2-resources.test.ts +232 -148
  8. package/__tests__/protocol-v2.test.ts +517 -1106
  9. package/dist/api/CheckAllFirmwareRelease.d.ts +3 -2
  10. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  11. package/dist/api/CheckBLEFirmwareRelease.d.ts +2 -3
  12. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  13. package/dist/api/CheckBootloaderRelease.d.ts +1 -2
  14. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  15. package/dist/api/CheckFirmwareRelease.d.ts +2 -3
  16. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  17. package/dist/api/FirmwareUpdateV2.d.ts +0 -1
  18. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  19. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  20. package/dist/api/FirmwareUpdateV4.d.ts +8 -6
  21. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  22. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  23. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  24. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  25. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  26. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  27. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +1 -48
  28. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  29. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  30. package/dist/api/firmware/getBinary.d.ts +1 -0
  31. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  32. package/dist/data-manager/DataManager.d.ts +3 -4
  33. package/dist/data-manager/DataManager.d.ts.map +1 -1
  34. package/dist/index.d.ts +111 -103
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +849 -1313
  37. package/dist/protocols/protocol-v2/resources.d.ts +28 -10
  38. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  39. package/dist/types/api/checkAllFirmwareRelease.d.ts +8 -20
  40. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  41. package/dist/types/api/checkBLEFirmwareRelease.d.ts +13 -2
  42. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  43. package/dist/types/api/checkBootloaderRelease.d.ts +6 -2
  44. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  45. package/dist/types/api/checkFirmwareRelease.d.ts +13 -2
  46. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  47. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  48. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  49. package/dist/types/api/export.d.ts +1 -1
  50. package/dist/types/api/export.d.ts.map +1 -1
  51. package/dist/types/api/firmwareUpdate.d.ts +11 -3
  52. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  53. package/dist/types/settings.d.ts +36 -35
  54. package/dist/types/settings.d.ts.map +1 -1
  55. package/package.json +4 -4
  56. package/src/api/CheckAllFirmwareRelease.ts +80 -108
  57. package/src/api/CheckBLEFirmwareRelease.ts +5 -37
  58. package/src/api/CheckBootloaderRelease.ts +3 -35
  59. package/src/api/CheckFirmwareRelease.ts +7 -35
  60. package/src/api/FirmwareUpdateV2.ts +3 -15
  61. package/src/api/FirmwareUpdateV3.ts +7 -29
  62. package/src/api/FirmwareUpdateV4.ts +407 -824
  63. package/src/api/UploadPortfolio.ts +0 -18
  64. package/src/api/device/DeviceUpdateBootloader.ts +5 -26
  65. package/src/api/firmware/FirmwareHostBinding.ts +3 -16
  66. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +2 -0
  67. package/src/api/firmware/FirmwareUpdatePlan.ts +88 -226
  68. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +3 -10
  69. package/src/data-manager/DataManager.ts +19 -35
  70. package/src/index.ts +0 -10
  71. package/src/protocols/protocol-v2/resources.ts +341 -157
  72. package/src/types/api/checkAllFirmwareRelease.ts +12 -27
  73. package/src/types/api/checkBLEFirmwareRelease.ts +13 -4
  74. package/src/types/api/checkBootloaderRelease.ts +6 -2
  75. package/src/types/api/checkFirmwareRelease.ts +13 -2
  76. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  77. package/src/types/api/export.ts +1 -6
  78. package/src/types/api/firmwareUpdate.ts +16 -10
  79. package/src/types/settings.ts +61 -35
  80. package/src/utils/deviceFeaturesUtils.ts +2 -2
  81. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -199
  82. package/__tests__/firmware-memory-host.test.ts +0 -112
  83. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +0 -469
  84. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  85. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  86. package/dist/api/firmware/protocolV2Release.d.ts +0 -33
  87. package/dist/api/firmware/protocolV2Release.d.ts.map +0 -1
  88. package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
  89. package/src/api/firmware/protocolV2Release.ts +0 -168
@@ -1,8 +1,11 @@
1
+ import { EDeviceType } from '@onekeyfe/hd-shared';
2
+
1
3
  import FirmwareUpdate from '../../src/api/FirmwareUpdate';
2
4
  import FirmwareUpdateV2 from '../../src/api/FirmwareUpdateV2';
3
5
  import FirmwareUpdateV3 from '../../src/api/FirmwareUpdateV3';
4
6
  import { BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS } from '../../src/api/firmware/FirmwareUpdateBaseMethod';
5
7
  import { DataManager } from '../../src/data-manager';
8
+ import { DevicePool } from '../../src/device/DevicePool';
6
9
 
7
10
  import type { Device } from '../../src/device/Device';
8
11
 
@@ -12,6 +15,7 @@ jest.mock('../../src/data/config', () => ({
12
15
  }));
13
16
 
14
17
  const BLE_ID = 'ble-device';
18
+ const WEBUSB_ID = 'webusb-device';
15
19
 
16
20
  const flush = () =>
17
21
  new Promise(resolve => {
@@ -198,3 +202,111 @@ describe.each([
198
202
  expect(maxActiveProbes).toBe(1);
199
203
  });
200
204
  });
205
+
206
+ describe.each([
207
+ [
208
+ 'FirmwareUpdateV2',
209
+ () =>
210
+ new FirmwareUpdateV2({
211
+ id: 1,
212
+ payload: { method: 'firmwareUpdateV2', connectId: WEBUSB_ID },
213
+ }),
214
+ ],
215
+ [
216
+ 'FirmwareUpdateV3 (base method)',
217
+ () =>
218
+ new FirmwareUpdateV3({
219
+ id: 1,
220
+ payload: { method: 'firmwareUpdateV3', connectId: WEBUSB_ID },
221
+ }),
222
+ ],
223
+ ])('%s WebUSB bootloader authorization', (_name, buildMethod) => {
224
+ beforeEach(() => {
225
+ jest.useFakeTimers({ doNotFake: ['setImmediate', 'performance'] });
226
+ });
227
+
228
+ afterEach(() => {
229
+ jest.clearAllTimers();
230
+ jest.useRealTimers();
231
+ jest.restoreAllMocks();
232
+ });
233
+
234
+ test('pauses the reboot deadline while waiting for browser authorization', async () => {
235
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
236
+ jest.spyOn(DataManager, 'isBleConnect').mockReturnValue(false);
237
+ jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(true);
238
+
239
+ const method = buildMethod();
240
+ let permissionGranted = false;
241
+ const bootloaderDevice = {
242
+ isBootloader: jest.fn(() => true),
243
+ } as unknown as Device;
244
+ jest.spyOn(DevicePool, 'getDevices').mockImplementation(() =>
245
+ Promise.resolve({
246
+ devices: {},
247
+ deviceList: permissionGranted ? [bootloaderDevice] : [],
248
+ })
249
+ );
250
+
251
+ const commands = { disposed: true };
252
+ const updateFromCache = jest.fn();
253
+ method.device = {
254
+ originalDescriptor: {
255
+ id: WEBUSB_ID,
256
+ path: WEBUSB_ID,
257
+ protocolType: 'V1',
258
+ },
259
+ deviceConnector: {
260
+ enumerate: jest.fn(() =>
261
+ Promise.resolve({
262
+ descriptors: permissionGranted ? [{ path: 'bootloader-device' }] : [],
263
+ })
264
+ ),
265
+ },
266
+ commands,
267
+ updateFromCache,
268
+ getCurrentDeviceType: jest.fn(() => EDeviceType.Pro),
269
+ } as unknown as Device;
270
+ method.postTipMessage = jest.fn();
271
+
272
+ let resolvePrompt: (deviceId: string) => void = () => undefined;
273
+ const promptPromise = new Promise<string>(resolve => {
274
+ resolvePrompt = resolve;
275
+ });
276
+ const prompt = jest
277
+ .spyOn(method as any, '_promptDeviceInBootloaderForWebDevice')
278
+ .mockReturnValue(promptPromise);
279
+
280
+ method.checkDeviceToBootloader(WEBUSB_ID);
281
+ const resolved = jest.fn();
282
+ const rejected = jest.fn();
283
+ method.checkPromise?.promise.then(resolved, rejected);
284
+
285
+ for (let elapsed = 0; elapsed < 15000; elapsed += 500) {
286
+ jest.advanceTimersByTime(500);
287
+ // eslint-disable-next-line no-await-in-loop
288
+ await flush();
289
+ if (prompt.mock.calls.length) break;
290
+ }
291
+
292
+ expect(prompt).toHaveBeenCalledTimes(1);
293
+
294
+ // Browser permission depends on a user gesture and must not consume the
295
+ // automatic reboot detection deadline.
296
+ for (let elapsed = 0; elapsed < 35000; elapsed += 500) {
297
+ jest.advanceTimersByTime(500);
298
+ // eslint-disable-next-line no-await-in-loop
299
+ await flush();
300
+ }
301
+ expect(rejected).not.toHaveBeenCalled();
302
+
303
+ permissionGranted = true;
304
+ resolvePrompt('bootloader-device');
305
+ await flush();
306
+ await flush();
307
+
308
+ expect(resolved).toHaveBeenCalledWith(true);
309
+ expect(updateFromCache).toHaveBeenCalledWith(bootloaderDevice);
310
+ expect(commands.disposed).toBe(false);
311
+ });
312
+ });
@@ -1,10 +1,6 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
4
- import {
5
- assertFirmwareUpdatePreparedPlanDeviceIdentity,
6
- prepareFirmwareUpdatePlan,
7
- } from '../../src/api/firmware/FirmwareUpdatePreparedPlan';
8
4
 
9
5
  import type { Features } from '../../src/types';
10
6
 
@@ -301,6 +297,12 @@ describe('buildFirmwareUpdatePlan', () => {
301
297
  forceTarget: 'firmware' as const,
302
298
  release: {
303
299
  components: {},
300
+ resourceBundles: [
301
+ {
302
+ name: 'images',
303
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
304
+ },
305
+ ],
304
306
  },
305
307
  },
306
308
  {
@@ -310,8 +312,6 @@ describe('buildFirmwareUpdatePlan', () => {
310
312
  applicationP1: {
311
313
  target: 'APPLICATION_P1',
312
314
  url: 'https://firmware.onekey.so/pro2/application-p1.bin',
313
- expectedSize: 1024,
314
- fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
315
315
  },
316
316
  },
317
317
  },
@@ -325,23 +325,41 @@ describe('buildFirmwareUpdatePlan', () => {
325
325
  }
326
326
  );
327
327
 
328
- test('limits a Pro2 firmware force to component artifacts', () => {
329
- const plan = buildFirmwareUpdatePlan(
330
- createProtocolV2ForceInput(['firmware'], {
331
- components: {
332
- applicationP1: {
333
- target: 'APPLICATION_P1',
334
- url: 'https://firmware.onekey.so/pro2/application-p1.bin',
335
- expectedSize: 1024,
336
- fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
328
+ test.each([
329
+ {
330
+ forceTarget: 'firmware' as const,
331
+ expectedArtifactIds: ['component:app_v1'],
332
+ expectedTargets: ['app_v1'],
333
+ },
334
+ {
335
+ forceTarget: 'resource' as const,
336
+ expectedArtifactIds: ['resourceBundle:images'],
337
+ expectedTargets: ['resource'],
338
+ },
339
+ ])(
340
+ 'limits a Pro2 $forceTarget force to the selected artifact role',
341
+ ({ forceTarget, expectedArtifactIds, expectedTargets }) => {
342
+ const plan = buildFirmwareUpdatePlan(
343
+ createProtocolV2ForceInput([forceTarget], {
344
+ components: {
345
+ applicationP1: {
346
+ target: 'APPLICATION_P1',
347
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
348
+ },
337
349
  },
338
- },
339
- })
340
- );
350
+ resourceBundles: [
351
+ {
352
+ name: 'images',
353
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
354
+ },
355
+ ],
356
+ })
357
+ );
341
358
 
342
- expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(['component:app_v1']);
343
- expect(plan.targetsToUpdate).toEqual(['app_v1']);
344
- });
359
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(expectedArtifactIds);
360
+ expect(plan.targetsToUpdate).toEqual(expectedTargets);
361
+ }
362
+ );
345
363
 
346
364
  test('rejects a prepared native plan without a stable device identity', () => {
347
365
  const features = createFeatures({
@@ -366,72 +384,7 @@ describe('buildFirmwareUpdatePlan', () => {
366
384
  ).toThrow('requires a stable device identity');
367
385
  });
368
386
 
369
- test('allows an identity-less Protocol V2 device while retaining the model binding', () => {
370
- const features = createFeatures({
371
- deviceType: EDeviceType.Pro2,
372
- });
373
- delete (features as Partial<Features>).serialNo;
374
-
375
- const plan = buildFirmwareUpdatePlan({
376
- features,
377
- firmwareType: EFirmwareType.Universal,
378
- platform: 'desktop',
379
- firmware: {
380
- status: 'outdated',
381
- release: {
382
- components: {
383
- applicationP1: {
384
- target: 'APPLICATION_P1',
385
- url: 'https://firmware.onekey.so/pro2/application-p1.bin',
386
- expectedSize: 1024,
387
- fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
388
- },
389
- },
390
- },
391
- },
392
- ble: noUpdate,
393
- bootloader: noUpdate,
394
- });
395
-
396
- expect(plan).toMatchObject({
397
- executor: 'v4',
398
- deviceIdentity: 'unavailable',
399
- deviceModel: String(EDeviceType.Pro2),
400
- });
401
-
402
- const preparedPlan = prepareFirmwareUpdatePlan({
403
- plan,
404
- leaseRef: 'fwlease:00000000-0000-4000-8000-000000000004',
405
- artifacts: [
406
- {
407
- artifactId: 'component:app_v1',
408
- artifact: {
409
- artifactRef: `fw:${'1'.repeat(64)}`,
410
- size: 1024,
411
- sha256: '1'.repeat(64),
412
- },
413
- },
414
- ],
415
- });
416
- expect(() =>
417
- assertFirmwareUpdatePreparedPlanDeviceIdentity({
418
- preparedPlan,
419
- deviceIdentity: undefined,
420
- bootloaderMode: false,
421
- deviceModel: String(EDeviceType.Pro2),
422
- })
423
- ).not.toThrow();
424
- expect(() =>
425
- assertFirmwareUpdatePreparedPlanDeviceIdentity({
426
- preparedPlan,
427
- deviceIdentity: undefined,
428
- bootloaderMode: false,
429
- deviceModel: String(EDeviceType.Neo),
430
- })
431
- ).toThrow();
432
- });
433
-
434
- test('maps the Pro2 component order without downloading artifacts', () => {
387
+ test('maps the Pro2 component order and resource bundles without downloading them', () => {
435
388
  const plan = buildFirmwareUpdatePlan({
436
389
  features: createFeatures({ deviceType: EDeviceType.Pro2 }),
437
390
  firmwareType: EFirmwareType.Universal,
@@ -450,16 +403,18 @@ describe('buildFirmwareUpdatePlan', () => {
450
403
  bootloader: {
451
404
  target: 'BOOTLOADER',
452
405
  url: 'https://firmware.onekey.so/pro2/bootloader.bin',
453
- expectedSize: 2048,
454
- fingerprint: '2222222222222222222222222222222222222222222222222222222222222222',
455
406
  },
456
407
  se01: {
457
408
  target: 'SE01',
458
409
  url: 'https://firmware.onekey.so/pro2/se01.bin',
459
- expectedSize: 512,
460
- fingerprint: '3333333333333333333333333333333333333333333333333333333333333333',
461
410
  },
462
411
  },
412
+ resourceBundles: [
413
+ {
414
+ name: 'images',
415
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
416
+ },
417
+ ],
463
418
  },
464
419
  },
465
420
  ble: noUpdate,
@@ -471,8 +426,9 @@ describe('buildFirmwareUpdatePlan', () => {
471
426
  'component:boot',
472
427
  'component:app_v1',
473
428
  'component:se01',
429
+ 'resourceBundle:images',
474
430
  ]);
475
- expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01']);
431
+ expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01', 'resource']);
476
432
  expect(plan.artifacts[1]).toEqual(
477
433
  expect.objectContaining({
478
434
  expectedSize: 1024,
@@ -481,41 +437,6 @@ describe('buildFirmwareUpdatePlan', () => {
481
437
  );
482
438
  });
483
439
 
484
- test.each([
485
- {
486
- expectedSize: undefined,
487
- fingerprint: '1'.repeat(64),
488
- },
489
- {
490
- expectedSize: 1024,
491
- fingerprint: undefined,
492
- },
493
- ])('rejects a remote Pro2 component without complete integrity metadata', integrity => {
494
- expectFirmwarePlanInvalid(
495
- () =>
496
- buildFirmwareUpdatePlan({
497
- features: createFeatures({ deviceType: EDeviceType.Pro2 }),
498
- firmwareType: EFirmwareType.Universal,
499
- platform: 'desktop',
500
- firmware: {
501
- status: 'required',
502
- release: {
503
- components: {
504
- applicationP1: {
505
- target: 'APPLICATION_P1',
506
- url: 'https://firmware.onekey.so/pro2/application-p1.bin',
507
- ...integrity,
508
- },
509
- },
510
- },
511
- },
512
- ble: noUpdate,
513
- bootloader: noUpdate,
514
- }),
515
- 'integrity metadata is invalid'
516
- );
517
- });
518
-
519
440
  test('rejects Pro2 ROMLOADER because it requires a separate loader flow', () => {
520
441
  expect(() =>
521
442
  buildFirmwareUpdatePlan({
@@ -547,19 +468,37 @@ describe('buildFirmwareUpdatePlan', () => {
547
468
  application: {
548
469
  target: 'APPLICATION_P1',
549
470
  url: 'https://firmware.onekey.so/pro2/application.bin',
550
- expectedSize: 1024,
551
- fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
552
471
  },
553
472
  applicationDuplicate: {
554
473
  target: 'APPLICATION_P1',
555
474
  url: 'https://firmware.onekey.so/pro2/application-duplicate.bin',
556
- expectedSize: 1024,
557
- fingerprint: '2222222222222222222222222222222222222222222222222222222222222222',
558
475
  },
559
476
  },
560
477
  },
561
478
  expectedError: 'duplicates target app_v1',
562
479
  },
480
+ {
481
+ label: 'resource bundle name',
482
+ release: {
483
+ components: {
484
+ application: {
485
+ target: 'APPLICATION_P1',
486
+ url: 'https://firmware.onekey.so/pro2/application.bin',
487
+ },
488
+ },
489
+ resourceBundles: [
490
+ {
491
+ name: 'images',
492
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
493
+ },
494
+ {
495
+ name: 'images',
496
+ url: 'https://firmware.onekey.so/pro2/images-duplicate.okpkg',
497
+ },
498
+ ],
499
+ },
500
+ expectedError: 'duplicates name images',
501
+ },
563
502
  ])('rejects a duplicate Pro2 $label', ({ release, expectedError }) => {
564
503
  expect(() =>
565
504
  buildFirmwareUpdatePlan({
@@ -1,9 +1,6 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
- import {
4
- buildFirmwareUpdatePlan,
5
- digestFirmwareUpdateContract,
6
- } from '../../src/api/firmware/FirmwareUpdatePlan';
3
+ import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
7
4
  import {
8
5
  assertFirmwareUpdatePreparedPlanBinding,
9
6
  assertFirmwareUpdatePreparedPlanDeviceIdentity,
@@ -212,55 +209,6 @@ describe('FirmwareUpdatePreparedPlan', () => {
212
209
  ).toThrow('entry name is invalid');
213
210
  });
214
211
 
215
- test('rejects duplicate materialized entry names during standalone validation', () => {
216
- const plan = buildPlan();
217
- const prepared = prepareFirmwareUpdatePlan({
218
- plan,
219
- leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
220
- artifacts: [
221
- {
222
- artifactId: 'firmware',
223
- artifact: artifact('a'.repeat(64), 4),
224
- },
225
- {
226
- artifactId: 'resource',
227
- artifact: artifact('b'.repeat(64), 8),
228
- materializedEntries: [
229
- {
230
- entryName: 'resource/index.bin',
231
- artifact: artifact('c'.repeat(64), 2),
232
- },
233
- ],
234
- },
235
- ],
236
- });
237
- const resourceArtifact = prepared.artifacts.find(item => item.target === 'resource')!;
238
- const tamperedWithoutDigest = {
239
- ...prepared,
240
- artifacts: prepared.artifacts.map(item =>
241
- item === resourceArtifact
242
- ? {
243
- ...item,
244
- materializedEntries: [
245
- ...resourceArtifact.materializedEntries!,
246
- {
247
- entryName: 'other/index.bin',
248
- artifact: artifact('d'.repeat(64), 2),
249
- },
250
- ],
251
- }
252
- : item
253
- ),
254
- };
255
- const { preparedPlanDigest: _preparedPlanDigest, ...digestInput } = tamperedWithoutDigest;
256
- const tampered = {
257
- ...tamperedWithoutDigest,
258
- preparedPlanDigest: digestFirmwareUpdateContract(digestInput),
259
- };
260
-
261
- expect(() => validateFirmwareUpdatePreparedPlan(tampered)).toThrow('duplicate entry names');
262
- });
263
-
264
212
  test('rejects unknown fields even when a caller presents a plan-shaped object', () => {
265
213
  const plan = buildPlan();
266
214
  expect(() =>