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

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 (103) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +411 -86
  2. package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
  3. package/__tests__/firmware-memory-host.test.ts +126 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -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-bootloader-poll.test.ts +65 -2
  9. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
  10. package/__tests__/firmware-update/firmware-update-plan.test.ts +293 -71
  11. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
  12. package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
  13. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
  14. package/__tests__/protocol-v2-firmware-targets.test.ts +16 -0
  15. package/__tests__/protocol-v2-resources.test.ts +148 -232
  16. package/__tests__/protocol-v2.test.ts +1326 -510
  17. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
  18. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  19. package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
  20. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  21. package/dist/api/CheckBootloaderRelease.d.ts +2 -1
  22. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  23. package/dist/api/CheckFirmwareRelease.d.ts +3 -2
  24. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdateV2.d.ts +1 -0
  26. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  27. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV4.d.ts +11 -9
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  32. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  33. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  34. package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
  35. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
  36. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
  37. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
  38. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  39. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
  40. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  41. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +7 -1
  42. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  43. package/dist/api/firmware/getBinary.d.ts +0 -1
  44. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  45. package/dist/api/firmware/protocolV2Release.d.ts +33 -0
  46. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
  47. package/dist/data-manager/DataManager.d.ts +4 -3
  48. package/dist/data-manager/DataManager.d.ts.map +1 -1
  49. package/dist/index.d.ts +108 -114
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1825 -1006
  52. package/dist/protocols/protocol-v2/resources.d.ts +10 -28
  53. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  54. package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
  55. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  56. package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
  57. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  58. package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
  59. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  60. package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
  61. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  62. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  63. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  64. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  65. package/dist/types/api/export.d.ts +1 -1
  66. package/dist/types/api/export.d.ts.map +1 -1
  67. package/dist/types/api/firmwareUpdate.d.ts +4 -12
  68. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  69. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  70. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  71. package/dist/types/settings.d.ts +35 -36
  72. package/dist/types/settings.d.ts.map +1 -1
  73. package/package.json +4 -4
  74. package/src/api/CheckAllFirmwareRelease.ts +113 -80
  75. package/src/api/CheckBLEFirmwareRelease.ts +37 -5
  76. package/src/api/CheckBootloaderRelease.ts +35 -3
  77. package/src/api/CheckFirmwareRelease.ts +35 -7
  78. package/src/api/FirmwareUpdateV2.ts +231 -138
  79. package/src/api/FirmwareUpdateV3.ts +101 -56
  80. package/src/api/FirmwareUpdateV4.ts +936 -408
  81. package/src/api/UploadPortfolio.ts +18 -0
  82. package/src/api/device/DeviceUpdateBootloader.ts +37 -11
  83. package/src/api/firmware/FirmwareHostBinding.ts +26 -3
  84. package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
  85. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
  86. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +122 -73
  87. package/src/api/firmware/FirmwareUpdatePlan.ts +295 -122
  88. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +49 -12
  89. package/src/api/firmware/protocolV2Release.ts +168 -0
  90. package/src/data-manager/DataManager.ts +35 -19
  91. package/src/index.ts +10 -0
  92. package/src/protocols/protocol-v2/resources.ts +157 -341
  93. package/src/types/api/checkAllFirmwareRelease.ts +27 -12
  94. package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
  95. package/src/types/api/checkBootloaderRelease.ts +2 -6
  96. package/src/types/api/checkFirmwareRelease.ts +2 -13
  97. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  98. package/src/types/api/deviceUpdateBootloader.ts +1 -0
  99. package/src/types/api/export.ts +6 -1
  100. package/src/types/api/firmwareUpdate.ts +12 -17
  101. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  102. package/src/types/settings.ts +35 -61
  103. 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,25 @@ 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',
391
- targetsToUpdate: ['boot', 'app_v1'],
330
+ resourceArchive: resourceSource,
331
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
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
+ { artifactId: 'resource:archive', target: 'resource' },
339
+ ],
340
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
341
+ },
392
342
  firmware: {
393
343
  status: 'required',
394
344
  },
@@ -400,11 +350,341 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
400
350
  expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
401
351
  });
402
352
 
353
+ test('preserves platform and forced firmware targets in the Protocol V2 prepared plan', async () => {
354
+ const method = new CheckAllFirmwareRelease({
355
+ id: 1,
356
+ payload: {
357
+ method: 'checkAllFirmwareRelease',
358
+ firmwareType: EFirmwareType.Universal,
359
+ platform: 'native',
360
+ forceUpdateTargets: ['firmware'],
361
+ },
362
+ });
363
+ method.init();
364
+ method.device = {
365
+ isProtocolV2: () => true,
366
+ features: {
367
+ deviceType: 'pro2',
368
+ serialNo: 'pro2-device-id',
369
+ firmwareVersion: '1.0.0',
370
+ },
371
+ getDeviceState: jest.fn().mockResolvedValue({
372
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
373
+ status: { mode: 'normal' },
374
+ versions: currentVersions,
375
+ }),
376
+ } as unknown as CheckAllFirmwareRelease['device'];
377
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
378
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
379
+
380
+ await expect(method.run()).resolves.toMatchObject({
381
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
382
+ firmwareUpdatePlan: {
383
+ executor: 'v4',
384
+ deviceIdentity: 'pro2-device-id',
385
+ platform: 'native',
386
+ artifacts: [
387
+ { artifactId: 'component:boot', target: 'boot' },
388
+ { artifactId: 'component:app_v1', target: 'app_v1' },
389
+ { artifactId: 'component:se02', target: 'se02' },
390
+ { artifactId: 'component:se03', target: 'se03' },
391
+ { artifactId: 'resource:archive', target: 'resource' },
392
+ ],
393
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
394
+ },
395
+ });
396
+ });
397
+
398
+ test('includes exact Protocol V2 developer force targets in the prepared plan', async () => {
399
+ const currentRelease: IFirmwareReleaseInfo = {
400
+ ...release,
401
+ required: false,
402
+ version: [1, 0, 0],
403
+ installOrder: ['applicationP1', 'coprocessor'],
404
+ components: {
405
+ applicationP1: {
406
+ target: 'APPLICATION_P1',
407
+ url: 'https://example.com/application-p1.okpkg',
408
+ version: [1, 0, 0],
409
+ ...validComponentIntegrity,
410
+ },
411
+ coprocessor: {
412
+ target: 'COPROCESSOR',
413
+ url: 'https://example.com/coprocessor.okpkg',
414
+ version: [1, 0, 0],
415
+ ...validComponentIntegrity,
416
+ },
417
+ },
418
+ };
419
+ const method = new CheckAllFirmwareRelease({
420
+ id: 1,
421
+ payload: {
422
+ method: 'checkAllFirmwareRelease',
423
+ platform: 'desktop',
424
+ protocolV2ForceUpdateTargets: ['app_v1', 'coprocessor'],
425
+ },
426
+ });
427
+ method.init();
428
+ method.device = {
429
+ isProtocolV2: () => true,
430
+ features: {
431
+ deviceType: 'pro2',
432
+ serialNo: 'pro2-device-id',
433
+ firmwareVersion: '1.0.0',
434
+ },
435
+ getDeviceState: jest.fn().mockResolvedValue({
436
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
437
+ status: { mode: 'normal' },
438
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
439
+ }),
440
+ } as unknown as CheckAllFirmwareRelease['device'];
441
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
442
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
443
+
444
+ await expect(method.run()).resolves.toMatchObject({
445
+ targetsToUpdate: ['app_v1', 'coprocessor'],
446
+ firmwareUpdatePlan: {
447
+ executor: 'v4',
448
+ platform: 'desktop',
449
+ artifacts: [
450
+ { artifactId: 'component:app_v1', target: 'app_v1' },
451
+ { artifactId: 'component:coprocessor', target: 'coprocessor' },
452
+ ],
453
+ targetsToUpdate: ['app_v1', 'coprocessor'],
454
+ },
455
+ });
456
+ });
457
+
458
+ test('builds a Desktop V4 prepared update without device identity', async () => {
459
+ const currentRelease: IFirmwareReleaseInfo = {
460
+ ...release,
461
+ required: false,
462
+ version: [1, 0, 0],
463
+ installOrder: ['applicationP1'],
464
+ components: {
465
+ applicationP1: {
466
+ target: 'APPLICATION_P1',
467
+ url: 'https://example.com/application-p1.okpkg',
468
+ version: [1, 0, 0],
469
+ ...validComponentIntegrity,
470
+ },
471
+ },
472
+ };
473
+ const method = new CheckAllFirmwareRelease({
474
+ id: 1,
475
+ payload: {
476
+ method: 'checkAllFirmwareRelease',
477
+ platform: 'desktop',
478
+ protocolV2ForceUpdateTargets: ['app_v1'],
479
+ },
480
+ });
481
+ method.init();
482
+ method.device = {
483
+ isProtocolV2: () => true,
484
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
485
+ getDeviceState: jest.fn().mockResolvedValue({
486
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
487
+ status: { mode: 'normal' },
488
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
489
+ }),
490
+ } as unknown as CheckAllFirmwareRelease['device'];
491
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
492
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
493
+
494
+ await expect(method.run()).resolves.toMatchObject({
495
+ firmwareUpdatePlan: {
496
+ executor: 'v4',
497
+ deviceIdentity: 'unavailable',
498
+ },
499
+ });
500
+ });
501
+
502
+ test('rejects Neo-only unsupported exact secure-element targets', async () => {
503
+ const neoRelease: IFirmwareReleaseInfo = {
504
+ ...release,
505
+ required: false,
506
+ installOrder: ['se03'],
507
+ components: {
508
+ se03: {
509
+ target: 'SE03',
510
+ url: 'https://example.com/se03.okpkg',
511
+ version: [1, 0, 0],
512
+ },
513
+ },
514
+ };
515
+ const method = new CheckAllFirmwareRelease({
516
+ id: 1,
517
+ payload: {
518
+ method: 'checkAllFirmwareRelease',
519
+ protocolV2ForceUpdateTargets: ['se03'],
520
+ },
521
+ });
522
+ method.init();
523
+ method.device = {
524
+ isProtocolV2: () => true,
525
+ features: { deviceType: 'neo', firmwareVersion: '1.0.0' },
526
+ getDeviceState: jest.fn().mockResolvedValue({
527
+ identity: { deviceType: 'neo', firmwareType: EFirmwareType.Universal },
528
+ status: { mode: 'normal' },
529
+ versions: currentVersions,
530
+ }),
531
+ } as unknown as CheckAllFirmwareRelease['device'];
532
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(neoRelease);
533
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
534
+
535
+ await expect(method.run()).rejects.toMatchObject({
536
+ params: { firmwareUpdateCode: 'FirmwarePlanInvalid' },
537
+ });
538
+ });
539
+
540
+ test('builds a V4 plan for Neo and excludes unsupported secure elements when forced', async () => {
541
+ const neoRelease: IFirmwareReleaseInfo = {
542
+ ...release,
543
+ installOrder: ['se01', 'se02', 'se03', 'se04'],
544
+ components: {
545
+ se01: {
546
+ target: 'SE01',
547
+ url: 'https://example.com/se01.bin',
548
+ version: [1, 0, 0],
549
+ ...validComponentIntegrity,
550
+ },
551
+ se02: {
552
+ target: 'SE02',
553
+ url: 'https://example.com/se02.bin',
554
+ version: [2, 0, 0],
555
+ ...validComponentIntegrity,
556
+ },
557
+ se03: { target: 'SE03', url: 'https://example.com/se03.bin', version: [1, 0, 0] },
558
+ se04: { target: 'SE04', url: 'https://example.com/se04.bin', version: [1, 0, 0] },
559
+ },
560
+ };
561
+ const method = new CheckAllFirmwareRelease({
562
+ id: 1,
563
+ payload: {
564
+ method: 'checkAllFirmwareRelease',
565
+ platform: 'native',
566
+ forceUpdateTargets: ['firmware'],
567
+ },
568
+ });
569
+ method.init();
570
+ method.device = {
571
+ isProtocolV2: () => true,
572
+ features: {
573
+ deviceType: 'neo',
574
+ serialNo: 'neo-device-id',
575
+ firmwareVersion: '1.0.0',
576
+ },
577
+ getDeviceState: jest.fn().mockResolvedValue({
578
+ identity: { deviceType: 'neo', firmwareType: EFirmwareType.Universal },
579
+ status: { mode: 'normal' },
580
+ versions: currentVersions,
581
+ }),
582
+ } as unknown as CheckAllFirmwareRelease['device'];
583
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(neoRelease);
584
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
585
+
586
+ await expect(method.run()).resolves.toMatchObject({
587
+ targetsToUpdate: ['se01', 'se02', 'resource'],
588
+ firmwareUpdatePlan: {
589
+ executor: 'v4',
590
+ deviceModel: 'neo',
591
+ platform: 'native',
592
+ targetsToUpdate: ['se01', 'se02', 'resource'],
593
+ },
594
+ });
595
+ });
596
+
597
+ test('rejects a forced Protocol V2 resource target without a configured archive', async () => {
598
+ const method = new CheckAllFirmwareRelease({
599
+ id: 1,
600
+ payload: {
601
+ method: 'checkAllFirmwareRelease',
602
+ forceUpdateTargets: ['resource'],
603
+ },
604
+ });
605
+ method.init();
606
+ method.device = {
607
+ isProtocolV2: () => true,
608
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
609
+ getDeviceState: jest.fn().mockResolvedValue({
610
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
611
+ status: { mode: 'normal' },
612
+ versions: currentVersions,
613
+ }),
614
+ } as unknown as CheckAllFirmwareRelease['device'];
615
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
616
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(undefined);
617
+
618
+ await expect(method.run()).rejects.toMatchObject({
619
+ params: { firmwareUpdateCode: 'FirmwarePlanInvalid' },
620
+ });
621
+ });
622
+
623
+ test.each(['resource', 'boot_resources'] as const)(
624
+ 'allows an explicitly forced Protocol V2 resource-only target through %s',
625
+ async forceTarget => {
626
+ const currentRelease: IFirmwareReleaseInfo = {
627
+ ...release,
628
+ required: false,
629
+ installOrder: ['applicationP1'],
630
+ components: {
631
+ applicationP1: {
632
+ target: 'APPLICATION_P1',
633
+ url: 'https://example.com/application-p1.okpkg',
634
+ version: [1, 0, 0],
635
+ },
636
+ },
637
+ };
638
+ const method = new CheckAllFirmwareRelease({
639
+ id: 1,
640
+ payload: {
641
+ method: 'checkAllFirmwareRelease',
642
+ platform: 'desktop',
643
+ protocolV2ForceUpdateTargets: [forceTarget],
644
+ },
645
+ });
646
+ method.init();
647
+ method.device = {
648
+ isProtocolV2: () => true,
649
+ features: {
650
+ deviceType: 'pro2',
651
+ serialNo: 'pro2-device-id',
652
+ firmwareVersion: '1.0.0',
653
+ },
654
+ getDeviceState: jest.fn().mockResolvedValue({
655
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
656
+ status: { mode: 'normal' },
657
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
658
+ }),
659
+ } as unknown as CheckAllFirmwareRelease['device'];
660
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
661
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
662
+
663
+ await expect(method.run()).resolves.toMatchObject({
664
+ status: 'valid',
665
+ hasUpgrade: true,
666
+ targetsToUpdate: ['resource'],
667
+ firmwareUpdatePlan: {
668
+ executor: 'v4',
669
+ platform: 'desktop',
670
+ artifacts: [
671
+ {
672
+ artifactId: 'resource:archive',
673
+ target: 'resource',
674
+ role: 'resourceBundle',
675
+ container: 'zip',
676
+ },
677
+ ],
678
+ targetsToUpdate: ['resource'],
679
+ },
680
+ });
681
+ }
682
+ );
683
+
403
684
  test('requests and compares firmware hashes only when explicitly enabled', async () => {
404
685
  const packageSet: IFirmwareReleaseInfo = {
405
686
  ...release,
406
687
  required: false,
407
- resourceBundles: undefined,
408
688
  installOrder: ['applicationP1'],
409
689
  components: {
410
690
  applicationP1: {
@@ -437,7 +717,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
437
717
  getCommands: () => ({ typedCall: jest.fn() }),
438
718
  } as unknown as CheckAllFirmwareRelease['device'];
439
719
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(packageSet);
440
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(undefined);
720
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(undefined);
441
721
 
442
722
  await expect(method.run()).resolves.toMatchObject({
443
723
  status: 'outdated',
@@ -449,7 +729,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
449
729
  });
450
730
 
451
731
  test.each(['bootloader', 'romloader'] as const)(
452
- 'uses filesystem inventory in %s mode instead of forcing all resources',
732
+ 'does not read vol0 resource inventory in %s mode',
453
733
  async mode => {
454
734
  const method = new CheckAllFirmwareRelease({
455
735
  id: 1,
@@ -459,7 +739,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
459
739
  },
460
740
  });
461
741
  method.init();
462
- const typedCall = createFilesystemTypedCall();
742
+ const typedCall = jest.fn();
463
743
  method.device = {
464
744
  isProtocolV2: () => true,
465
745
  features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
@@ -471,18 +751,61 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
471
751
  getCommands: () => ({ typedCall }),
472
752
  } as unknown as CheckAllFirmwareRelease['device'];
473
753
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
474
- jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
754
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
475
755
 
476
756
  await expect(method.run()).resolves.toMatchObject({
477
757
  protocol: 'V2',
478
- resourceStatus: 'valid',
479
- targetsToUpdate: ['boot', 'app_v1'],
758
+ resourceStatus: 'unknown',
759
+ resourceArchive: resourceSource,
760
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
480
761
  });
481
- expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
482
- expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
762
+ expect(typedCall).not.toHaveBeenCalled();
483
763
  }
484
764
  );
485
765
 
766
+ test('exposes resource archive availability without reporting an unconfirmed update', async () => {
767
+ const currentRelease: IFirmwareReleaseInfo = {
768
+ ...release,
769
+ required: false,
770
+ installOrder: ['applicationP1'],
771
+ components: {
772
+ applicationP1: {
773
+ target: 'APPLICATION_P1',
774
+ url: 'https://example.com/application-p1.okpkg',
775
+ version: [1, 0, 0],
776
+ },
777
+ },
778
+ };
779
+ const method = new CheckAllFirmwareRelease({
780
+ id: 1,
781
+ payload: {
782
+ method: 'checkAllFirmwareRelease',
783
+ firmwareType: EFirmwareType.Universal,
784
+ },
785
+ });
786
+ method.init();
787
+ method.device = {
788
+ isProtocolV2: () => true,
789
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
790
+ getDeviceState: jest.fn().mockResolvedValue({
791
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
792
+ status: { mode: 'normal' },
793
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
794
+ }),
795
+ } as unknown as CheckAllFirmwareRelease['device'];
796
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
797
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
798
+
799
+ await expect(method.run()).resolves.toMatchObject({
800
+ status: 'valid',
801
+ hasUpgrade: false,
802
+ resourceStatus: 'unknown',
803
+ resourceArchive: resourceSource,
804
+ targetsToUpdate: [],
805
+ firmwareUpdatePlan: undefined,
806
+ });
807
+ });
808
+
486
809
  test('continues forwarding the existing public method', async () => {
487
810
  const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
488
811
  const api = createCoreApi(call as CoreApi['call']) as CoreApi;
@@ -490,6 +813,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
490
813
  await api.checkAllFirmwareRelease('pro2-connect-id', {
491
814
  checkFirmwareHash: true,
492
815
  firmwareType: EFirmwareType.Universal,
816
+ protocolV2ForceUpdateTargets: ['app_v1'],
493
817
  retryCount: 0,
494
818
  });
495
819
 
@@ -497,6 +821,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
497
821
  connectId: 'pro2-connect-id',
498
822
  checkFirmwareHash: true,
499
823
  firmwareType: EFirmwareType.Universal,
824
+ protocolV2ForceUpdateTargets: ['app_v1'],
500
825
  retryCount: 0,
501
826
  method: 'checkAllFirmwareRelease',
502
827
  });