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

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 (98) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +404 -84
  2. package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
  3. package/__tests__/firmware-memory-host.test.ts +112 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +13 -2
  5. package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
  6. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +573 -0
  7. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
  8. package/__tests__/firmware-update/firmware-update-plan.test.ts +232 -70
  9. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
  10. package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
  11. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
  12. package/__tests__/protocol-v2-resources.test.ts +148 -232
  13. package/__tests__/protocol-v2.test.ts +1236 -516
  14. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
  15. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  16. package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
  17. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  18. package/dist/api/CheckBootloaderRelease.d.ts +2 -1
  19. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  20. package/dist/api/CheckFirmwareRelease.d.ts +3 -2
  21. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  22. package/dist/api/FirmwareUpdateV2.d.ts +1 -0
  23. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdateV4.d.ts +8 -8
  26. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  27. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  28. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  29. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  30. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  31. package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
  32. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
  33. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
  34. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
  35. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
  36. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  37. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +6 -1
  38. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  39. package/dist/api/firmware/getBinary.d.ts +0 -1
  40. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  41. package/dist/api/firmware/protocolV2Release.d.ts +33 -0
  42. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
  43. package/dist/data-manager/DataManager.d.ts +4 -3
  44. package/dist/data-manager/DataManager.d.ts.map +1 -1
  45. package/dist/index.d.ts +108 -114
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1633 -967
  48. package/dist/protocols/protocol-v2/resources.d.ts +10 -28
  49. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  50. package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
  51. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  52. package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
  53. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  54. package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
  55. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  56. package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
  57. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  58. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  59. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  60. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  61. package/dist/types/api/export.d.ts +1 -1
  62. package/dist/types/api/export.d.ts.map +1 -1
  63. package/dist/types/api/firmwareUpdate.d.ts +4 -12
  64. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  65. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  66. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  67. package/dist/types/settings.d.ts +35 -36
  68. package/dist/types/settings.d.ts.map +1 -1
  69. package/package.json +4 -4
  70. package/src/api/CheckAllFirmwareRelease.ts +108 -80
  71. package/src/api/CheckBLEFirmwareRelease.ts +37 -5
  72. package/src/api/CheckBootloaderRelease.ts +35 -3
  73. package/src/api/CheckFirmwareRelease.ts +35 -7
  74. package/src/api/FirmwareUpdateV2.ts +108 -73
  75. package/src/api/FirmwareUpdateV3.ts +101 -56
  76. package/src/api/FirmwareUpdateV4.ts +885 -403
  77. package/src/api/UploadPortfolio.ts +18 -0
  78. package/src/api/device/DeviceUpdateBootloader.ts +37 -11
  79. package/src/api/firmware/FirmwareHostBinding.ts +16 -3
  80. package/src/api/firmware/FirmwareMemoryHost.ts +142 -0
  81. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
  82. package/src/api/firmware/FirmwareUpdatePlan.ts +282 -122
  83. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +27 -3
  84. package/src/api/firmware/protocolV2Release.ts +168 -0
  85. package/src/data-manager/DataManager.ts +35 -19
  86. package/src/index.ts +10 -0
  87. package/src/protocols/protocol-v2/resources.ts +157 -341
  88. package/src/types/api/checkAllFirmwareRelease.ts +27 -12
  89. package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
  90. package/src/types/api/checkBootloaderRelease.ts +2 -6
  91. package/src/types/api/checkFirmwareRelease.ts +2 -13
  92. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  93. package/src/types/api/deviceUpdateBootloader.ts +1 -0
  94. package/src/types/api/export.ts +6 -1
  95. package/src/types/api/firmwareUpdate.ts +12 -17
  96. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  97. package/src/types/settings.ts +35 -61
  98. package/src/utils/deviceFeaturesUtils.ts +2 -2
@@ -5,7 +5,6 @@ import CheckAllFirmwareRelease, {
5
5
  } from '../src/api/CheckAllFirmwareRelease';
6
6
  import { DataManager } from '../src/data-manager';
7
7
  import { createCoreApi } from '../src/inject';
8
- import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
9
8
 
10
9
  import type { CoreApi } from '../src/types/api';
11
10
  import type { DeviceStateVersions, IFirmwareReleaseInfo } from '../src/types';
@@ -26,6 +25,11 @@ const currentVersions: DeviceStateVersions = {
26
25
  se04: '1.0.0',
27
26
  };
28
27
 
28
+ const validComponentIntegrity = {
29
+ expectedSize: 1024,
30
+ fingerprint: '1'.repeat(64),
31
+ };
32
+
29
33
  const release: IFirmwareReleaseInfo = {
30
34
  required: true,
31
35
  version: [2, 0, 0],
@@ -41,21 +45,25 @@ const release: IFirmwareReleaseInfo = {
41
45
  target: 'BOOTLOADER',
42
46
  url: 'https://example.com/bootloader.bin',
43
47
  version: [1, 1, 0],
48
+ ...validComponentIntegrity,
44
49
  },
45
50
  applicationP1: {
46
51
  target: 'APPLICATION_P1',
47
52
  url: 'https://example.com/application-p1.bin',
48
53
  version: [2, 0, 0],
54
+ ...validComponentIntegrity,
49
55
  },
50
56
  se02: {
51
57
  target: 'SE02',
52
58
  url: 'https://example.com/se02.bin',
53
59
  version: [2, 0, 0],
60
+ ...validComponentIntegrity,
54
61
  },
55
62
  se03: {
56
63
  target: 'SE03',
57
64
  url: 'https://example.com/se03.bin',
58
65
  version: [1, 0, 0],
66
+ ...validComponentIntegrity,
59
67
  },
60
68
  romloader: {
61
69
  target: 'ROMLOADER',
@@ -63,77 +71,12 @@ const release: IFirmwareReleaseInfo = {
63
71
  version: [2, 0, 0],
64
72
  },
65
73
  },
66
- resourceBundles: [
67
- {
68
- name: 'images',
69
- url: 'https://example.com/images.okpkg',
70
- devicePath: 'vol0:/bundles/images/images.okpkg',
71
- version: [1, 0, 0],
72
- },
73
- ],
74
74
  };
75
75
 
76
- const stableResources = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
77
- (type, index) => ({
78
- type,
79
- url: `https://example.com/${type}.okpkg`,
80
- size: 0x52a0 + index + 1,
81
- fileHash: 'a'.repeat(64),
82
- headerHash: index.toString(16).padStart(128, '0'),
83
- })
84
- );
85
-
86
- const createFilesystemTypedCall = (missingAll = false) => {
87
- const resourceByPath = new Map(
88
- stableResources.map(resource => [
89
- PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
90
- resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
91
- ],
92
- resource,
93
- ])
94
- );
95
- return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
96
- if (requestType === 'ResourceInventoryGet') {
97
- throw new Error('ResourceInventoryGet is unavailable on released firmware');
98
- }
99
- if (requestType === 'FilesystemPathInfoQuery') {
100
- const resource = resourceByPath.get(payload.path);
101
- return {
102
- message: {
103
- exist: Boolean(resource) && !missingAll,
104
- directory: false,
105
- size: missingAll ? 0 : resource?.size,
106
- },
107
- };
108
- }
109
- if (requestType === 'FilesystemFileRead') {
110
- const resource = resourceByPath.get(payload.file.path);
111
- if (!resource || missingAll) throw new Error('missing resource');
112
- const header = new Uint8Array(0x52a0);
113
- const view = new DataView(header.buffer);
114
- 'OKPP'.split('').forEach((char, index) => {
115
- header[index] = char.charCodeAt(0);
116
- });
117
- 'RESC'.split('').forEach((char, index) => {
118
- header[0x08 + index] = char.charCodeAt(0);
119
- });
120
- view.setUint32(0x0c, header.byteLength, true);
121
- for (let index = 0; index < resource.headerHash.length / 2; index++) {
122
- header[0x240 + index] = Number.parseInt(
123
- resource.headerHash.slice(index * 2, index * 2 + 2),
124
- 16
125
- );
126
- }
127
- const offset = Number(payload.file.offset);
128
- const chunkLength = Number(payload.chunk_len);
129
- return {
130
- message: {
131
- data: header.slice(offset, offset + chunkLength),
132
- },
133
- };
134
- }
135
- throw new Error(`Unexpected request: ${requestType}`);
136
- });
76
+ const resourceSource = {
77
+ archiveUrl: 'https://example.com/pro2-resource/pro2-resource.zip',
78
+ archiveSha256: 'a'.repeat(64),
79
+ archiveSize: 16_815_479,
137
80
  };
138
81
 
139
82
  describe('checkAllFirmwareRelease Protocol V2 support', () => {
@@ -210,7 +153,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
210
153
  test('detects same-version package hotfixes by P1 and P2 payload hash', () => {
211
154
  const packageSet: IFirmwareReleaseInfo = {
212
155
  ...release,
213
- resourceBundles: undefined,
214
156
  installOrder: ['applicationP1', 'applicationP2'],
215
157
  components: {
216
158
  applicationP1: {
@@ -261,7 +203,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
261
203
  const packageSet: IFirmwareReleaseInfo = {
262
204
  ...release,
263
205
  required: false,
264
- resourceBundles: undefined,
265
206
  installOrder: ['applicationP1'],
266
207
  components: {
267
208
  applicationP1: {
@@ -292,7 +233,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
292
233
  const packageSet: IFirmwareReleaseInfo = {
293
234
  ...release,
294
235
  required: false,
295
- resourceBundles: undefined,
296
236
  installOrder: ['applicationP1'],
297
237
  components: {
298
238
  applicationP1: {
@@ -323,7 +263,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
323
263
  test('updates both application packages when normal mode only reports P1', () => {
324
264
  const packageSet: IFirmwareReleaseInfo = {
325
265
  ...release,
326
- resourceBundles: undefined,
327
266
  installOrder: ['applicationP1', 'applicationP2'],
328
267
  components: {
329
268
  applicationP1: {
@@ -370,7 +309,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
370
309
  status: { mode: 'normal' },
371
310
  versions: currentVersions,
372
311
  });
373
- const typedCall = createFilesystemTypedCall(true);
312
+ const typedCall = jest.fn();
374
313
  method.device = {
375
314
  isProtocolV2: () => true,
376
315
  features: {
@@ -381,14 +320,24 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
381
320
  getCommands: () => ({ typedCall }),
382
321
  } as unknown as CheckAllFirmwareRelease['device'];
383
322
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
384
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
323
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
385
324
 
386
325
  await expect(method.run()).resolves.toMatchObject({
387
326
  protocol: 'V2',
388
327
  deviceType: 'pro2',
389
328
  status: 'required',
390
329
  resourceStatus: 'unknown',
330
+ resourceArchive: resourceSource,
391
331
  targetsToUpdate: ['boot', 'app_v1'],
332
+ firmwareUpdatePlan: {
333
+ executor: 'v4',
334
+ platform: 'web',
335
+ artifacts: [
336
+ { artifactId: 'component:boot', target: 'boot' },
337
+ { artifactId: 'component:app_v1', target: 'app_v1' },
338
+ ],
339
+ targetsToUpdate: ['boot', 'app_v1'],
340
+ },
392
341
  firmware: {
393
342
  status: 'required',
394
343
  },
@@ -400,11 +349,337 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
400
349
  expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
401
350
  });
402
351
 
352
+ test('preserves platform and forced firmware targets in the Protocol V2 prepared plan', async () => {
353
+ const method = new CheckAllFirmwareRelease({
354
+ id: 1,
355
+ payload: {
356
+ method: 'checkAllFirmwareRelease',
357
+ firmwareType: EFirmwareType.Universal,
358
+ platform: 'native',
359
+ forceUpdateTargets: ['firmware'],
360
+ },
361
+ });
362
+ method.init();
363
+ method.device = {
364
+ isProtocolV2: () => true,
365
+ features: {
366
+ deviceType: 'pro2',
367
+ serialNo: 'pro2-device-id',
368
+ firmwareVersion: '1.0.0',
369
+ },
370
+ getDeviceState: jest.fn().mockResolvedValue({
371
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
372
+ status: { mode: 'normal' },
373
+ versions: currentVersions,
374
+ }),
375
+ } as unknown as CheckAllFirmwareRelease['device'];
376
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
377
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
378
+
379
+ await expect(method.run()).resolves.toMatchObject({
380
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
381
+ firmwareUpdatePlan: {
382
+ executor: 'v4',
383
+ deviceIdentity: 'pro2-device-id',
384
+ platform: 'native',
385
+ artifacts: [
386
+ { artifactId: 'component:boot', target: 'boot' },
387
+ { artifactId: 'component:app_v1', target: 'app_v1' },
388
+ { artifactId: 'component:se02', target: 'se02' },
389
+ { artifactId: 'component:se03', target: 'se03' },
390
+ ],
391
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
392
+ },
393
+ });
394
+ });
395
+
396
+ test('includes exact Protocol V2 developer force targets in the prepared plan', async () => {
397
+ const currentRelease: IFirmwareReleaseInfo = {
398
+ ...release,
399
+ required: false,
400
+ version: [1, 0, 0],
401
+ installOrder: ['applicationP1', 'coprocessor'],
402
+ components: {
403
+ applicationP1: {
404
+ target: 'APPLICATION_P1',
405
+ url: 'https://example.com/application-p1.okpkg',
406
+ version: [1, 0, 0],
407
+ ...validComponentIntegrity,
408
+ },
409
+ coprocessor: {
410
+ target: 'COPROCESSOR',
411
+ url: 'https://example.com/coprocessor.okpkg',
412
+ version: [1, 0, 0],
413
+ ...validComponentIntegrity,
414
+ },
415
+ },
416
+ };
417
+ const method = new CheckAllFirmwareRelease({
418
+ id: 1,
419
+ payload: {
420
+ method: 'checkAllFirmwareRelease',
421
+ platform: 'desktop',
422
+ protocolV2ForceUpdateTargets: ['app_v1', 'coprocessor'],
423
+ },
424
+ });
425
+ method.init();
426
+ method.device = {
427
+ isProtocolV2: () => true,
428
+ features: {
429
+ deviceType: 'pro2',
430
+ serialNo: 'pro2-device-id',
431
+ firmwareVersion: '1.0.0',
432
+ },
433
+ getDeviceState: jest.fn().mockResolvedValue({
434
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
435
+ status: { mode: 'normal' },
436
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
437
+ }),
438
+ } as unknown as CheckAllFirmwareRelease['device'];
439
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
440
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
441
+
442
+ await expect(method.run()).resolves.toMatchObject({
443
+ targetsToUpdate: ['app_v1', 'coprocessor'],
444
+ firmwareUpdatePlan: {
445
+ executor: 'v4',
446
+ platform: 'desktop',
447
+ artifacts: [
448
+ { artifactId: 'component:app_v1', target: 'app_v1' },
449
+ { artifactId: 'component:coprocessor', target: 'coprocessor' },
450
+ ],
451
+ targetsToUpdate: ['app_v1', 'coprocessor'],
452
+ },
453
+ });
454
+ });
455
+
456
+ test('builds a Desktop V4 prepared update without device identity', async () => {
457
+ const currentRelease: IFirmwareReleaseInfo = {
458
+ ...release,
459
+ required: false,
460
+ version: [1, 0, 0],
461
+ installOrder: ['applicationP1'],
462
+ components: {
463
+ applicationP1: {
464
+ target: 'APPLICATION_P1',
465
+ url: 'https://example.com/application-p1.okpkg',
466
+ version: [1, 0, 0],
467
+ ...validComponentIntegrity,
468
+ },
469
+ },
470
+ };
471
+ const method = new CheckAllFirmwareRelease({
472
+ id: 1,
473
+ payload: {
474
+ method: 'checkAllFirmwareRelease',
475
+ platform: 'desktop',
476
+ protocolV2ForceUpdateTargets: ['app_v1'],
477
+ },
478
+ });
479
+ method.init();
480
+ method.device = {
481
+ isProtocolV2: () => true,
482
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
483
+ getDeviceState: jest.fn().mockResolvedValue({
484
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
485
+ status: { mode: 'normal' },
486
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
487
+ }),
488
+ } as unknown as CheckAllFirmwareRelease['device'];
489
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
490
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
491
+
492
+ await expect(method.run()).resolves.toMatchObject({
493
+ firmwareUpdatePlan: {
494
+ executor: 'v4',
495
+ deviceIdentity: 'unavailable',
496
+ },
497
+ });
498
+ });
499
+
500
+ test('rejects Neo-only unsupported exact secure-element targets', async () => {
501
+ const neoRelease: IFirmwareReleaseInfo = {
502
+ ...release,
503
+ required: false,
504
+ installOrder: ['se03'],
505
+ components: {
506
+ se03: {
507
+ target: 'SE03',
508
+ url: 'https://example.com/se03.okpkg',
509
+ version: [1, 0, 0],
510
+ },
511
+ },
512
+ };
513
+ const method = new CheckAllFirmwareRelease({
514
+ id: 1,
515
+ payload: {
516
+ method: 'checkAllFirmwareRelease',
517
+ protocolV2ForceUpdateTargets: ['se03'],
518
+ },
519
+ });
520
+ method.init();
521
+ method.device = {
522
+ isProtocolV2: () => true,
523
+ features: { deviceType: 'neo', firmwareVersion: '1.0.0' },
524
+ getDeviceState: jest.fn().mockResolvedValue({
525
+ identity: { deviceType: 'neo', firmwareType: EFirmwareType.Universal },
526
+ status: { mode: 'normal' },
527
+ versions: currentVersions,
528
+ }),
529
+ } as unknown as CheckAllFirmwareRelease['device'];
530
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(neoRelease);
531
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
532
+
533
+ await expect(method.run()).rejects.toMatchObject({
534
+ params: { firmwareUpdateCode: 'FirmwarePlanInvalid' },
535
+ });
536
+ });
537
+
538
+ test('builds a V4 plan for Neo and excludes unsupported secure elements when forced', async () => {
539
+ const neoRelease: IFirmwareReleaseInfo = {
540
+ ...release,
541
+ installOrder: ['se01', 'se02', 'se03', 'se04'],
542
+ components: {
543
+ se01: {
544
+ target: 'SE01',
545
+ url: 'https://example.com/se01.bin',
546
+ version: [1, 0, 0],
547
+ ...validComponentIntegrity,
548
+ },
549
+ se02: {
550
+ target: 'SE02',
551
+ url: 'https://example.com/se02.bin',
552
+ version: [2, 0, 0],
553
+ ...validComponentIntegrity,
554
+ },
555
+ se03: { target: 'SE03', url: 'https://example.com/se03.bin', version: [1, 0, 0] },
556
+ se04: { target: 'SE04', url: 'https://example.com/se04.bin', version: [1, 0, 0] },
557
+ },
558
+ };
559
+ const method = new CheckAllFirmwareRelease({
560
+ id: 1,
561
+ payload: {
562
+ method: 'checkAllFirmwareRelease',
563
+ platform: 'native',
564
+ forceUpdateTargets: ['firmware'],
565
+ },
566
+ });
567
+ method.init();
568
+ method.device = {
569
+ isProtocolV2: () => true,
570
+ features: {
571
+ deviceType: 'neo',
572
+ serialNo: 'neo-device-id',
573
+ firmwareVersion: '1.0.0',
574
+ },
575
+ getDeviceState: jest.fn().mockResolvedValue({
576
+ identity: { deviceType: 'neo', firmwareType: EFirmwareType.Universal },
577
+ status: { mode: 'normal' },
578
+ versions: currentVersions,
579
+ }),
580
+ } as unknown as CheckAllFirmwareRelease['device'];
581
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(neoRelease);
582
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
583
+
584
+ await expect(method.run()).resolves.toMatchObject({
585
+ targetsToUpdate: ['se01', 'se02'],
586
+ firmwareUpdatePlan: {
587
+ executor: 'v4',
588
+ deviceModel: 'neo',
589
+ platform: 'native',
590
+ targetsToUpdate: ['se01', 'se02'],
591
+ },
592
+ });
593
+ });
594
+
595
+ test('rejects a forced Protocol V2 resource target without a configured archive', async () => {
596
+ const method = new CheckAllFirmwareRelease({
597
+ id: 1,
598
+ payload: {
599
+ method: 'checkAllFirmwareRelease',
600
+ forceUpdateTargets: ['resource'],
601
+ },
602
+ });
603
+ method.init();
604
+ method.device = {
605
+ isProtocolV2: () => true,
606
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
607
+ getDeviceState: jest.fn().mockResolvedValue({
608
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
609
+ status: { mode: 'normal' },
610
+ versions: currentVersions,
611
+ }),
612
+ } as unknown as CheckAllFirmwareRelease['device'];
613
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
614
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(undefined);
615
+
616
+ await expect(method.run()).rejects.toMatchObject({
617
+ params: { firmwareUpdateCode: 'FirmwarePlanInvalid' },
618
+ });
619
+ });
620
+
621
+ test('adds a Protocol V2 resource target only when explicitly forced', async () => {
622
+ const currentRelease: IFirmwareReleaseInfo = {
623
+ ...release,
624
+ required: false,
625
+ installOrder: ['applicationP1'],
626
+ components: {
627
+ applicationP1: {
628
+ target: 'APPLICATION_P1',
629
+ url: 'https://example.com/application-p1.okpkg',
630
+ version: [1, 0, 0],
631
+ },
632
+ },
633
+ };
634
+ const method = new CheckAllFirmwareRelease({
635
+ id: 1,
636
+ payload: {
637
+ method: 'checkAllFirmwareRelease',
638
+ platform: 'desktop',
639
+ protocolV2ForceUpdateTargets: ['resource'],
640
+ },
641
+ });
642
+ method.init();
643
+ method.device = {
644
+ isProtocolV2: () => true,
645
+ features: {
646
+ deviceType: 'pro2',
647
+ serialNo: 'pro2-device-id',
648
+ firmwareVersion: '1.0.0',
649
+ },
650
+ getDeviceState: jest.fn().mockResolvedValue({
651
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
652
+ status: { mode: 'normal' },
653
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
654
+ }),
655
+ } as unknown as CheckAllFirmwareRelease['device'];
656
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
657
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
658
+
659
+ await expect(method.run()).resolves.toMatchObject({
660
+ status: 'valid',
661
+ hasUpgrade: true,
662
+ targetsToUpdate: ['resource'],
663
+ firmwareUpdatePlan: {
664
+ executor: 'v4',
665
+ platform: 'desktop',
666
+ artifacts: [
667
+ {
668
+ artifactId: 'resource:archive',
669
+ target: 'resource',
670
+ role: 'resourceBundle',
671
+ container: 'zip',
672
+ },
673
+ ],
674
+ targetsToUpdate: ['resource'],
675
+ },
676
+ });
677
+ });
678
+
403
679
  test('requests and compares firmware hashes only when explicitly enabled', async () => {
404
680
  const packageSet: IFirmwareReleaseInfo = {
405
681
  ...release,
406
682
  required: false,
407
- resourceBundles: undefined,
408
683
  installOrder: ['applicationP1'],
409
684
  components: {
410
685
  applicationP1: {
@@ -437,7 +712,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
437
712
  getCommands: () => ({ typedCall: jest.fn() }),
438
713
  } as unknown as CheckAllFirmwareRelease['device'];
439
714
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(packageSet);
440
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(undefined);
715
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(undefined);
441
716
 
442
717
  await expect(method.run()).resolves.toMatchObject({
443
718
  status: 'outdated',
@@ -449,7 +724,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
449
724
  });
450
725
 
451
726
  test.each(['bootloader', 'romloader'] as const)(
452
- 'uses filesystem inventory in %s mode instead of forcing all resources',
727
+ 'does not read vol0 resource inventory in %s mode',
453
728
  async mode => {
454
729
  const method = new CheckAllFirmwareRelease({
455
730
  id: 1,
@@ -459,7 +734,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
459
734
  },
460
735
  });
461
736
  method.init();
462
- const typedCall = createFilesystemTypedCall();
737
+ const typedCall = jest.fn();
463
738
  method.device = {
464
739
  isProtocolV2: () => true,
465
740
  features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
@@ -471,18 +746,61 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
471
746
  getCommands: () => ({ typedCall }),
472
747
  } as unknown as CheckAllFirmwareRelease['device'];
473
748
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
474
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
749
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
475
750
 
476
751
  await expect(method.run()).resolves.toMatchObject({
477
752
  protocol: 'V2',
478
- resourceStatus: 'valid',
753
+ resourceStatus: 'unknown',
754
+ resourceArchive: resourceSource,
479
755
  targetsToUpdate: ['boot', 'app_v1'],
480
756
  });
481
- expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
482
- expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
757
+ expect(typedCall).not.toHaveBeenCalled();
483
758
  }
484
759
  );
485
760
 
761
+ test('exposes resource archive availability without reporting an unconfirmed update', async () => {
762
+ const currentRelease: IFirmwareReleaseInfo = {
763
+ ...release,
764
+ required: false,
765
+ installOrder: ['applicationP1'],
766
+ components: {
767
+ applicationP1: {
768
+ target: 'APPLICATION_P1',
769
+ url: 'https://example.com/application-p1.okpkg',
770
+ version: [1, 0, 0],
771
+ },
772
+ },
773
+ };
774
+ const method = new CheckAllFirmwareRelease({
775
+ id: 1,
776
+ payload: {
777
+ method: 'checkAllFirmwareRelease',
778
+ firmwareType: EFirmwareType.Universal,
779
+ },
780
+ });
781
+ method.init();
782
+ method.device = {
783
+ isProtocolV2: () => true,
784
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
785
+ getDeviceState: jest.fn().mockResolvedValue({
786
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
787
+ status: { mode: 'normal' },
788
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
789
+ }),
790
+ } as unknown as CheckAllFirmwareRelease['device'];
791
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
792
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
793
+
794
+ await expect(method.run()).resolves.toMatchObject({
795
+ status: 'valid',
796
+ hasUpgrade: false,
797
+ resourceStatus: 'unknown',
798
+ resourceArchive: resourceSource,
799
+ targetsToUpdate: [],
800
+ firmwareUpdatePlan: undefined,
801
+ });
802
+ });
803
+
486
804
  test('continues forwarding the existing public method', async () => {
487
805
  const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
488
806
  const api = createCoreApi(call as CoreApi['call']) as CoreApi;
@@ -490,6 +808,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
490
808
  await api.checkAllFirmwareRelease('pro2-connect-id', {
491
809
  checkFirmwareHash: true,
492
810
  firmwareType: EFirmwareType.Universal,
811
+ protocolV2ForceUpdateTargets: ['app_v1'],
493
812
  retryCount: 0,
494
813
  });
495
814
 
@@ -497,6 +816,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
497
816
  connectId: 'pro2-connect-id',
498
817
  checkFirmwareHash: true,
499
818
  firmwareType: EFirmwareType.Universal,
819
+ protocolV2ForceUpdateTargets: ['app_v1'],
500
820
  retryCount: 0,
501
821
  method: 'checkAllFirmwareRelease',
502
822
  });