@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.
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +84 -404
- package/__tests__/firmware-update/device-update-bootloader.test.ts +2 -6
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -9
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +112 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -131
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +1 -53
- package/__tests__/protocol-v2-resources.test.ts +232 -148
- package/__tests__/protocol-v2.test.ts +517 -1106
- package/dist/api/CheckAllFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +1 -2
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +8 -6
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +1 -48
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +1 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +111 -103
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +849 -1313
- package/dist/protocols/protocol-v2/resources.d.ts +28 -10
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +8 -20
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +13 -2
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +6 -2
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +13 -2
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +11 -3
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/settings.d.ts +36 -35
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +80 -108
- package/src/api/CheckBLEFirmwareRelease.ts +5 -37
- package/src/api/CheckBootloaderRelease.ts +3 -35
- package/src/api/CheckFirmwareRelease.ts +7 -35
- package/src/api/FirmwareUpdateV2.ts +3 -15
- package/src/api/FirmwareUpdateV3.ts +7 -29
- package/src/api/FirmwareUpdateV4.ts +407 -824
- package/src/api/UploadPortfolio.ts +0 -18
- package/src/api/device/DeviceUpdateBootloader.ts +5 -26
- package/src/api/firmware/FirmwareHostBinding.ts +3 -16
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +2 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +88 -226
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +3 -10
- package/src/data-manager/DataManager.ts +19 -35
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/resources.ts +341 -157
- package/src/types/api/checkAllFirmwareRelease.ts +12 -27
- package/src/types/api/checkBLEFirmwareRelease.ts +13 -4
- package/src/types/api/checkBootloaderRelease.ts +6 -2
- package/src/types/api/checkFirmwareRelease.ts +13 -2
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/export.ts +1 -6
- package/src/types/api/firmwareUpdate.ts +16 -10
- package/src/types/settings.ts +61 -35
- package/src/utils/deviceFeaturesUtils.ts +2 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -199
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +0 -469
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/dist/api/firmware/protocolV2Release.d.ts +0 -33
- package/dist/api/firmware/protocolV2Release.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
- package/src/api/firmware/protocolV2Release.ts +0 -168
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
|
-
import JSZip from 'jszip';
|
|
5
2
|
import {
|
|
6
3
|
DeviceRebootType,
|
|
7
4
|
DeviceSessionPinType,
|
|
8
5
|
DeviceSettingsPage,
|
|
9
6
|
DeviceType,
|
|
10
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
11
9
|
|
|
12
10
|
import * as firmwareBinaryApi from '../src/api/firmware/getBinary';
|
|
13
11
|
import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
|
|
@@ -81,6 +79,7 @@ import {
|
|
|
81
79
|
requestProtocolV2ProtocolInfo,
|
|
82
80
|
supportsProtocolV2Message,
|
|
83
81
|
} from '../src/protocols/protocol-v2/features';
|
|
82
|
+
import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
|
|
84
83
|
import {
|
|
85
84
|
getProtocolV2WalletSession,
|
|
86
85
|
refreshProtocolV2DeviceStatus,
|
|
@@ -103,12 +102,6 @@ import {
|
|
|
103
102
|
} from '../src/utils';
|
|
104
103
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
105
104
|
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
106
|
-
import {
|
|
107
|
-
registerFirmwareUpdateHostBinding,
|
|
108
|
-
unregisterFirmwareUpdateHostBinding,
|
|
109
|
-
} from '../src/api/firmware/FirmwareHostBinding';
|
|
110
|
-
import { prepareFirmwareUpdatePlan } from '../src/api/firmware/FirmwareUpdatePreparedPlan';
|
|
111
|
-
import { digestFirmwareUpdateContract } from '../src/api/firmware/FirmwareUpdatePlan';
|
|
112
105
|
import {
|
|
113
106
|
getPassphraseState,
|
|
114
107
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -123,26 +116,6 @@ jest.mock('../src/data/config', () => ({
|
|
|
123
116
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
124
117
|
}));
|
|
125
118
|
|
|
126
|
-
const createProtocolV2OkppBinary = ({
|
|
127
|
-
version = [1, 2, 3],
|
|
128
|
-
payloadHashByte = 0x11,
|
|
129
|
-
headerHashByte = 0x22,
|
|
130
|
-
}: {
|
|
131
|
-
version?: [number, number, number];
|
|
132
|
-
payloadHashByte?: number;
|
|
133
|
-
headerHashByte?: number;
|
|
134
|
-
} = {}) => {
|
|
135
|
-
const bytes = new Uint8Array(0x52a0);
|
|
136
|
-
bytes.set([0x4f, 0x4b, 0x50, 0x50], 0);
|
|
137
|
-
bytes.set([0x52, 0x45, 0x53, 0x43], 0x08);
|
|
138
|
-
const view = new DataView(bytes.buffer);
|
|
139
|
-
view.setUint32(0x0c, 0x52a0, true);
|
|
140
|
-
view.setUint32(0x10, version[0] * 0x10000 + version[1] * 0x100 + version[2], true);
|
|
141
|
-
bytes.fill(payloadHashByte, 0x200, 0x240);
|
|
142
|
-
bytes.fill(headerHashByte, 0x240, 0x280);
|
|
143
|
-
return bytes.buffer;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
119
|
const createWalletSessionTypedCall = (
|
|
147
120
|
implementation = jest.fn(),
|
|
148
121
|
status: Partial<DeviceStatus> = { passphrase_enabled: true }
|
|
@@ -254,17 +227,6 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
254
227
|
});
|
|
255
228
|
|
|
256
229
|
describe('UploadPortfolio', () => {
|
|
257
|
-
const portfolioProtocolInfo = {
|
|
258
|
-
version: 2,
|
|
259
|
-
supported_messages: [60805, 61400],
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const stubPortfolioDevice = <T extends Record<string, any>>(device: T) =>
|
|
263
|
-
stubDevice({
|
|
264
|
-
...device,
|
|
265
|
-
ensureProtocolV2RuntimeContext: jest.fn().mockResolvedValue(portfolioProtocolInfo),
|
|
266
|
-
});
|
|
267
|
-
|
|
268
230
|
test('writes and applies the portfolio while the device is locked without unlocking', async () => {
|
|
269
231
|
const packageBytes = new Uint8Array([1, 2, 3]);
|
|
270
232
|
const typedCall = jest
|
|
@@ -272,7 +234,7 @@ describe('UploadPortfolio', () => {
|
|
|
272
234
|
.mockResolvedValueOnce({ message: { processed_byte: 3 } })
|
|
273
235
|
.mockResolvedValueOnce({ message: { message: 'Portfolio updated' } });
|
|
274
236
|
const unlockDevice = jest.fn().mockResolvedValue(undefined);
|
|
275
|
-
const device =
|
|
237
|
+
const device = stubDevice({
|
|
276
238
|
features: { unlocked: false },
|
|
277
239
|
commands: { typedCall },
|
|
278
240
|
isProtocolV2: () => true,
|
|
@@ -317,7 +279,7 @@ describe('UploadPortfolio', () => {
|
|
|
317
279
|
packageBytes,
|
|
318
280
|
},
|
|
319
281
|
});
|
|
320
|
-
(method as any).device =
|
|
282
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
321
283
|
method.postMessage = jest.fn();
|
|
322
284
|
|
|
323
285
|
method.init();
|
|
@@ -365,7 +327,7 @@ describe('UploadPortfolio', () => {
|
|
|
365
327
|
packageBytes: new Uint8Array([1]),
|
|
366
328
|
},
|
|
367
329
|
});
|
|
368
|
-
(method as any).device =
|
|
330
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
369
331
|
|
|
370
332
|
method.init();
|
|
371
333
|
|
|
@@ -388,7 +350,7 @@ describe('UploadPortfolio', () => {
|
|
|
388
350
|
},
|
|
389
351
|
});
|
|
390
352
|
method.abortSignal = abortController.signal;
|
|
391
|
-
(method as any).device =
|
|
353
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
392
354
|
|
|
393
355
|
method.init();
|
|
394
356
|
|
|
@@ -398,31 +360,6 @@ describe('UploadPortfolio', () => {
|
|
|
398
360
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
399
361
|
expect(typedCall).not.toHaveBeenCalledWith('PortfolioUpdate', 'Success', {});
|
|
400
362
|
});
|
|
401
|
-
|
|
402
|
-
test('rejects unsupported firmware before staging the package', async () => {
|
|
403
|
-
const typedCall = jest.fn();
|
|
404
|
-
const method = new UploadPortfolio({
|
|
405
|
-
id: 1,
|
|
406
|
-
payload: {
|
|
407
|
-
method: 'uploadPortfolio',
|
|
408
|
-
packageBytes: new Uint8Array([1]),
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
(method as any).device = stubDevice({
|
|
412
|
-
commands: { typedCall },
|
|
413
|
-
ensureProtocolV2RuntimeContext: jest.fn().mockResolvedValue({
|
|
414
|
-
version: 2,
|
|
415
|
-
supported_messages: [60805],
|
|
416
|
-
}),
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
method.init();
|
|
420
|
-
|
|
421
|
-
await expect(method.run()).rejects.toMatchObject({
|
|
422
|
-
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
423
|
-
});
|
|
424
|
-
expect(typedCall).not.toHaveBeenCalled();
|
|
425
|
-
});
|
|
426
363
|
});
|
|
427
364
|
|
|
428
365
|
const descriptor = {
|
|
@@ -2787,12 +2724,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2787
2724
|
expect(checkedTypes).toEqual(['pro2', 'model_pro2']);
|
|
2788
2725
|
});
|
|
2789
2726
|
|
|
2790
|
-
test
|
|
2791
|
-
['Pro2', DeviceType.PRO2],
|
|
2792
|
-
['Neo', DeviceType.NEO],
|
|
2793
|
-
] as const)('uses firmware-v1 as the %s remote firmware config field', (_name, deviceType) => {
|
|
2727
|
+
test('uses firmware-v1 as the Pro2 remote firmware config field', () => {
|
|
2794
2728
|
const features = normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2795
|
-
hw: { Device_type:
|
|
2729
|
+
hw: { Device_type: DeviceType.PRO2 },
|
|
2796
2730
|
});
|
|
2797
2731
|
|
|
2798
2732
|
expect(
|
|
@@ -4033,7 +3967,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4033
3967
|
});
|
|
4034
3968
|
});
|
|
4035
3969
|
|
|
4036
|
-
test('
|
|
3970
|
+
test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
|
|
4037
3971
|
const method = new FirmwareUpdateV4({
|
|
4038
3972
|
id: 1,
|
|
4039
3973
|
payload: {
|
|
@@ -4053,21 +3987,55 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4053
3987
|
bootloaderMode: false,
|
|
4054
3988
|
capabilities: [],
|
|
4055
3989
|
},
|
|
4056
|
-
getCurrentDeviceType: () => 'pro2',
|
|
4057
3990
|
isBootloader: () => false,
|
|
4058
3991
|
isRomloader: () => false,
|
|
4059
3992
|
});
|
|
4060
3993
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4061
|
-
(method as any).
|
|
4062
|
-
|
|
3994
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
3995
|
+
bootloaderBinary: null,
|
|
3996
|
+
fwBinaryMap: [],
|
|
3997
|
+
installItems: [],
|
|
3998
|
+
});
|
|
4063
3999
|
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
}),
|
|
4000
|
+
const order: string[] = [];
|
|
4001
|
+
(method as any).prepareProtocolV2BootResources = jest.fn().mockImplementation(() => {
|
|
4002
|
+
order.push('prepare-startup-resources');
|
|
4003
|
+
return Promise.resolve([]);
|
|
4069
4004
|
});
|
|
4070
|
-
|
|
4005
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
|
|
4006
|
+
order.push('enter-bootloader');
|
|
4007
|
+
return Promise.resolve(true);
|
|
4008
|
+
});
|
|
4009
|
+
(method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
|
|
4010
|
+
order.push('prepare-resources');
|
|
4011
|
+
return Promise.resolve([
|
|
4012
|
+
{
|
|
4013
|
+
name: 'images.okpkg',
|
|
4014
|
+
binary: new Uint8Array([1]).buffer,
|
|
4015
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
4016
|
+
},
|
|
4017
|
+
]);
|
|
4018
|
+
});
|
|
4019
|
+
(method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
|
|
4020
|
+
order.push('execute-update');
|
|
4021
|
+
return Promise.resolve();
|
|
4022
|
+
});
|
|
4023
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4024
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
4025
|
+
bootloaderVersion: '1.0.0',
|
|
4026
|
+
bleVersion: '1.0.0',
|
|
4027
|
+
firmwareVersion: '1.0.0',
|
|
4028
|
+
});
|
|
4029
|
+
method.postTipMessage = jest.fn();
|
|
4030
|
+
|
|
4031
|
+
await method.run();
|
|
4032
|
+
|
|
4033
|
+
expect(order).toEqual([
|
|
4034
|
+
'prepare-startup-resources',
|
|
4035
|
+
'enter-bootloader',
|
|
4036
|
+
'prepare-resources',
|
|
4037
|
+
'execute-update',
|
|
4038
|
+
]);
|
|
4071
4039
|
});
|
|
4072
4040
|
|
|
4073
4041
|
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
@@ -4236,6 +4204,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4236
4204
|
},
|
|
4237
4205
|
});
|
|
4238
4206
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4207
|
+
const prepareResourceBundles = jest
|
|
4208
|
+
.spyOn(method as any, 'prepareProtocolV2ResourceBundles')
|
|
4209
|
+
.mockResolvedValue(undefined);
|
|
4239
4210
|
(method as any).protocolV2Reboot = jest.fn();
|
|
4240
4211
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
4241
4212
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
@@ -4248,6 +4219,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4248
4219
|
|
|
4249
4220
|
await method.run();
|
|
4250
4221
|
|
|
4222
|
+
expect(prepareResourceBundles).not.toHaveBeenCalled();
|
|
4251
4223
|
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4252
4224
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
4253
4225
|
bootloaderBinary: null,
|
|
@@ -5586,33 +5558,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5586
5558
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
5587
5559
|
.fn()
|
|
5588
5560
|
.mockResolvedValue(undefined);
|
|
5589
|
-
(method as any).isProtocolV2ResourceBundleUpToDate = jest.fn().mockResolvedValue(false);
|
|
5590
5561
|
|
|
5591
|
-
await (method as any).
|
|
5592
|
-
|
|
5562
|
+
await (method as any).executeProtocolV2Update({
|
|
5563
|
+
resourceBundles: [
|
|
5593
5564
|
{
|
|
5594
5565
|
name: 'images.okpkg',
|
|
5595
|
-
|
|
5596
|
-
size: 2,
|
|
5597
|
-
readAt: jest.fn(),
|
|
5598
|
-
close: jest.fn(),
|
|
5599
|
-
},
|
|
5566
|
+
binary: new Uint8Array([1, 2]).buffer,
|
|
5600
5567
|
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
5601
|
-
version: [1, 2, 3],
|
|
5602
|
-
payloadHash: '11'.repeat(64),
|
|
5603
|
-
headerHash: '22'.repeat(64),
|
|
5604
5568
|
},
|
|
5605
5569
|
],
|
|
5606
|
-
|
|
5570
|
+
bootloaderBinary: null,
|
|
5571
|
+
fwBinaryMap: [
|
|
5607
5572
|
{
|
|
5608
5573
|
fileName: 'application_p1.bin',
|
|
5609
|
-
|
|
5610
|
-
size: 1,
|
|
5611
|
-
readAt: jest.fn(),
|
|
5612
|
-
close: jest.fn(),
|
|
5613
|
-
},
|
|
5574
|
+
binary: new Uint8Array([3]).buffer,
|
|
5614
5575
|
targetId: 4,
|
|
5615
|
-
kind: 'firmware',
|
|
5616
5576
|
},
|
|
5617
5577
|
],
|
|
5618
5578
|
});
|
|
@@ -5629,32 +5589,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5629
5589
|
2,
|
|
5630
5590
|
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
5631
5591
|
);
|
|
5632
|
-
expect((method as any).isProtocolV2ResourceBundleUpToDate).toHaveBeenCalledWith(
|
|
5633
|
-
expect.objectContaining({
|
|
5634
|
-
version: [1, 2, 3],
|
|
5635
|
-
payloadHash: '11'.repeat(64),
|
|
5636
|
-
headerHash: '22'.repeat(64),
|
|
5637
|
-
})
|
|
5638
|
-
);
|
|
5639
|
-
});
|
|
5640
|
-
|
|
5641
|
-
test('installs and verifies bootloader before syncing manifest resources', () => {
|
|
5642
|
-
const method = new FirmwareUpdateV4({
|
|
5643
|
-
id: 1,
|
|
5644
|
-
payload: { method: 'firmwareUpdateV4' },
|
|
5645
|
-
});
|
|
5646
|
-
const phases = (method as any).buildProtocolV2ExecutionPhases({
|
|
5647
|
-
installSources: [{ kind: 'bootloader' }, { kind: 'component' }],
|
|
5648
|
-
resourceSources: [{ kind: 'resource' }],
|
|
5649
|
-
});
|
|
5650
|
-
|
|
5651
|
-
expect(phases.map((phase: { kind: string }) => phase.kind)).toEqual([
|
|
5652
|
-
'bootloader-install',
|
|
5653
|
-
'bootloader-verify',
|
|
5654
|
-
'resource-sync',
|
|
5655
|
-
'component-install',
|
|
5656
|
-
'final-verify',
|
|
5657
|
-
]);
|
|
5658
5592
|
});
|
|
5659
5593
|
|
|
5660
5594
|
test('does not request installation when the staged file size does not match', async () => {
|
|
@@ -5799,14 +5733,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5799
5733
|
binary: binaries.get(url) ?? new Uint8Array([0]).buffer,
|
|
5800
5734
|
})
|
|
5801
5735
|
);
|
|
5802
|
-
const componentIntegrity = (url: string) => {
|
|
5803
|
-
const binary = binaries.get(url);
|
|
5804
|
-
if (!binary) throw new Error(`Missing test firmware binary: ${url}`);
|
|
5805
|
-
return {
|
|
5806
|
-
expectedSize: binary.byteLength,
|
|
5807
|
-
fingerprint: bytesToHex(sha256(new Uint8Array(binary))),
|
|
5808
|
-
};
|
|
5809
|
-
};
|
|
5810
5736
|
const getFirmwareLatestReleaseSpy = jest
|
|
5811
5737
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5812
5738
|
.mockReturnValue({
|
|
@@ -5831,43 +5757,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5831
5757
|
bootloader: {
|
|
5832
5758
|
target: 'BOOTLOADER',
|
|
5833
5759
|
url: 'https://example.com/bootloader.pp.bin',
|
|
5834
|
-
...componentIntegrity('https://example.com/bootloader.pp.bin'),
|
|
5835
5760
|
},
|
|
5836
5761
|
applicationP1: {
|
|
5837
5762
|
target: 'APPLICATION_P1',
|
|
5838
5763
|
url: 'https://example.com/applicationP1.pp.bin',
|
|
5839
|
-
...componentIntegrity('https://example.com/applicationP1.pp.bin'),
|
|
5840
5764
|
},
|
|
5841
5765
|
applicationP2: {
|
|
5842
5766
|
target: 'APPLICATION_P2',
|
|
5843
5767
|
url: 'https://example.com/applicationP2.pp.bin',
|
|
5844
|
-
...componentIntegrity('https://example.com/applicationP2.pp.bin'),
|
|
5845
5768
|
},
|
|
5846
5769
|
coprocessor: {
|
|
5847
5770
|
target: 'COPROCESSOR',
|
|
5848
5771
|
url: 'https://example.com/coprocessor.pp.bin',
|
|
5849
|
-
...componentIntegrity('https://example.com/coprocessor.pp.bin'),
|
|
5850
|
-
},
|
|
5851
|
-
se01: {
|
|
5852
|
-
target: 'SE01',
|
|
5853
|
-
url: 'https://example.com/se01.pp.bin',
|
|
5854
|
-
...componentIntegrity('https://example.com/se01.pp.bin'),
|
|
5855
|
-
},
|
|
5856
|
-
se02: {
|
|
5857
|
-
target: 'SE02',
|
|
5858
|
-
url: 'https://example.com/se02.pp.bin',
|
|
5859
|
-
...componentIntegrity('https://example.com/se02.pp.bin'),
|
|
5860
|
-
},
|
|
5861
|
-
se03: {
|
|
5862
|
-
target: 'SE03',
|
|
5863
|
-
url: 'https://example.com/se03.pp.bin',
|
|
5864
|
-
...componentIntegrity('https://example.com/se03.pp.bin'),
|
|
5865
|
-
},
|
|
5866
|
-
se04: {
|
|
5867
|
-
target: 'SE04',
|
|
5868
|
-
url: 'https://example.com/se04.pp.bin',
|
|
5869
|
-
...componentIntegrity('https://example.com/se04.pp.bin'),
|
|
5870
5772
|
},
|
|
5773
|
+
se01: { target: 'SE01', url: 'https://example.com/se01.pp.bin' },
|
|
5774
|
+
se02: { target: 'SE02', url: 'https://example.com/se02.pp.bin' },
|
|
5775
|
+
se03: { target: 'SE03', url: 'https://example.com/se03.pp.bin' },
|
|
5776
|
+
se04: { target: 'SE04', url: 'https://example.com/se04.pp.bin' },
|
|
5871
5777
|
},
|
|
5872
5778
|
fingerprint: '',
|
|
5873
5779
|
changelog: {
|
|
@@ -5908,84 +5814,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5908
5814
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5909
5815
|
});
|
|
5910
5816
|
|
|
5911
|
-
test('downloads only firmware targets that are missing from an explicit mixed payload', async () => {
|
|
5912
|
-
const explicitApplicationBinary = new Uint8Array([1]).buffer;
|
|
5913
|
-
const remoteCoprocessorBinary = new Uint8Array([2]).buffer;
|
|
5914
|
-
const method = new FirmwareUpdateV4({
|
|
5915
|
-
id: 1,
|
|
5916
|
-
payload: {
|
|
5917
|
-
method: 'firmwareUpdateV4',
|
|
5918
|
-
platform: 'web',
|
|
5919
|
-
targetsToUpdate: ['app_v1', 'coprocessor'],
|
|
5920
|
-
},
|
|
5921
|
-
});
|
|
5922
|
-
method.init();
|
|
5923
|
-
|
|
5924
|
-
const getSysResourceBinarySpy = jest
|
|
5925
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5926
|
-
.mockResolvedValue({ binary: remoteCoprocessorBinary });
|
|
5927
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
5928
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5929
|
-
.mockReturnValue({
|
|
5930
|
-
required: false,
|
|
5931
|
-
version: [1, 0, 0],
|
|
5932
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5933
|
-
installOrder: ['applicationP1', 'coprocessor'],
|
|
5934
|
-
components: {
|
|
5935
|
-
applicationP1: {
|
|
5936
|
-
target: 'APPLICATION_P1',
|
|
5937
|
-
url: 'https://example.com/applicationP1.pp.bin',
|
|
5938
|
-
expectedSize: explicitApplicationBinary.byteLength,
|
|
5939
|
-
fingerprint: bytesToHex(sha256(new Uint8Array(explicitApplicationBinary))),
|
|
5940
|
-
},
|
|
5941
|
-
coprocessor: {
|
|
5942
|
-
target: 'COPROCESSOR',
|
|
5943
|
-
url: 'https://example.com/coprocessor.pp.bin',
|
|
5944
|
-
expectedSize: remoteCoprocessorBinary.byteLength,
|
|
5945
|
-
fingerprint: bytesToHex(sha256(new Uint8Array(remoteCoprocessorBinary))),
|
|
5946
|
-
},
|
|
5947
|
-
},
|
|
5948
|
-
fingerprint: '',
|
|
5949
|
-
changelog: {
|
|
5950
|
-
'zh-CN': '',
|
|
5951
|
-
'en-US': '',
|
|
5952
|
-
},
|
|
5953
|
-
});
|
|
5954
|
-
|
|
5955
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries(
|
|
5956
|
-
'universal',
|
|
5957
|
-
{ deviceType: 'pro2', firmwareVersion: '0.0.0' },
|
|
5958
|
-
[
|
|
5959
|
-
{
|
|
5960
|
-
fileName: 'application_p1.bin',
|
|
5961
|
-
binary: explicitApplicationBinary,
|
|
5962
|
-
targetId: 4,
|
|
5963
|
-
kind: 'firmware',
|
|
5964
|
-
},
|
|
5965
|
-
]
|
|
5966
|
-
);
|
|
5967
|
-
|
|
5968
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledTimes(1);
|
|
5969
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledWith('https://example.com/coprocessor.pp.bin');
|
|
5970
|
-
expect(remoteBinaries.installItems).toEqual([
|
|
5971
|
-
{
|
|
5972
|
-
fileName: 'application_p1.bin',
|
|
5973
|
-
binary: explicitApplicationBinary,
|
|
5974
|
-
targetId: 4,
|
|
5975
|
-
kind: 'firmware',
|
|
5976
|
-
},
|
|
5977
|
-
{
|
|
5978
|
-
fileName: 'coprocessor.bin',
|
|
5979
|
-
binary: remoteCoprocessorBinary,
|
|
5980
|
-
targetId: 6,
|
|
5981
|
-
kind: 'firmware',
|
|
5982
|
-
},
|
|
5983
|
-
]);
|
|
5984
|
-
|
|
5985
|
-
getSysResourceBinarySpy.mockRestore();
|
|
5986
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5987
|
-
});
|
|
5988
|
-
|
|
5989
5817
|
test('ignores an unsupported remote ROMLOADER component when installing selected targets', async () => {
|
|
5990
5818
|
const method = new FirmwareUpdateV4({
|
|
5991
5819
|
id: 1,
|
|
@@ -6150,753 +5978,211 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6150
5978
|
});
|
|
6151
5979
|
});
|
|
6152
5980
|
|
|
6153
|
-
test('
|
|
5981
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
6154
5982
|
const method = new FirmwareUpdateV4({
|
|
6155
5983
|
id: 1,
|
|
6156
5984
|
payload: {
|
|
6157
5985
|
method: 'firmwareUpdateV4',
|
|
6158
5986
|
},
|
|
6159
5987
|
});
|
|
6160
|
-
const
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
const manifest = {
|
|
6165
|
-
schema: 1,
|
|
6166
|
-
artifact_name: 'pro2-resource',
|
|
6167
|
-
release_name: 'test-release',
|
|
6168
|
-
variant: 'resource',
|
|
6169
|
-
commit: 'abc123',
|
|
6170
|
-
short_sha: 'abc123',
|
|
6171
|
-
timestamp_utc: '2026-08-09T00:00:00Z',
|
|
6172
|
-
core_version: '1.0.0',
|
|
6173
|
-
key_set: 'production',
|
|
6174
|
-
device_root: 'vol0:',
|
|
6175
|
-
restore_mode: 'bootloader_update',
|
|
6176
|
-
trees: [{ path: 'bundles', device: 'pro2' }],
|
|
6177
|
-
files: [
|
|
6178
|
-
{
|
|
6179
|
-
archive_path: 'bundles/images/images.okpkg',
|
|
6180
|
-
original_name: 'images.okpkg',
|
|
6181
|
-
device_path: 'vol0:/bundles/images/images.okpkg',
|
|
6182
|
-
size: imagesBinary.byteLength,
|
|
6183
|
-
sha256: imagesSha256,
|
|
6184
|
-
signed: true,
|
|
6185
|
-
sig_algo: 'ed25519',
|
|
6186
|
-
payload_version: '1.2.3',
|
|
6187
|
-
},
|
|
5988
|
+
const openPreparedSource = jest.fn();
|
|
5989
|
+
(method as any).params = {
|
|
5990
|
+
targetsToUpdate: ['resource'],
|
|
5991
|
+
resourceBundleArtifacts: [
|
|
6188
5992
|
{
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
sig_algo: 'ed25519',
|
|
6196
|
-
payload_version: '1.2.3',
|
|
5993
|
+
name: 'images',
|
|
5994
|
+
artifact: {
|
|
5995
|
+
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5996
|
+
size: 1,
|
|
5997
|
+
sha256: 'a'.repeat(64),
|
|
5998
|
+
},
|
|
6197
5999
|
},
|
|
6198
6000
|
],
|
|
6199
6001
|
};
|
|
6200
|
-
|
|
6201
|
-
const
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
entryName: 'manifest.json',
|
|
6209
|
-
artifact: {
|
|
6210
|
-
artifactRef: 'manifest',
|
|
6211
|
-
size: manifestBinary.byteLength,
|
|
6212
|
-
sha256: bytesToHex(sha256(new Uint8Array(manifestBinary))),
|
|
6213
|
-
},
|
|
6214
|
-
},
|
|
6215
|
-
{
|
|
6216
|
-
entryName: 'bundles/images/images.okpkg',
|
|
6217
|
-
artifact: {
|
|
6218
|
-
artifactRef: 'images',
|
|
6219
|
-
size: imagesBinary.byteLength,
|
|
6220
|
-
sha256: imagesSha256,
|
|
6221
|
-
},
|
|
6222
|
-
},
|
|
6223
|
-
{
|
|
6224
|
-
entryName: 'loaders/bootloader/boot_resource.okpkg',
|
|
6225
|
-
artifact: {
|
|
6226
|
-
artifactRef: 'boot',
|
|
6227
|
-
size: bootBinary.byteLength,
|
|
6228
|
-
sha256: bootSha256,
|
|
6229
|
-
},
|
|
6230
|
-
},
|
|
6231
|
-
];
|
|
6232
|
-
const artifacts = new Map([['archive', archiveBinary]]);
|
|
6233
|
-
(method as any).params = {
|
|
6234
|
-
targetsToUpdate: ['resource'],
|
|
6235
|
-
preparedPlan: {
|
|
6236
|
-
artifacts: [
|
|
6002
|
+
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
6003
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
6004
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
6005
|
+
.mockReturnValue({
|
|
6006
|
+
required: false,
|
|
6007
|
+
version: [1, 0, 0],
|
|
6008
|
+
url: '',
|
|
6009
|
+
resourceBundles: [
|
|
6237
6010
|
{
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
container: 'zip',
|
|
6242
|
-
artifact: {
|
|
6243
|
-
artifactRef: 'archive',
|
|
6244
|
-
size: archiveBinary.byteLength,
|
|
6245
|
-
sha256: bytesToHex(sha256(new Uint8Array(archiveBinary))),
|
|
6246
|
-
},
|
|
6247
|
-
materializedEntries: preparedEntries,
|
|
6011
|
+
name: 'images',
|
|
6012
|
+
url: 'https://example.com/images.okpkg',
|
|
6013
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
6248
6014
|
},
|
|
6249
6015
|
],
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
});
|
|
6257
|
-
|
|
6258
|
-
const sources = await (method as any).prepareProtocolV2ResourceArchiveSources();
|
|
6016
|
+
fingerprint: '',
|
|
6017
|
+
changelog: {
|
|
6018
|
+
'zh-CN': '',
|
|
6019
|
+
'en-US': '',
|
|
6020
|
+
},
|
|
6021
|
+
});
|
|
6259
6022
|
|
|
6260
|
-
expect(
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
}),
|
|
6267
|
-
expect.objectContaining({
|
|
6268
|
-
name: 'boot_resource.okpkg',
|
|
6269
|
-
devicePath: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
6270
|
-
version: [1, 2, 3],
|
|
6271
|
-
}),
|
|
6272
|
-
]);
|
|
6273
|
-
await Promise.all(sources.map(source => source.source.close()));
|
|
6023
|
+
await expect(
|
|
6024
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
6025
|
+
deviceType: 'pro2',
|
|
6026
|
+
})
|
|
6027
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
6028
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
6274
6029
|
|
|
6275
|
-
|
|
6276
|
-
await expect((method as any).prepareProtocolV2ResourceArchiveSources()).rejects.toThrow(
|
|
6277
|
-
'entries do not match the approved archive'
|
|
6278
|
-
);
|
|
6030
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
6279
6031
|
});
|
|
6280
6032
|
|
|
6281
|
-
test('
|
|
6282
|
-
const
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
firmwareType: EFirmwareType.Universal,
|
|
6288
|
-
platform: 'web' as const,
|
|
6289
|
-
targetsToUpdate: ['resource' as const],
|
|
6290
|
-
artifacts: [
|
|
6291
|
-
{
|
|
6292
|
-
artifactId: 'resource:archive',
|
|
6293
|
-
role: 'resourceBundle' as const,
|
|
6294
|
-
target: 'resource' as const,
|
|
6295
|
-
url: 'https://example.com/resource.zip',
|
|
6296
|
-
container: 'zip' as const,
|
|
6297
|
-
logicalName: 'protocol-v2-resource-archive',
|
|
6298
|
-
},
|
|
6299
|
-
],
|
|
6300
|
-
};
|
|
6301
|
-
const plan = {
|
|
6302
|
-
...planWithoutDigest,
|
|
6303
|
-
planDigest: digestFirmwareUpdateContract(planWithoutDigest),
|
|
6304
|
-
};
|
|
6305
|
-
const preparedPlan = prepareFirmwareUpdatePlan({
|
|
6306
|
-
plan,
|
|
6307
|
-
leaseRef: 'resource-archive-test',
|
|
6308
|
-
artifacts: [
|
|
6309
|
-
{
|
|
6310
|
-
artifactId: 'resource:archive',
|
|
6311
|
-
artifact: {
|
|
6312
|
-
artifactRef: 'resource-archive',
|
|
6313
|
-
size: 3,
|
|
6314
|
-
sha256: 'a'.repeat(64),
|
|
6315
|
-
},
|
|
6316
|
-
materializedEntries: [
|
|
6317
|
-
{
|
|
6318
|
-
entryName: 'manifest.json',
|
|
6319
|
-
artifact: {
|
|
6320
|
-
artifactRef: 'resource-manifest',
|
|
6321
|
-
size: 2,
|
|
6322
|
-
sha256: 'b'.repeat(64),
|
|
6323
|
-
},
|
|
6324
|
-
},
|
|
6325
|
-
],
|
|
6326
|
-
},
|
|
6327
|
-
],
|
|
6328
|
-
});
|
|
6329
|
-
const mismatchedHostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
6330
|
-
preparedPlanDigest: 'c'.repeat(64),
|
|
6331
|
-
artifactReader: {
|
|
6332
|
-
open: jest.fn(),
|
|
6333
|
-
read: jest.fn(),
|
|
6334
|
-
close: jest.fn(),
|
|
6033
|
+
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
6034
|
+
const method = new FirmwareUpdateV4({
|
|
6035
|
+
id: 1,
|
|
6036
|
+
payload: {
|
|
6037
|
+
method: 'firmwareUpdateV4',
|
|
6038
|
+
platform: 'web',
|
|
6335
6039
|
},
|
|
6336
6040
|
});
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6041
|
+
method.init();
|
|
6042
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6043
|
+
(method as any).device = stubDevice({
|
|
6044
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
6045
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
6046
|
+
});
|
|
6047
|
+
forceReloadDataSpy.mockRejectedValue(
|
|
6048
|
+
ERRORS.TypedError(HardwareErrorCode.NetworkError, 'latest config unavailable')
|
|
6049
|
+
);
|
|
6050
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
6051
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
6052
|
+
method.postTipMessage = jest.fn();
|
|
6348
6053
|
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
}
|
|
6353
|
-
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
6354
|
-
preparedPlanDigest: preparedPlan.preparedPlanDigest,
|
|
6355
|
-
artifactReader: {
|
|
6356
|
-
open: jest.fn(),
|
|
6357
|
-
read: jest.fn(),
|
|
6358
|
-
close: jest.fn(),
|
|
6359
|
-
},
|
|
6054
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
6055
|
+
errorCode: HardwareErrorCode.NetworkError,
|
|
6056
|
+
message: 'latest config unavailable',
|
|
6360
6057
|
});
|
|
6361
|
-
try {
|
|
6362
|
-
const method = new FirmwareUpdateV4({
|
|
6363
|
-
id: 1,
|
|
6364
|
-
payload: {
|
|
6365
|
-
method: 'firmwareUpdateV4',
|
|
6366
|
-
platform: 'web',
|
|
6367
|
-
targetsToUpdate: ['resource'],
|
|
6368
|
-
preparedPlan,
|
|
6369
|
-
hostBindingGeneration,
|
|
6370
|
-
},
|
|
6371
|
-
});
|
|
6372
6058
|
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
unregisterFirmwareUpdateHostBinding(hostBindingGeneration);
|
|
6376
|
-
}
|
|
6059
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
6060
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
6377
6061
|
});
|
|
6378
6062
|
|
|
6379
|
-
test('
|
|
6380
|
-
const
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
schemaVersion: 2 as const,
|
|
6388
|
-
executor: 'v4' as const,
|
|
6389
|
-
deviceIdentity: 'PRO2-SERIAL',
|
|
6390
|
-
deviceModel: 'pro2',
|
|
6391
|
-
firmwareType: EFirmwareType.Universal,
|
|
6392
|
-
platform: 'web' as const,
|
|
6393
|
-
targetsToUpdate: ['boot' as const],
|
|
6394
|
-
artifacts: [
|
|
6395
|
-
{
|
|
6396
|
-
artifactId: 'component:boot',
|
|
6397
|
-
role: 'component' as const,
|
|
6398
|
-
target: 'boot' as const,
|
|
6399
|
-
url: 'https://example.com/bootloader.bin',
|
|
6400
|
-
container: 'raw' as const,
|
|
6401
|
-
logicalName: 'bootloader',
|
|
6402
|
-
targetVersion: '1.2.3',
|
|
6403
|
-
},
|
|
6404
|
-
],
|
|
6405
|
-
};
|
|
6406
|
-
const preparedPlan = prepareFirmwareUpdatePlan({
|
|
6407
|
-
plan: {
|
|
6408
|
-
...planWithoutDigest,
|
|
6409
|
-
planDigest: digestFirmwareUpdateContract(planWithoutDigest),
|
|
6410
|
-
},
|
|
6411
|
-
leaseRef: 'prepared-component-test',
|
|
6412
|
-
artifacts: [{ artifactId: 'component:boot', artifact: componentArtifact }],
|
|
6413
|
-
});
|
|
6414
|
-
const artifactReader = {
|
|
6415
|
-
open: jest.fn(() =>
|
|
6416
|
-
Promise.resolve({
|
|
6417
|
-
readerId: 'prepared-component-reader',
|
|
6418
|
-
size: componentBinary.byteLength,
|
|
6419
|
-
})
|
|
6420
|
-
),
|
|
6421
|
-
read: jest.fn(({ offset, length }: { offset: number; length: number }) => {
|
|
6422
|
-
const data = componentBinary.slice(offset, offset + length);
|
|
6423
|
-
return Promise.resolve({
|
|
6424
|
-
data,
|
|
6425
|
-
bytesRead: data.byteLength,
|
|
6426
|
-
eof: offset + length === componentBinary.byteLength,
|
|
6427
|
-
});
|
|
6428
|
-
}),
|
|
6429
|
-
close: jest.fn(() => Promise.resolve()),
|
|
6430
|
-
};
|
|
6431
|
-
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
6432
|
-
preparedPlanDigest: preparedPlan.preparedPlanDigest,
|
|
6433
|
-
artifactReader,
|
|
6063
|
+
test('keeps explicit Protocol V2 binaries isolated from remote component auto-fill', async () => {
|
|
6064
|
+
const method = new FirmwareUpdateV4({
|
|
6065
|
+
id: 1,
|
|
6066
|
+
payload: {
|
|
6067
|
+
method: 'firmwareUpdateV4',
|
|
6068
|
+
platform: 'web',
|
|
6069
|
+
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
6070
|
+
},
|
|
6434
6071
|
});
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
.mockReturnValue(undefined);
|
|
6438
|
-
|
|
6439
|
-
try {
|
|
6440
|
-
const method = new FirmwareUpdateV4({
|
|
6441
|
-
id: 1,
|
|
6442
|
-
payload: {
|
|
6443
|
-
method: 'firmwareUpdateV4',
|
|
6444
|
-
platform: 'web',
|
|
6445
|
-
preparedPlan,
|
|
6446
|
-
hostBindingGeneration,
|
|
6447
|
-
},
|
|
6448
|
-
});
|
|
6072
|
+
method.init();
|
|
6073
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6449
6074
|
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6075
|
+
(method as any).device = stubDevice({
|
|
6076
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
6077
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
6078
|
+
});
|
|
6079
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
6080
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
6081
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
6082
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
6083
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
6084
|
+
bootloaderVersion: '1.0.0',
|
|
6085
|
+
bleVersion: '0.0.0',
|
|
6086
|
+
firmwareVersion: '1.0.0',
|
|
6087
|
+
});
|
|
6088
|
+
method.postTipMessage = jest.fn();
|
|
6459
6089
|
|
|
6460
|
-
|
|
6461
|
-
(method as any).runProtocolV2PreparedArtifacts({} as Features, EFirmwareType.Universal)
|
|
6462
|
-
).resolves.toBe('prepared-result');
|
|
6463
|
-
expect(releaseSpy).not.toHaveBeenCalled();
|
|
6464
|
-
expect(artifactReader.open).toHaveBeenCalledWith({ artifactRef: 'prepared-bootloader' });
|
|
6465
|
-
expect((method as any).executeProtocolV2SourceUpdate).toHaveBeenCalledWith({
|
|
6466
|
-
installSources: [
|
|
6467
|
-
expect.objectContaining({
|
|
6468
|
-
fileName: 'bootloader.bin',
|
|
6469
|
-
kind: 'bootloader',
|
|
6470
|
-
}),
|
|
6471
|
-
],
|
|
6472
|
-
resourceSources: [],
|
|
6473
|
-
});
|
|
6474
|
-
} finally {
|
|
6475
|
-
releaseSpy.mockRestore();
|
|
6476
|
-
unregisterFirmwareUpdateHostBinding(hostBindingGeneration);
|
|
6477
|
-
}
|
|
6478
|
-
});
|
|
6090
|
+
await method.run();
|
|
6479
6091
|
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
deviceIdentity: 'PRO2-SERIAL',
|
|
6485
|
-
deviceModel: 'pro2',
|
|
6486
|
-
firmwareType: EFirmwareType.Universal,
|
|
6487
|
-
platform: 'web' as const,
|
|
6488
|
-
targetsToUpdate: ['resource' as const],
|
|
6489
|
-
artifacts: [
|
|
6490
|
-
{
|
|
6491
|
-
artifactId: 'resource:archive',
|
|
6492
|
-
role: 'resourceBundle' as const,
|
|
6493
|
-
target: 'resource' as const,
|
|
6494
|
-
url: 'https://example.com/resource.zip',
|
|
6495
|
-
container: 'zip' as const,
|
|
6496
|
-
logicalName: 'protocol-v2-resource-archive',
|
|
6497
|
-
},
|
|
6498
|
-
],
|
|
6499
|
-
};
|
|
6500
|
-
const preparedPlan = prepareFirmwareUpdatePlan({
|
|
6501
|
-
plan: {
|
|
6502
|
-
...planWithoutDigest,
|
|
6503
|
-
planDigest: digestFirmwareUpdateContract(planWithoutDigest),
|
|
6504
|
-
},
|
|
6505
|
-
leaseRef: 'prepared-resource-only-test',
|
|
6506
|
-
artifacts: [
|
|
6507
|
-
{
|
|
6508
|
-
artifactId: 'resource:archive',
|
|
6509
|
-
artifact: {
|
|
6510
|
-
artifactRef: 'prepared-resource-archive',
|
|
6511
|
-
size: 3,
|
|
6512
|
-
sha256: 'a'.repeat(64),
|
|
6513
|
-
},
|
|
6514
|
-
materializedEntries: [
|
|
6515
|
-
{
|
|
6516
|
-
entryName: 'manifest.json',
|
|
6517
|
-
artifact: {
|
|
6518
|
-
artifactRef: 'prepared-resource-manifest',
|
|
6519
|
-
size: 2,
|
|
6520
|
-
sha256: 'b'.repeat(64),
|
|
6521
|
-
},
|
|
6522
|
-
},
|
|
6523
|
-
],
|
|
6524
|
-
},
|
|
6525
|
-
],
|
|
6526
|
-
});
|
|
6527
|
-
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
6528
|
-
preparedPlanDigest: preparedPlan.preparedPlanDigest,
|
|
6529
|
-
artifactReader: {
|
|
6530
|
-
open: jest.fn(),
|
|
6531
|
-
read: jest.fn(),
|
|
6532
|
-
close: jest.fn(),
|
|
6533
|
-
},
|
|
6092
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
6093
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
6094
|
+
bootloaderBinary: null,
|
|
6095
|
+
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
6534
6096
|
});
|
|
6535
|
-
|
|
6536
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
6537
|
-
.mockReturnValue(undefined);
|
|
6097
|
+
});
|
|
6538
6098
|
|
|
6539
|
-
|
|
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;
|
|
6540
6103
|
const method = new FirmwareUpdateV4({
|
|
6541
6104
|
id: 1,
|
|
6542
6105
|
payload: {
|
|
6543
6106
|
method: 'firmwareUpdateV4',
|
|
6544
6107
|
platform: 'web',
|
|
6545
|
-
|
|
6546
|
-
|
|
6108
|
+
targetsToUpdate: [target],
|
|
6109
|
+
resourceFiles: [
|
|
6110
|
+
{
|
|
6111
|
+
binary: resourceBundle,
|
|
6112
|
+
devicePath: ' VOL0:/resource/images/images.okpkg ',
|
|
6113
|
+
},
|
|
6114
|
+
],
|
|
6547
6115
|
},
|
|
6548
6116
|
});
|
|
6117
|
+
method.init();
|
|
6118
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6119
|
+
const bootResourcesSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
6549
6120
|
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
(method as any).prepareProtocolV2ResourceSources = jest
|
|
6554
|
-
.fn()
|
|
6555
|
-
.mockResolvedValue(resourceSources);
|
|
6556
|
-
(method as any).executeProtocolV2SourceUpdate = jest
|
|
6557
|
-
.fn()
|
|
6558
|
-
.mockResolvedValue('resource-result');
|
|
6559
|
-
|
|
6560
|
-
await expect(
|
|
6561
|
-
(method as any).runProtocolV2PreparedArtifacts({} as Features, EFirmwareType.Universal)
|
|
6562
|
-
).resolves.toBe('resource-result');
|
|
6563
|
-
expect(releaseSpy).not.toHaveBeenCalled();
|
|
6564
|
-
expect((method as any).executeProtocolV2SourceUpdate).toHaveBeenCalledWith({
|
|
6565
|
-
installSources: [],
|
|
6566
|
-
resourceSources,
|
|
6121
|
+
(method as any).device = stubDevice({
|
|
6122
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
6123
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
6567
6124
|
});
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
const imagesBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
6576
|
-
const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
|
|
6577
|
-
const manifest = {
|
|
6578
|
-
schema: 1,
|
|
6579
|
-
artifact_name: 'pro2-resource',
|
|
6580
|
-
release_name: 'local-development',
|
|
6581
|
-
variant: 'resource',
|
|
6582
|
-
commit: 'local',
|
|
6583
|
-
short_sha: 'local',
|
|
6584
|
-
timestamp_utc: '2026-08-09T00:00:00Z',
|
|
6585
|
-
core_version: '1.0.0',
|
|
6586
|
-
key_set: 'development',
|
|
6587
|
-
device_root: 'vol0:',
|
|
6588
|
-
restore_mode: 'bootloader_update',
|
|
6589
|
-
trees: [{ path: 'bundles', device: 'pro2' }],
|
|
6590
|
-
files: [
|
|
6591
|
-
{
|
|
6592
|
-
archive_path: 'bundles/images/images.okpkg',
|
|
6593
|
-
original_name: 'images.okpkg',
|
|
6594
|
-
device_path: 'vol0:/bundles/images/images.okpkg',
|
|
6595
|
-
size: imagesBinary.byteLength,
|
|
6596
|
-
sha256: bytesToHex(sha256(new Uint8Array(imagesBinary))),
|
|
6597
|
-
signed: true,
|
|
6598
|
-
sig_algo: 'ed25519',
|
|
6599
|
-
payload_version: '1.0.0',
|
|
6600
|
-
},
|
|
6601
|
-
{
|
|
6602
|
-
archive_path: 'loaders/bootloader/boot_resource.okpkg',
|
|
6603
|
-
original_name: 'boot_resource.okpkg',
|
|
6604
|
-
device_path: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
6605
|
-
size: bootResourceBinary.byteLength,
|
|
6606
|
-
sha256: bytesToHex(sha256(new Uint8Array(bootResourceBinary))),
|
|
6607
|
-
signed: true,
|
|
6608
|
-
sig_algo: 'ed25519',
|
|
6609
|
-
payload_version: '1.0.0',
|
|
6610
|
-
},
|
|
6611
|
-
],
|
|
6612
|
-
};
|
|
6613
|
-
const zip = new JSZip();
|
|
6614
|
-
zip.file('manifest.json', JSON.stringify(manifest));
|
|
6615
|
-
zip.file('bundles/images/images.okpkg', imagesBinary);
|
|
6616
|
-
zip.file('loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
6617
|
-
const resourceArchiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
|
|
6618
|
-
const applicationP1Binary = new Uint8Array([7, 8, 9]).buffer;
|
|
6619
|
-
const method = new FirmwareUpdateV4({
|
|
6620
|
-
id: 1,
|
|
6621
|
-
payload: {
|
|
6622
|
-
method: 'firmwareUpdateV4',
|
|
6623
|
-
platform: 'web',
|
|
6624
|
-
targetsToUpdate: ['app_v1'],
|
|
6625
|
-
applicationP1Binary,
|
|
6626
|
-
resourceArchiveBinary,
|
|
6627
|
-
},
|
|
6628
|
-
});
|
|
6629
|
-
method.init();
|
|
6630
|
-
expect((method as any).params.targetsToUpdate).toEqual(['app_v1', 'resource']);
|
|
6631
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6632
|
-
(method as any).postTipMessage = jest.fn();
|
|
6633
|
-
(method as any).device = stubDevice({
|
|
6634
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
6635
|
-
features: {
|
|
6636
|
-
deviceType: 'pro2',
|
|
6637
|
-
serialNo: 'pro2-device-id',
|
|
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',
|
|
6638
6132
|
firmwareVersion: '1.0.0',
|
|
6639
|
-
capabilities: [],
|
|
6640
|
-
},
|
|
6641
|
-
getCurrentDeviceType: () => 'pro2',
|
|
6642
|
-
});
|
|
6643
|
-
(method as any).protocolV2ExpectedSerialNumber = 'pro2-device-id';
|
|
6644
|
-
(method as any).executeProtocolV2SourceUpdate = jest
|
|
6645
|
-
.fn()
|
|
6646
|
-
.mockResolvedValue('local-resource-result');
|
|
6647
|
-
const releaseSpy = jest.spyOn(DataManager, 'getFirmwareLatestRelease');
|
|
6648
|
-
const reloadSpy = jest.spyOn(DataManager, 'forceReloadData');
|
|
6649
|
-
|
|
6650
|
-
try {
|
|
6651
|
-
await expect(method.run()).resolves.toBe('local-resource-result');
|
|
6652
|
-
expect(releaseSpy).not.toHaveBeenCalled();
|
|
6653
|
-
expect(reloadSpy).not.toHaveBeenCalled();
|
|
6654
|
-
expect((method as any).params.preparedPlan).toMatchObject({
|
|
6655
|
-
networkPolicy: 'forbid',
|
|
6656
|
-
executor: 'v4',
|
|
6657
|
-
targetsToUpdate: ['app_v1', 'resource'],
|
|
6658
6133
|
});
|
|
6659
|
-
|
|
6134
|
+
method.postTipMessage = jest.fn();
|
|
6135
|
+
|
|
6136
|
+
await method.run();
|
|
6137
|
+
|
|
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(
|
|
6660
6142
|
expect.objectContaining({
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
fileName: 'application_p1.bin',
|
|
6664
|
-
targetId: 4,
|
|
6665
|
-
}),
|
|
6666
|
-
],
|
|
6667
|
-
resourceSources: [
|
|
6668
|
-
expect.objectContaining({
|
|
6143
|
+
resourceBundles: [
|
|
6144
|
+
{
|
|
6669
6145
|
name: 'images.okpkg',
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
name: 'boot_resource.okpkg',
|
|
6674
|
-
devicePath: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
6675
|
-
}),
|
|
6146
|
+
binary: resourceBundle,
|
|
6147
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
6148
|
+
},
|
|
6676
6149
|
],
|
|
6677
6150
|
})
|
|
6678
6151
|
);
|
|
6679
|
-
} finally {
|
|
6680
|
-
releaseSpy.mockRestore();
|
|
6681
|
-
reloadSpy.mockRestore();
|
|
6682
6152
|
}
|
|
6683
|
-
|
|
6153
|
+
);
|
|
6684
6154
|
|
|
6685
|
-
test('rejects
|
|
6686
|
-
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
6687
|
-
const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
|
|
6688
|
-
const zip = new JSZip();
|
|
6689
|
-
zip.file(
|
|
6690
|
-
'manifest.json',
|
|
6691
|
-
JSON.stringify({
|
|
6692
|
-
schema: 1,
|
|
6693
|
-
artifact_name: 'pro2-resource',
|
|
6694
|
-
release_name: 'local-development',
|
|
6695
|
-
variant: 'resource',
|
|
6696
|
-
commit: 'local',
|
|
6697
|
-
short_sha: 'local',
|
|
6698
|
-
timestamp_utc: '2026-08-09T00:00:00Z',
|
|
6699
|
-
core_version: '1.0.0',
|
|
6700
|
-
key_set: 'development',
|
|
6701
|
-
device_root: 'vol0:',
|
|
6702
|
-
restore_mode: 'bootloader_update',
|
|
6703
|
-
trees: [{ path: 'bundles', device: 'pro2' }],
|
|
6704
|
-
files: [
|
|
6705
|
-
{
|
|
6706
|
-
archive_path: 'bundles/images/images.okpkg',
|
|
6707
|
-
original_name: 'images.okpkg',
|
|
6708
|
-
device_path: 'vol0:/bundles/images/images.okpkg',
|
|
6709
|
-
size: resourceBinary.byteLength,
|
|
6710
|
-
sha256: '0'.repeat(64),
|
|
6711
|
-
signed: true,
|
|
6712
|
-
sig_algo: 'ed25519',
|
|
6713
|
-
payload_version: '1.0.0',
|
|
6714
|
-
},
|
|
6715
|
-
{
|
|
6716
|
-
archive_path: 'loaders/bootloader/boot_resource.okpkg',
|
|
6717
|
-
original_name: 'boot_resource.okpkg',
|
|
6718
|
-
device_path: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
6719
|
-
size: bootResourceBinary.byteLength,
|
|
6720
|
-
sha256: bytesToHex(sha256(new Uint8Array(bootResourceBinary))),
|
|
6721
|
-
signed: true,
|
|
6722
|
-
sig_algo: 'ed25519',
|
|
6723
|
-
payload_version: '1.0.0',
|
|
6724
|
-
},
|
|
6725
|
-
],
|
|
6726
|
-
})
|
|
6727
|
-
);
|
|
6728
|
-
zip.file('bundles/images/images.okpkg', resourceBinary);
|
|
6729
|
-
zip.file('loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
6155
|
+
test('rejects manual RESC bundle paths before bootloader entry', async () => {
|
|
6730
6156
|
const method = new FirmwareUpdateV4({
|
|
6731
6157
|
id: 1,
|
|
6732
6158
|
payload: {
|
|
6733
6159
|
method: 'firmwareUpdateV4',
|
|
6734
6160
|
platform: 'web',
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
await expect(
|
|
6741
|
-
(method as any).prepareProtocolV2LocalResourceArchive(
|
|
6742
|
-
await zip.generateAsync({ type: 'arraybuffer' })
|
|
6743
|
-
)
|
|
6744
|
-
).rejects.toThrow(
|
|
6745
|
-
'Protocol V2 local resource file does not match manifest: bundles/images/images.okpkg'
|
|
6746
|
-
);
|
|
6747
|
-
});
|
|
6748
|
-
|
|
6749
|
-
test('rejects an oversized ZIP entry before allocating its decompressed bytes', async () => {
|
|
6750
|
-
const extractEntry = jest.fn();
|
|
6751
|
-
const loadSpy = jest.spyOn(JSZip, 'loadAsync').mockResolvedValue({
|
|
6752
|
-
files: {
|
|
6753
|
-
'manifest.json': {
|
|
6754
|
-
name: 'manifest.json',
|
|
6755
|
-
dir: false,
|
|
6756
|
-
_data: {
|
|
6757
|
-
compressedSize: 1,
|
|
6758
|
-
uncompressedSize: 2 * 1024 * 1024,
|
|
6161
|
+
resourceFiles: [
|
|
6162
|
+
{
|
|
6163
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
6164
|
+
devicePath: 'vol2:/resource/images/images.okpkg',
|
|
6759
6165
|
},
|
|
6760
|
-
|
|
6761
|
-
},
|
|
6762
|
-
},
|
|
6763
|
-
} as unknown as JSZip);
|
|
6764
|
-
const method = new FirmwareUpdateV4({
|
|
6765
|
-
id: 1,
|
|
6766
|
-
payload: {
|
|
6767
|
-
method: 'firmwareUpdateV4',
|
|
6768
|
-
platform: 'web',
|
|
6769
|
-
targetsToUpdate: ['resource'],
|
|
6770
|
-
},
|
|
6771
|
-
});
|
|
6772
|
-
method.init();
|
|
6773
|
-
|
|
6774
|
-
try {
|
|
6775
|
-
await expect(
|
|
6776
|
-
(method as any).prepareProtocolV2LocalResourceArchive(new Uint8Array([1]).buffer)
|
|
6777
|
-
).rejects.toThrow('declared size exceeds the allowed limit');
|
|
6778
|
-
expect(extractEntry).not.toHaveBeenCalled();
|
|
6779
|
-
} finally {
|
|
6780
|
-
loadSpy.mockRestore();
|
|
6781
|
-
}
|
|
6782
|
-
});
|
|
6783
|
-
|
|
6784
|
-
test('normalizes the deprecated boot_resources target to resource', () => {
|
|
6785
|
-
const method = new FirmwareUpdateV4({
|
|
6786
|
-
id: 1,
|
|
6787
|
-
payload: {
|
|
6788
|
-
method: 'firmwareUpdateV4',
|
|
6789
|
-
platform: 'web',
|
|
6790
|
-
targetsToUpdate: ['boot_resources'],
|
|
6166
|
+
],
|
|
6791
6167
|
},
|
|
6792
6168
|
});
|
|
6793
|
-
|
|
6794
6169
|
method.init();
|
|
6795
|
-
expect((method as any).params.targetsToUpdate).toEqual(['resource']);
|
|
6796
|
-
});
|
|
6797
|
-
|
|
6798
|
-
test('keeps the legacy componentArtifacts and artifactReader path without a prepared Plan', async () => {
|
|
6799
|
-
const artifactReader = {
|
|
6800
|
-
open: jest.fn(),
|
|
6801
|
-
read: jest.fn(),
|
|
6802
|
-
close: jest.fn(),
|
|
6803
|
-
};
|
|
6804
|
-
const method = new FirmwareUpdateV4({
|
|
6805
|
-
id: 1,
|
|
6806
|
-
payload: {
|
|
6807
|
-
method: 'firmwareUpdateV4',
|
|
6808
|
-
platform: 'web',
|
|
6809
|
-
targetsToUpdate: ['boot'],
|
|
6810
|
-
artifactReader,
|
|
6811
|
-
componentArtifacts: {
|
|
6812
|
-
boot: {
|
|
6813
|
-
artifactRef: 'legacy-bootloader',
|
|
6814
|
-
size: 3,
|
|
6815
|
-
sha256: 'a'.repeat(64),
|
|
6816
|
-
},
|
|
6817
|
-
},
|
|
6818
|
-
},
|
|
6819
|
-
});
|
|
6820
|
-
|
|
6821
|
-
expect(() => method.init()).not.toThrow();
|
|
6822
6170
|
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6823
|
-
(method as any).device = stubDevice({
|
|
6824
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
6825
|
-
features: { deviceType: 'pro2', firmwareVersion: '1.0.0', capabilities: [] },
|
|
6826
|
-
});
|
|
6827
|
-
(method as any).runProtocolV2PreparedArtifacts = jest.fn().mockResolvedValue('legacy-result');
|
|
6828
|
-
|
|
6829
|
-
await expect(method.run()).resolves.toBe('legacy-result');
|
|
6830
|
-
expect((method as any).runProtocolV2PreparedArtifacts).toHaveBeenCalledTimes(1);
|
|
6831
|
-
});
|
|
6832
6171
|
|
|
6833
|
-
test('stops a remote update before reboot when the latest config cannot be refreshed', async () => {
|
|
6834
|
-
const method = new FirmwareUpdateV4({
|
|
6835
|
-
id: 1,
|
|
6836
|
-
payload: {
|
|
6837
|
-
method: 'firmwareUpdateV4',
|
|
6838
|
-
platform: 'web',
|
|
6839
|
-
},
|
|
6840
|
-
});
|
|
6841
|
-
method.init();
|
|
6842
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6843
6172
|
(method as any).device = stubDevice({
|
|
6844
6173
|
originalDescriptor: { protocolType: 'V2' },
|
|
6845
6174
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
6846
6175
|
});
|
|
6847
|
-
forceReloadDataSpy.mockRejectedValue(
|
|
6848
|
-
ERRORS.TypedError(HardwareErrorCode.NetworkError, 'latest config unavailable')
|
|
6849
|
-
);
|
|
6850
6176
|
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
6851
6177
|
(method as any).enterProtocolV2BootloaderMode = jest.fn();
|
|
6852
6178
|
method.postTipMessage = jest.fn();
|
|
6853
6179
|
|
|
6854
|
-
await expect(method.run()).rejects.
|
|
6855
|
-
errorCode: HardwareErrorCode.NetworkError,
|
|
6856
|
-
message: 'latest config unavailable',
|
|
6857
|
-
});
|
|
6858
|
-
|
|
6180
|
+
await expect(method.run()).rejects.toThrow('resourceFiles[0].devicePath');
|
|
6859
6181
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
6860
6182
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
6861
6183
|
});
|
|
6862
6184
|
|
|
6863
|
-
test('
|
|
6864
|
-
const method = new FirmwareUpdateV4({
|
|
6865
|
-
id: 1,
|
|
6866
|
-
payload: {
|
|
6867
|
-
method: 'firmwareUpdateV4',
|
|
6868
|
-
platform: 'web',
|
|
6869
|
-
coprocessorBinary: new Uint8Array([1]).buffer,
|
|
6870
|
-
},
|
|
6871
|
-
});
|
|
6872
|
-
method.init();
|
|
6873
|
-
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6874
|
-
|
|
6875
|
-
(method as any).device = stubDevice({
|
|
6876
|
-
originalDescriptor: { protocolType: 'V2' },
|
|
6877
|
-
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
6878
|
-
});
|
|
6879
|
-
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
6880
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
6881
|
-
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
6882
|
-
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
6883
|
-
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
6884
|
-
bootloaderVersion: '1.0.0',
|
|
6885
|
-
bleVersion: '0.0.0',
|
|
6886
|
-
firmwareVersion: '1.0.0',
|
|
6887
|
-
});
|
|
6888
|
-
method.postTipMessage = jest.fn();
|
|
6889
|
-
|
|
6890
|
-
await method.run();
|
|
6891
|
-
|
|
6892
|
-
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
6893
|
-
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
6894
|
-
bootloaderBinary: null,
|
|
6895
|
-
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
6896
|
-
});
|
|
6897
|
-
});
|
|
6898
|
-
|
|
6899
|
-
test('syncs prepared resource sources without sending a firmware install request', async () => {
|
|
6185
|
+
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
6900
6186
|
const method = new FirmwareUpdateV4({
|
|
6901
6187
|
id: 1,
|
|
6902
6188
|
payload: {
|
|
@@ -6913,70 +6199,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6913
6199
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
6914
6200
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
6915
6201
|
|
|
6916
|
-
await (method as any).
|
|
6917
|
-
|
|
6918
|
-
resourceSources: [
|
|
6202
|
+
await (method as any).executeProtocolV2Update({
|
|
6203
|
+
resourceBundles: [
|
|
6919
6204
|
{
|
|
6920
6205
|
name: 'images.okpkg',
|
|
6921
|
-
|
|
6922
|
-
size: 3,
|
|
6923
|
-
readAt: jest.fn(),
|
|
6924
|
-
close: jest.fn(),
|
|
6925
|
-
},
|
|
6206
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
6926
6207
|
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
6927
6208
|
},
|
|
6928
6209
|
],
|
|
6210
|
+
bootloaderBinary: null,
|
|
6211
|
+
fwBinaryMap: [],
|
|
6929
6212
|
});
|
|
6930
6213
|
|
|
6931
6214
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
6932
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledWith(
|
|
6933
|
-
expect.objectContaining({ filePath: 'vol0:/resource/images/images.okpkg' })
|
|
6934
|
-
);
|
|
6935
|
-
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledWith(
|
|
6936
|
-
'vol0:/resource/images/images.okpkg',
|
|
6937
|
-
3
|
|
6938
|
-
);
|
|
6939
6215
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
6940
6216
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
6941
6217
|
});
|
|
6942
6218
|
|
|
6943
|
-
test('writes the mounted boot resource package through its staging path', async () => {
|
|
6944
|
-
const method = new FirmwareUpdateV4({
|
|
6945
|
-
id: 1,
|
|
6946
|
-
payload: {
|
|
6947
|
-
method: 'firmwareUpdateV4',
|
|
6948
|
-
},
|
|
6949
|
-
});
|
|
6950
|
-
|
|
6951
|
-
method.postTipMessage = jest.fn();
|
|
6952
|
-
method.postProgressMessage = jest.fn();
|
|
6953
|
-
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
6954
|
-
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
6955
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
6956
|
-
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
6957
|
-
|
|
6958
|
-
await (method as any).executeProtocolV2SourceUpdate({
|
|
6959
|
-
installSources: [],
|
|
6960
|
-
resourceSources: [
|
|
6961
|
-
{
|
|
6962
|
-
name: 'boot_resource.okpkg',
|
|
6963
|
-
source: {
|
|
6964
|
-
size: 3,
|
|
6965
|
-
readAt: jest.fn(),
|
|
6966
|
-
close: jest.fn(),
|
|
6967
|
-
},
|
|
6968
|
-
devicePath: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
6969
|
-
},
|
|
6970
|
-
],
|
|
6971
|
-
});
|
|
6972
|
-
|
|
6973
|
-
const stagingPath = 'vol0:/loaders/bootloader/boot_resource.okpkg.staging';
|
|
6974
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledWith(
|
|
6975
|
-
expect.objectContaining({ filePath: stagingPath })
|
|
6976
|
-
);
|
|
6977
|
-
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledWith(stagingPath, 3);
|
|
6978
|
-
});
|
|
6979
|
-
|
|
6980
6219
|
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
6981
6220
|
const method = new FirmwareUpdateV4({
|
|
6982
6221
|
id: 1,
|
|
@@ -7680,6 +6919,64 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
7680
6919
|
});
|
|
7681
6920
|
|
|
7682
6921
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6922
|
+
const createResourceFilesystemTypedCall = (
|
|
6923
|
+
stable: Array<{ type: string; size: number; headerHash: string }>,
|
|
6924
|
+
missingTypes: string[] = []
|
|
6925
|
+
) => {
|
|
6926
|
+
const missing = new Set(missingTypes);
|
|
6927
|
+
const resourceByPath = new Map(
|
|
6928
|
+
stable.map(resource => [
|
|
6929
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
|
|
6930
|
+
resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
|
|
6931
|
+
],
|
|
6932
|
+
resource,
|
|
6933
|
+
])
|
|
6934
|
+
);
|
|
6935
|
+
return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
6936
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
6937
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
6938
|
+
}
|
|
6939
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
6940
|
+
const resource = resourceByPath.get(payload.path);
|
|
6941
|
+
const exists = Boolean(resource && !missing.has(resource.type));
|
|
6942
|
+
return {
|
|
6943
|
+
message: {
|
|
6944
|
+
exist: exists,
|
|
6945
|
+
directory: false,
|
|
6946
|
+
size: exists ? resource?.size : 0,
|
|
6947
|
+
},
|
|
6948
|
+
};
|
|
6949
|
+
}
|
|
6950
|
+
if (requestType === 'FilesystemFileRead') {
|
|
6951
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
6952
|
+
if (!resource || missing.has(resource.type)) throw new Error('missing resource');
|
|
6953
|
+
const header = new Uint8Array(0x52a0);
|
|
6954
|
+
const view = new DataView(header.buffer);
|
|
6955
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
6956
|
+
header[index] = char.charCodeAt(0);
|
|
6957
|
+
});
|
|
6958
|
+
'RESC'.split('').forEach((char, index) => {
|
|
6959
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
6960
|
+
});
|
|
6961
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
6962
|
+
for (let index = 0; index < resource.headerHash.length / 2; index++) {
|
|
6963
|
+
header[0x240 + index] = Number.parseInt(
|
|
6964
|
+
resource.headerHash.slice(index * 2, index * 2 + 2),
|
|
6965
|
+
16
|
|
6966
|
+
);
|
|
6967
|
+
}
|
|
6968
|
+
const offset = Number(payload.file.offset);
|
|
6969
|
+
const chunkLength = Number(payload.chunk_len);
|
|
6970
|
+
return {
|
|
6971
|
+
message: {
|
|
6972
|
+
data: header.slice(offset, offset + chunkLength),
|
|
6973
|
+
},
|
|
6974
|
+
};
|
|
6975
|
+
}
|
|
6976
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6977
|
+
});
|
|
6978
|
+
};
|
|
6979
|
+
|
|
7683
6980
|
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
7684
6981
|
const method = new FirmwareUpdateV4({
|
|
7685
6982
|
id: 1,
|
|
@@ -7746,7 +7043,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
7746
7043
|
);
|
|
7747
7044
|
});
|
|
7748
7045
|
|
|
7749
|
-
test('
|
|
7046
|
+
test('rejects firmware update startup when DeviceInfo has no physical serial', async () => {
|
|
7750
7047
|
const method = new FirmwareUpdateV4({
|
|
7751
7048
|
id: 1,
|
|
7752
7049
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -7756,96 +7053,12 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
7756
7053
|
message: { protocol_version: 1, hw: {} },
|
|
7757
7054
|
});
|
|
7758
7055
|
(method as any).device = stubDevice({
|
|
7759
|
-
originalDescriptor: { path: '' },
|
|
7760
|
-
getConnectId: () => '000000000000',
|
|
7761
7056
|
getCommands: () => ({ typedCall }),
|
|
7762
7057
|
});
|
|
7763
7058
|
|
|
7764
|
-
await expect((method as any).captureProtocolV2PhysicalIdentity()).
|
|
7765
|
-
|
|
7766
|
-
expect((method as any).protocolV2ExpectedPath).toBe('000000000000');
|
|
7767
|
-
});
|
|
7768
|
-
|
|
7769
|
-
test('executes a serial-less Pro2 prepared plan when the live model matches', async () => {
|
|
7770
|
-
const planWithoutDigest = {
|
|
7771
|
-
schemaVersion: 2 as const,
|
|
7772
|
-
executor: 'v4' as const,
|
|
7773
|
-
deviceIdentity: 'unavailable',
|
|
7774
|
-
deviceModel: 'pro2',
|
|
7775
|
-
firmwareType: EFirmwareType.Universal,
|
|
7776
|
-
platform: 'web' as const,
|
|
7777
|
-
targetsToUpdate: ['boot' as const],
|
|
7778
|
-
artifacts: [
|
|
7779
|
-
{
|
|
7780
|
-
artifactId: 'component:boot',
|
|
7781
|
-
role: 'component' as const,
|
|
7782
|
-
target: 'boot' as const,
|
|
7783
|
-
url: 'https://example.com/bootloader.bin',
|
|
7784
|
-
container: 'raw' as const,
|
|
7785
|
-
logicalName: 'bootloader',
|
|
7786
|
-
},
|
|
7787
|
-
],
|
|
7788
|
-
};
|
|
7789
|
-
const preparedPlan = prepareFirmwareUpdatePlan({
|
|
7790
|
-
plan: {
|
|
7791
|
-
...planWithoutDigest,
|
|
7792
|
-
planDigest: digestFirmwareUpdateContract(planWithoutDigest),
|
|
7793
|
-
},
|
|
7794
|
-
leaseRef: 'serial-less-v4-executor-test',
|
|
7795
|
-
artifacts: [
|
|
7796
|
-
{
|
|
7797
|
-
artifactId: 'component:boot',
|
|
7798
|
-
artifact: {
|
|
7799
|
-
artifactRef: 'serial-less-bootloader',
|
|
7800
|
-
size: 1,
|
|
7801
|
-
sha256: 'a'.repeat(64),
|
|
7802
|
-
},
|
|
7803
|
-
},
|
|
7804
|
-
],
|
|
7805
|
-
});
|
|
7806
|
-
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
7807
|
-
preparedPlanDigest: preparedPlan.preparedPlanDigest,
|
|
7808
|
-
artifactReader: {
|
|
7809
|
-
open: jest.fn(),
|
|
7810
|
-
read: jest.fn(),
|
|
7811
|
-
close: jest.fn(),
|
|
7812
|
-
},
|
|
7059
|
+
await expect((method as any).captureProtocolV2PhysicalIdentity()).rejects.toMatchObject({
|
|
7060
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
7813
7061
|
});
|
|
7814
|
-
try {
|
|
7815
|
-
const method = new FirmwareUpdateV4({
|
|
7816
|
-
id: 1,
|
|
7817
|
-
payload: {
|
|
7818
|
-
method: 'firmwareUpdateV4',
|
|
7819
|
-
platform: 'web',
|
|
7820
|
-
preparedPlan,
|
|
7821
|
-
hostBindingGeneration,
|
|
7822
|
-
},
|
|
7823
|
-
});
|
|
7824
|
-
method.init();
|
|
7825
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
7826
|
-
type: 'DeviceInfo',
|
|
7827
|
-
message: { protocol_version: 1, hw: {} },
|
|
7828
|
-
});
|
|
7829
|
-
(method as any).device = stubDevice({
|
|
7830
|
-
originalDescriptor: { protocolType: 'V2', path: 'serial-less-pro2' },
|
|
7831
|
-
features: {
|
|
7832
|
-
deviceType: 'pro2',
|
|
7833
|
-
firmwareVersion: '1.0.0',
|
|
7834
|
-
capabilities: [],
|
|
7835
|
-
},
|
|
7836
|
-
getCommands: () => ({ typedCall }),
|
|
7837
|
-
});
|
|
7838
|
-
(method as any).runProtocolV2PreparedArtifacts = jest
|
|
7839
|
-
.fn()
|
|
7840
|
-
.mockResolvedValue('serial-less-result');
|
|
7841
|
-
|
|
7842
|
-
await expect(method.run()).resolves.toBe('serial-less-result');
|
|
7843
|
-
expect((method as any).runProtocolV2PreparedArtifacts).toHaveBeenCalled();
|
|
7844
|
-
expect((method as any).protocolV2ExpectedSerialNumber).toBeUndefined();
|
|
7845
|
-
expect((method as any).protocolV2ExpectedPath).toBe('serial-less-pro2');
|
|
7846
|
-
} finally {
|
|
7847
|
-
unregisterFirmwareUpdateHostBinding(hostBindingGeneration);
|
|
7848
|
-
}
|
|
7849
7062
|
});
|
|
7850
7063
|
|
|
7851
7064
|
test('uses the BLE read limit when reading an existing firmware bundle header', async () => {
|
|
@@ -7878,94 +7091,292 @@ describe('Protocol V2 firmware reconnect identity', () => {
|
|
|
7878
7091
|
expect(Math.max(...readChunkLengths)).toBe(900);
|
|
7879
7092
|
});
|
|
7880
7093
|
|
|
7881
|
-
test('
|
|
7094
|
+
test('uses filesystem reads instead of ResourceInventoryGet for resource comparison', async () => {
|
|
7882
7095
|
const method = new FirmwareUpdateV4({
|
|
7883
7096
|
id: 1,
|
|
7884
7097
|
payload: {
|
|
7885
7098
|
method: 'firmwareUpdateV4',
|
|
7886
7099
|
platform: 'web',
|
|
7100
|
+
targetsToUpdate: ['resource'],
|
|
7887
7101
|
},
|
|
7888
7102
|
});
|
|
7889
7103
|
method.init();
|
|
7890
|
-
const
|
|
7891
|
-
|
|
7892
|
-
|
|
7104
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
7105
|
+
(type, index) => ({
|
|
7106
|
+
type,
|
|
7107
|
+
url: `https://example.com/${type}.okpkg`,
|
|
7108
|
+
size: 0x52a0 + index + 1,
|
|
7109
|
+
fileHash: 'a'.repeat(64),
|
|
7110
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
7111
|
+
})
|
|
7112
|
+
);
|
|
7113
|
+
const typedCall = createResourceFilesystemTypedCall(stable);
|
|
7114
|
+
(method as any).device = stubDevice({
|
|
7115
|
+
getCommands: () => ({ typedCall }),
|
|
7116
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7117
|
+
});
|
|
7118
|
+
const resourcesSpy = jest
|
|
7119
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
7120
|
+
.mockReturnValue(stable as any);
|
|
7121
|
+
(method as any).downloadProtocolV2Resource = jest.fn();
|
|
7122
|
+
|
|
7123
|
+
await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
|
|
7124
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
7125
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
7126
|
+
expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
|
|
7127
|
+
resourcesSpy.mockRestore();
|
|
7128
|
+
});
|
|
7129
|
+
|
|
7130
|
+
test('downloads only changed resources from loader inventory', async () => {
|
|
7131
|
+
const method = new FirmwareUpdateV4({
|
|
7132
|
+
id: 1,
|
|
7133
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
7134
|
+
});
|
|
7135
|
+
method.init();
|
|
7136
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
7137
|
+
(type, index) => ({
|
|
7138
|
+
type,
|
|
7139
|
+
url: `https://example.com/${type}.okpkg`,
|
|
7140
|
+
size: 0x52a0 + index + 1,
|
|
7141
|
+
fileHash: 'a'.repeat(64),
|
|
7142
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
7143
|
+
})
|
|
7144
|
+
);
|
|
7145
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
7146
|
+
(method as any).device = stubDevice({
|
|
7147
|
+
getCommands: () => ({ typedCall }),
|
|
7148
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7149
|
+
});
|
|
7150
|
+
const resourcesSpy = jest
|
|
7151
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
7152
|
+
.mockReturnValue(stable as any);
|
|
7153
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
7154
|
+
Promise.resolve({
|
|
7155
|
+
name: `${resource.type}.okpkg`,
|
|
7156
|
+
binary: new ArrayBuffer(resource.size),
|
|
7157
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
7158
|
+
})
|
|
7159
|
+
);
|
|
7160
|
+
|
|
7161
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
7162
|
+
|
|
7163
|
+
expect(bundles).toHaveLength(1);
|
|
7164
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
7165
|
+
resourcesSpy.mockRestore();
|
|
7166
|
+
});
|
|
7167
|
+
|
|
7168
|
+
test('uses filesystem inventory for incremental Bootloader recovery', async () => {
|
|
7169
|
+
const method = new FirmwareUpdateV4({
|
|
7170
|
+
id: 1,
|
|
7171
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
7172
|
+
});
|
|
7173
|
+
method.init();
|
|
7174
|
+
const stable = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
|
|
7175
|
+
(type, index) => ({
|
|
7176
|
+
type,
|
|
7177
|
+
url: `https://example.com/${type}.okpkg`,
|
|
7178
|
+
size: 0x52a0 + index + 1,
|
|
7179
|
+
fileHash: 'a'.repeat(64),
|
|
7180
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
7181
|
+
})
|
|
7182
|
+
);
|
|
7183
|
+
const typedCall = createResourceFilesystemTypedCall(stable, ['images']);
|
|
7184
|
+
(method as any).device = stubDevice({
|
|
7185
|
+
getCommands: () => ({ typedCall }),
|
|
7186
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7187
|
+
});
|
|
7188
|
+
const resourcesSpy = jest
|
|
7189
|
+
.spyOn(DataManager, 'getProtocolV2Resources')
|
|
7190
|
+
.mockReturnValue(stable as any);
|
|
7191
|
+
(method as any).downloadProtocolV2Resource = jest.fn(resource =>
|
|
7192
|
+
Promise.resolve({
|
|
7193
|
+
name: `${resource.type}.okpkg`,
|
|
7194
|
+
binary: new ArrayBuffer(resource.size),
|
|
7195
|
+
devicePath: `vol0:/${resource.type}.okpkg`,
|
|
7196
|
+
})
|
|
7197
|
+
);
|
|
7198
|
+
|
|
7199
|
+
const bundles = await (method as any).prepareProtocolV2ResourceBundles();
|
|
7200
|
+
|
|
7201
|
+
expect(bundles).toHaveLength(1);
|
|
7202
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
7203
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemPathInfoQuery')).toBe(true);
|
|
7204
|
+
expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
|
|
7205
|
+
resourcesSpy.mockRestore();
|
|
7206
|
+
});
|
|
7207
|
+
|
|
7208
|
+
test('does not resolve boot resources when no resource target is selected', async () => {
|
|
7209
|
+
const method = new FirmwareUpdateV4({
|
|
7210
|
+
id: 1,
|
|
7211
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web' },
|
|
7212
|
+
});
|
|
7213
|
+
method.init();
|
|
7214
|
+
const configSpy = jest.spyOn(DataManager, 'getProtocolV2BootResources');
|
|
7215
|
+
const downloadSpy = jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary');
|
|
7216
|
+
|
|
7217
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toBeUndefined();
|
|
7218
|
+
|
|
7219
|
+
expect(configSpy).not.toHaveBeenCalled();
|
|
7220
|
+
expect(downloadSpy).not.toHaveBeenCalled();
|
|
7221
|
+
});
|
|
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') {
|
|
7893
7273
|
return Promise.resolve({
|
|
7894
|
-
message: { exist: true, directory: false, size:
|
|
7274
|
+
message: { exist: true, directory: false, size: bytes.byteLength },
|
|
7895
7275
|
});
|
|
7896
7276
|
}
|
|
7897
|
-
if (
|
|
7277
|
+
if (name === 'FilesystemFileRead') {
|
|
7278
|
+
const offset = Number(payload.file.offset);
|
|
7898
7279
|
return Promise.resolve({
|
|
7899
|
-
message: {
|
|
7900
|
-
data: installedBinary.slice(
|
|
7901
|
-
Number(request.file.offset),
|
|
7902
|
-
Number(request.file.offset) + Number(request.chunk_len)
|
|
7903
|
-
),
|
|
7904
|
-
},
|
|
7280
|
+
message: { data: bytes.slice(offset, offset + Number(payload.chunk_len)) },
|
|
7905
7281
|
});
|
|
7906
7282
|
}
|
|
7907
|
-
|
|
7283
|
+
return Promise.reject(new Error(`Unexpected request: ${name}`));
|
|
7908
7284
|
});
|
|
7285
|
+
const method = new FirmwareUpdateV4({
|
|
7286
|
+
id: 1,
|
|
7287
|
+
payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
|
|
7288
|
+
});
|
|
7289
|
+
method.init();
|
|
7909
7290
|
(method as any).device = stubDevice({
|
|
7291
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7910
7292
|
getCommands: () => ({ typedCall }),
|
|
7911
7293
|
});
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
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
|
+
});
|
|
7915
7304
|
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
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
|
+
|
|
7320
|
+
test('includes startup resources in the complete resource target', async () => {
|
|
7321
|
+
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
7322
|
+
const binary = bytes.buffer as ArrayBuffer;
|
|
7323
|
+
const fileHash = Array.from(sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
7324
|
+
const resource = {
|
|
7325
|
+
required: false as const,
|
|
7326
|
+
target: 'RES' as const,
|
|
7327
|
+
files: [
|
|
7919
7328
|
{
|
|
7920
|
-
name: '
|
|
7921
|
-
|
|
7922
|
-
devicePath: 'vol0:/
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
headerHash: '22'.repeat(64),
|
|
7329
|
+
name: 'bootloader_crest.bin',
|
|
7330
|
+
url: 'https://example.com/bootloader_crest.bin',
|
|
7331
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
7332
|
+
size: bytes.byteLength,
|
|
7333
|
+
fileHash,
|
|
7926
7334
|
},
|
|
7927
7335
|
],
|
|
7336
|
+
};
|
|
7337
|
+
const method = new FirmwareUpdateV4({
|
|
7338
|
+
id: 1,
|
|
7339
|
+
payload: {
|
|
7340
|
+
method: 'firmwareUpdateV4',
|
|
7341
|
+
platform: 'web',
|
|
7342
|
+
targetsToUpdate: ['resource'],
|
|
7343
|
+
},
|
|
7928
7344
|
});
|
|
7345
|
+
method.init();
|
|
7346
|
+
(method as any).device = stubDevice({ getCurrentDeviceType: () => 'pro2' });
|
|
7347
|
+
jest.spyOn(DataManager, 'getProtocolV2BootResources').mockReturnValue(resource);
|
|
7348
|
+
jest.spyOn(firmwareBinaryApi, 'getSysResourceBinary').mockResolvedValue({ binary });
|
|
7929
7349
|
|
|
7930
|
-
expect(
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7350
|
+
await expect((method as any).prepareProtocolV2BootResources()).resolves.toEqual([
|
|
7351
|
+
{
|
|
7352
|
+
name: 'bootloader_crest.bin',
|
|
7353
|
+
binary,
|
|
7354
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
7355
|
+
},
|
|
7356
|
+
]);
|
|
7934
7357
|
});
|
|
7935
7358
|
|
|
7936
|
-
test('
|
|
7359
|
+
test('rejects a manually supplied resource file when its manifest hash does not match', () => {
|
|
7937
7360
|
const method = new FirmwareUpdateV4({
|
|
7938
7361
|
id: 1,
|
|
7939
7362
|
payload: {
|
|
7940
7363
|
method: 'firmwareUpdateV4',
|
|
7941
7364
|
platform: 'web',
|
|
7365
|
+
resourceFiles: [
|
|
7366
|
+
{
|
|
7367
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
7368
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
7369
|
+
size: 3,
|
|
7370
|
+
fileHash: '00'.repeat(32),
|
|
7371
|
+
},
|
|
7372
|
+
],
|
|
7942
7373
|
},
|
|
7943
7374
|
});
|
|
7944
7375
|
method.init();
|
|
7945
|
-
const installedBinary = new Uint8Array(createProtocolV2OkppBinary());
|
|
7946
|
-
const typedCall = jest.fn((requestType: string) => {
|
|
7947
|
-
if (requestType === 'FilesystemPathInfoQuery') {
|
|
7948
|
-
return Promise.resolve({
|
|
7949
|
-
message: { exist: true, directory: false, size: installedBinary.byteLength + 1 },
|
|
7950
|
-
});
|
|
7951
|
-
}
|
|
7952
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
7953
|
-
});
|
|
7954
|
-
(method as any).device = stubDevice({
|
|
7955
|
-
getCommands: () => ({ typedCall }),
|
|
7956
|
-
});
|
|
7957
7376
|
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
source: { size: installedBinary.byteLength },
|
|
7962
|
-
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
7963
|
-
version: [1, 2, 3],
|
|
7964
|
-
payloadHash: '11'.repeat(64),
|
|
7965
|
-
headerHash: '22'.repeat(64),
|
|
7966
|
-
})
|
|
7967
|
-
).resolves.toBe(false);
|
|
7968
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
7377
|
+
expect(() => (method as any).prepareExplicitProtocolV2ResourceFiles()).toThrow(
|
|
7378
|
+
'SHA-256 mismatch'
|
|
7379
|
+
);
|
|
7969
7380
|
});
|
|
7970
7381
|
});
|
|
7971
7382
|
|