@onekeyfe/hd-core 1.2.0-alpha.107 → 1.2.0-alpha.109
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__/DeviceCommands.test.ts +140 -26
- package/__tests__/base64Data.test.ts +70 -0
- package/__tests__/device-lifecycle-events.test.ts +113 -2
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-state-mapper.test.ts +4 -4
- package/__tests__/device-utils.test.ts +1 -1
- package/__tests__/deviceSettings.test.ts +0 -1
- package/__tests__/deviceUploadNft.test.ts +33 -4
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +125 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/protocol-v2-resources.test.ts +8 -0
- package/__tests__/protocol-v2.test.ts +821 -227
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/FirmwareUpdateV4.d.ts +7 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +7 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +16 -34
- package/dist/index.js +506 -329
- package/dist/protocols/protocol-v2/features.d.ts +13 -5
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/settings.d.ts +4 -19
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/FirmwareUpdateV4.ts +307 -245
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +6 -2
- package/src/data/messages/messages-protocol-v2.json +31 -14
- package/src/device/Device.ts +53 -31
- package/src/device/DeviceCommands.ts +4 -14
- package/src/device/DevicePool.ts +6 -2
- package/src/device/DeviceStateMapper.ts +15 -15
- package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
- package/src/events/logBlockEvent.ts +14 -1
- package/src/protocols/protocol-v2/features.ts +22 -5
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +9 -46
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/settings.ts +4 -19
- package/src/utils/deviceSettings.ts +1 -1
- package/src/utils/pro2Nft.ts +31 -8
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
4
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
5
|
import JSZip from 'jszip';
|
|
6
|
+
import { encode as encodeJpeg } from 'jpeg-js';
|
|
5
7
|
import {
|
|
6
8
|
DeviceRebootType,
|
|
7
9
|
DeviceSessionPinType,
|
|
@@ -123,6 +125,18 @@ jest.mock('../src/data/config', () => ({
|
|
|
123
125
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
124
126
|
}));
|
|
125
127
|
|
|
128
|
+
const jpegBase64Cache = new Map<string, string>();
|
|
129
|
+
|
|
130
|
+
const createJpegBase64 = (width: number, height: number) => {
|
|
131
|
+
const key = `${width}x${height}`;
|
|
132
|
+
const cached = jpegBase64Cache.get(key);
|
|
133
|
+
if (cached) return cached;
|
|
134
|
+
const rgba = new Uint8Array(width * height * 4).fill(0xff);
|
|
135
|
+
const value = encodeJpeg({ width, height, data: rgba }, 80).data.toString('base64');
|
|
136
|
+
jpegBase64Cache.set(key, value);
|
|
137
|
+
return value;
|
|
138
|
+
};
|
|
139
|
+
|
|
126
140
|
const createProtocolV2OkppBinary = ({
|
|
127
141
|
version = [1, 2, 3],
|
|
128
142
|
payloadHashByte = 0x11,
|
|
@@ -160,8 +174,18 @@ const createWalletSessionTypedCall = (
|
|
|
160
174
|
});
|
|
161
175
|
|
|
162
176
|
describe('DeviceUploadWallpaper', () => {
|
|
177
|
+
const wallpaperProtocolInfo = {
|
|
178
|
+
version: 2,
|
|
179
|
+
supported_messages: [60412, 60805, 60809],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const stubWallpaperDevice = <T extends Record<string, any>>(device: T) =>
|
|
183
|
+
stubDevice({
|
|
184
|
+
...device,
|
|
185
|
+
ensureProtocolV2RuntimeContext: jest.fn().mockResolvedValue(wallpaperProtocolInfo),
|
|
186
|
+
});
|
|
187
|
+
|
|
163
188
|
test('encodes, uploads and applies a Pro2 wallpaper', async () => {
|
|
164
|
-
const rgba = new Uint8Array(604 * 1024 * 4).fill(255);
|
|
165
189
|
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
166
190
|
if (request === 'FilesystemDirMake') return { message: { message: 'directory ready' } };
|
|
167
191
|
if (request === 'FilesystemFileWrite') {
|
|
@@ -175,9 +199,12 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
175
199
|
});
|
|
176
200
|
const method = new DeviceUploadWallpaper({
|
|
177
201
|
id: 1,
|
|
178
|
-
payload: {
|
|
202
|
+
payload: {
|
|
203
|
+
method: 'deviceUploadWallpaper',
|
|
204
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
205
|
+
},
|
|
179
206
|
});
|
|
180
|
-
(method as any).device =
|
|
207
|
+
(method as any).device = stubWallpaperDevice({ commands: { typedCall } });
|
|
181
208
|
method.postMessage = jest.fn();
|
|
182
209
|
|
|
183
210
|
method.init();
|
|
@@ -225,12 +252,10 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
225
252
|
id: 1,
|
|
226
253
|
payload: {
|
|
227
254
|
method: 'deviceUploadWallpaper',
|
|
228
|
-
|
|
229
|
-
height: 1024,
|
|
230
|
-
rgba: new Uint8Array(604 * 1024 * 4),
|
|
255
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
231
256
|
},
|
|
232
257
|
});
|
|
233
|
-
(method as any).device =
|
|
258
|
+
(method as any).device = stubWallpaperDevice({ commands: { typedCall } });
|
|
234
259
|
method.init();
|
|
235
260
|
|
|
236
261
|
await expect(method.run()).rejects.toThrow('write failed');
|
|
@@ -242,15 +267,37 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
242
267
|
id: 1,
|
|
243
268
|
payload: {
|
|
244
269
|
method: 'deviceUploadWallpaper',
|
|
245
|
-
|
|
246
|
-
height: 1024,
|
|
247
|
-
rgba: new Uint8Array(604 * 1024 * 4),
|
|
270
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
248
271
|
fileName: '../wallpaper.bin',
|
|
249
272
|
},
|
|
250
273
|
});
|
|
251
274
|
|
|
252
275
|
expect(() => method.init()).toThrow('fileName');
|
|
253
276
|
});
|
|
277
|
+
|
|
278
|
+
test('rejects unsupported firmware before creating or writing files', async () => {
|
|
279
|
+
const typedCall = jest.fn();
|
|
280
|
+
const method = new DeviceUploadWallpaper({
|
|
281
|
+
id: 1,
|
|
282
|
+
payload: {
|
|
283
|
+
method: 'deviceUploadWallpaper',
|
|
284
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
(method as any).device = stubDevice({
|
|
288
|
+
commands: { typedCall },
|
|
289
|
+
ensureProtocolV2RuntimeContext: jest.fn().mockResolvedValue({
|
|
290
|
+
version: 2,
|
|
291
|
+
supported_messages: [60805, 60809],
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
method.init();
|
|
295
|
+
|
|
296
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
297
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
298
|
+
});
|
|
299
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
254
301
|
});
|
|
255
302
|
|
|
256
303
|
describe('UploadPortfolio', () => {
|
|
@@ -266,7 +313,6 @@ describe('UploadPortfolio', () => {
|
|
|
266
313
|
});
|
|
267
314
|
|
|
268
315
|
test('writes and applies the portfolio while the device is locked without unlocking', async () => {
|
|
269
|
-
const packageBytes = new Uint8Array([1, 2, 3]);
|
|
270
316
|
const typedCall = jest
|
|
271
317
|
.fn()
|
|
272
318
|
.mockResolvedValueOnce({ message: { processed_byte: 3 } })
|
|
@@ -282,7 +328,7 @@ describe('UploadPortfolio', () => {
|
|
|
282
328
|
id: 1,
|
|
283
329
|
payload: {
|
|
284
330
|
method: 'uploadPortfolio',
|
|
285
|
-
|
|
331
|
+
packageBase64: 'AQID',
|
|
286
332
|
},
|
|
287
333
|
});
|
|
288
334
|
(method as any).device = device;
|
|
@@ -314,7 +360,7 @@ describe('UploadPortfolio', () => {
|
|
|
314
360
|
id: 1,
|
|
315
361
|
payload: {
|
|
316
362
|
method: 'uploadPortfolio',
|
|
317
|
-
|
|
363
|
+
packageBase64: 'AQID',
|
|
318
364
|
},
|
|
319
365
|
});
|
|
320
366
|
(method as any).device = stubPortfolioDevice({ commands: { typedCall } });
|
|
@@ -362,7 +408,7 @@ describe('UploadPortfolio', () => {
|
|
|
362
408
|
id: 1,
|
|
363
409
|
payload: {
|
|
364
410
|
method: 'uploadPortfolio',
|
|
365
|
-
|
|
411
|
+
packageBase64: 'AQ==',
|
|
366
412
|
},
|
|
367
413
|
});
|
|
368
414
|
(method as any).device = stubPortfolioDevice({ commands: { typedCall } });
|
|
@@ -384,7 +430,7 @@ describe('UploadPortfolio', () => {
|
|
|
384
430
|
id: 1,
|
|
385
431
|
payload: {
|
|
386
432
|
method: 'uploadPortfolio',
|
|
387
|
-
packageBytes,
|
|
433
|
+
packageBase64: Buffer.from(packageBytes).toString('base64'),
|
|
388
434
|
},
|
|
389
435
|
});
|
|
390
436
|
method.abortSignal = abortController.signal;
|
|
@@ -405,7 +451,7 @@ describe('UploadPortfolio', () => {
|
|
|
405
451
|
id: 1,
|
|
406
452
|
payload: {
|
|
407
453
|
method: 'uploadPortfolio',
|
|
408
|
-
|
|
454
|
+
packageBase64: 'AQ==',
|
|
409
455
|
},
|
|
410
456
|
});
|
|
411
457
|
(method as any).device = stubDevice({
|
|
@@ -423,6 +469,18 @@ describe('UploadPortfolio', () => {
|
|
|
423
469
|
});
|
|
424
470
|
expect(typedCall).not.toHaveBeenCalled();
|
|
425
471
|
});
|
|
472
|
+
|
|
473
|
+
test('rejects non-canonical Base64 before device communication', () => {
|
|
474
|
+
const method = new UploadPortfolio({
|
|
475
|
+
id: 1,
|
|
476
|
+
payload: {
|
|
477
|
+
method: 'uploadPortfolio',
|
|
478
|
+
packageBase64: 'data:application/octet-stream;base64,AQID',
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
expect(() => method.init()).toThrow('canonical Base64');
|
|
483
|
+
});
|
|
426
484
|
});
|
|
427
485
|
|
|
428
486
|
const descriptor = {
|
|
@@ -555,7 +613,7 @@ const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
|
|
|
555
613
|
hw: {
|
|
556
614
|
serial_no: 'PR9999999999',
|
|
557
615
|
},
|
|
558
|
-
|
|
616
|
+
main_mcu: {
|
|
559
617
|
bootloader: {
|
|
560
618
|
version: '0.0.1',
|
|
561
619
|
},
|
|
@@ -619,7 +677,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
619
677
|
const deviceInfo: ProtocolV2DeviceInfo = {
|
|
620
678
|
protocol_version: 1,
|
|
621
679
|
hw: { serial_no: 'P2-STATIC' },
|
|
622
|
-
|
|
680
|
+
main_mcu: { application: { version: '1.0.0' } },
|
|
623
681
|
coprocessor: { bt_adv_name: 'Pro2 STATIC' },
|
|
624
682
|
};
|
|
625
683
|
const deviceStatus: DeviceStatus = {
|
|
@@ -693,7 +751,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
693
751
|
Device_type: DeviceType.PRO2,
|
|
694
752
|
serial_no: 'PR2SERIAL',
|
|
695
753
|
},
|
|
696
|
-
|
|
754
|
+
main_mcu: {
|
|
697
755
|
romloader: {
|
|
698
756
|
version: '0.1.0',
|
|
699
757
|
build_id: 'rom-build',
|
|
@@ -783,7 +841,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
783
841
|
hw: {
|
|
784
842
|
serial_no: 'PR2BOOT',
|
|
785
843
|
},
|
|
786
|
-
|
|
844
|
+
main_mcu: {
|
|
787
845
|
bootloader: {
|
|
788
846
|
version: '0.2.0',
|
|
789
847
|
},
|
|
@@ -805,7 +863,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
805
863
|
const features = normalizeProtocolV2Features(descriptor as any, {
|
|
806
864
|
protocol_version: 1,
|
|
807
865
|
hw: { serial_no: 'PR2NORMAL' },
|
|
808
|
-
|
|
866
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
809
867
|
});
|
|
810
868
|
|
|
811
869
|
expect(features.mode).toBe('normal');
|
|
@@ -818,7 +876,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
818
876
|
hw: {
|
|
819
877
|
serial_no: 'PR2ROM',
|
|
820
878
|
},
|
|
821
|
-
|
|
879
|
+
main_mcu: {
|
|
822
880
|
romloader: {
|
|
823
881
|
version: '1.0.0',
|
|
824
882
|
},
|
|
@@ -849,6 +907,34 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
849
907
|
).toBeUndefined();
|
|
850
908
|
});
|
|
851
909
|
|
|
910
|
+
test('infers legacy Protocol V2 loader mode from firmware image metadata', () => {
|
|
911
|
+
const legacyProtocolInfo = {
|
|
912
|
+
version: 1,
|
|
913
|
+
build_fingerprint: '',
|
|
914
|
+
supported_messages: [],
|
|
915
|
+
protobuf_definition: null,
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
expect(
|
|
919
|
+
getProtocolV2RuntimeMode(legacyProtocolInfo, {
|
|
920
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
921
|
+
})
|
|
922
|
+
).toBe('bootloader');
|
|
923
|
+
expect(
|
|
924
|
+
getProtocolV2RuntimeMode(legacyProtocolInfo, {
|
|
925
|
+
main_mcu: { romloader: { version: '1.0.0' } },
|
|
926
|
+
})
|
|
927
|
+
).toBe('romloader');
|
|
928
|
+
expect(
|
|
929
|
+
getProtocolV2RuntimeMode(legacyProtocolInfo, {
|
|
930
|
+
main_mcu: {
|
|
931
|
+
application: { version: '1.2.3' },
|
|
932
|
+
bootloader: { version: '0.2.0' },
|
|
933
|
+
},
|
|
934
|
+
})
|
|
935
|
+
).toBeUndefined();
|
|
936
|
+
});
|
|
937
|
+
|
|
852
938
|
test('keeps the Protocol V2 main wallet on the default empty-passphrase context', async () => {
|
|
853
939
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
854
940
|
features.firmwareVersion = '1.2.3';
|
|
@@ -1912,7 +1998,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1912
1998
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1913
1999
|
{
|
|
1914
2000
|
protocol_version: 2,
|
|
1915
|
-
|
|
2001
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
1916
2002
|
se1: { application: { version: '4.5.6' } },
|
|
1917
2003
|
status: { unlocked: false, passphrase_enabled: false },
|
|
1918
2004
|
}
|
|
@@ -1949,7 +2035,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1949
2035
|
deviceInfo: {
|
|
1950
2036
|
protocol_version: 1,
|
|
1951
2037
|
hw: { serial_no: 'PR9999999999' },
|
|
1952
|
-
|
|
2038
|
+
main_mcu: { application: { version: '1.0.0' } },
|
|
1953
2039
|
},
|
|
1954
2040
|
deviceStatus: {
|
|
1955
2041
|
device_id: 'firmware-reconnect-id',
|
|
@@ -2314,7 +2400,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2314
2400
|
(device as any).features = {
|
|
2315
2401
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2316
2402
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2317
|
-
|
|
2403
|
+
main_mcu: { application: { version: '4.15.0' } },
|
|
2318
2404
|
status: { passphrase_enabled: true },
|
|
2319
2405
|
}),
|
|
2320
2406
|
unlocked: true,
|
|
@@ -2697,19 +2783,35 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2697
2783
|
});
|
|
2698
2784
|
});
|
|
2699
2785
|
|
|
2700
|
-
test('syncs Protocol V2 cached features
|
|
2786
|
+
test('syncs Protocol V2 cached features and renegotiates the adopted command channel', async () => {
|
|
2787
|
+
const typedCall = jest.fn().mockResolvedValue({ message: protocolV2ApplicationInfo });
|
|
2701
2788
|
const cached = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2702
2789
|
(cached as any).features = {
|
|
2703
2790
|
...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
|
|
2704
2791
|
deviceId: null,
|
|
2705
2792
|
serialNo: 'CACHED-SERIAL',
|
|
2706
2793
|
};
|
|
2794
|
+
(cached as any).commands = { typedCall };
|
|
2707
2795
|
|
|
2708
2796
|
const current = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
2797
|
+
current.updateState(
|
|
2798
|
+
{
|
|
2799
|
+
raw: { protocolV2ProtocolInfo: protocolV2BootloaderInfo },
|
|
2800
|
+
status: { mode: 'bootloader' },
|
|
2801
|
+
},
|
|
2802
|
+
'initialize'
|
|
2803
|
+
);
|
|
2709
2804
|
current.updateFromCache(cached);
|
|
2710
2805
|
|
|
2806
|
+
await expect(current.ensureProtocolV2RuntimeContext()).resolves.toEqual(
|
|
2807
|
+
protocolV2ApplicationInfo
|
|
2808
|
+
);
|
|
2809
|
+
|
|
2711
2810
|
expect(current.getCurrentDeviceId()).toBeUndefined();
|
|
2712
2811
|
expect(current.getCurrentSerialNo()).toBe('CACHED-SERIAL');
|
|
2812
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2813
|
+
eventless_wallet_session: true,
|
|
2814
|
+
});
|
|
2713
2815
|
expect(current.toMessageObject()).toMatchObject({
|
|
2714
2816
|
serialNo: 'CACHED-SERIAL',
|
|
2715
2817
|
uuid: 'CACHED-SERIAL',
|
|
@@ -2723,7 +2825,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2723
2825
|
type: 'DeviceInfo',
|
|
2724
2826
|
message: {
|
|
2725
2827
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
2726
|
-
|
|
2828
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
2727
2829
|
},
|
|
2728
2830
|
}),
|
|
2729
2831
|
};
|
|
@@ -2744,7 +2846,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2744
2846
|
{
|
|
2745
2847
|
targets: {
|
|
2746
2848
|
hw: true,
|
|
2747
|
-
|
|
2849
|
+
main_mcu: true,
|
|
2748
2850
|
coprocessor: true,
|
|
2749
2851
|
},
|
|
2750
2852
|
types: {
|
|
@@ -2853,7 +2955,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2853
2955
|
const device = stubDevice({
|
|
2854
2956
|
originalDescriptor: { protocolType: 'V2' },
|
|
2855
2957
|
features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
|
|
2856
|
-
|
|
2958
|
+
main_mcu: {
|
|
2857
2959
|
application: {
|
|
2858
2960
|
version: '4.14.0',
|
|
2859
2961
|
},
|
|
@@ -2907,7 +3009,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2907
3009
|
type: 'DeviceInfo',
|
|
2908
3010
|
message: {
|
|
2909
3011
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
2910
|
-
|
|
3012
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
2911
3013
|
},
|
|
2912
3014
|
})
|
|
2913
3015
|
.mockResolvedValueOnce({
|
|
@@ -2942,7 +3044,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2942
3044
|
{
|
|
2943
3045
|
targets: {
|
|
2944
3046
|
hw: true,
|
|
2945
|
-
|
|
3047
|
+
main_mcu: true,
|
|
2946
3048
|
coprocessor: true,
|
|
2947
3049
|
},
|
|
2948
3050
|
types: {
|
|
@@ -2971,7 +3073,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2971
3073
|
type: 'DeviceInfo',
|
|
2972
3074
|
message: {
|
|
2973
3075
|
hw: { serial_no: 'PR2SERIAL' },
|
|
2974
|
-
|
|
3076
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
2975
3077
|
se1: {},
|
|
2976
3078
|
},
|
|
2977
3079
|
})
|
|
@@ -3021,7 +3123,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3021
3123
|
|
|
3022
3124
|
await device.probeProtocolV2RuntimeState({
|
|
3023
3125
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3024
|
-
|
|
3126
|
+
main_mcu: { romloader: { version: '1.0.0' } },
|
|
3025
3127
|
});
|
|
3026
3128
|
|
|
3027
3129
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
@@ -3049,7 +3151,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3049
3151
|
await expect(
|
|
3050
3152
|
device.probeProtocolV2RuntimeState({
|
|
3051
3153
|
hw: { Device_type: DeviceType.PRO, serial_no: 'PRO-SERIAL' },
|
|
3052
|
-
|
|
3154
|
+
main_mcu: { romloader: { version: '1.0.0' } },
|
|
3053
3155
|
})
|
|
3054
3156
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceInitializeFailed });
|
|
3055
3157
|
|
|
@@ -3074,7 +3176,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3074
3176
|
|
|
3075
3177
|
await device.probeProtocolV2RuntimeState({
|
|
3076
3178
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3077
|
-
|
|
3179
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3078
3180
|
});
|
|
3079
3181
|
|
|
3080
3182
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
@@ -3106,7 +3208,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3106
3208
|
|
|
3107
3209
|
await device.probeProtocolV2RuntimeState({
|
|
3108
3210
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3109
|
-
|
|
3211
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3110
3212
|
});
|
|
3111
3213
|
|
|
3112
3214
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
@@ -3134,7 +3236,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3134
3236
|
|
|
3135
3237
|
await device.probeProtocolV2RuntimeState({
|
|
3136
3238
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3137
|
-
|
|
3239
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
3138
3240
|
});
|
|
3139
3241
|
|
|
3140
3242
|
expect(device.features).toMatchObject({
|
|
@@ -3163,7 +3265,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3163
3265
|
await expect(
|
|
3164
3266
|
device.probeProtocolV2RuntimeState({
|
|
3165
3267
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3166
|
-
|
|
3268
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3167
3269
|
})
|
|
3168
3270
|
).rejects.toBe(linkError);
|
|
3169
3271
|
|
|
@@ -3188,12 +3290,165 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3188
3290
|
await expect(
|
|
3189
3291
|
device.probeProtocolV2RuntimeState({
|
|
3190
3292
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3191
|
-
|
|
3293
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3192
3294
|
})
|
|
3193
3295
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceInitializeFailed });
|
|
3194
3296
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
3195
3297
|
});
|
|
3196
3298
|
|
|
3299
|
+
test('initializes a legacy Protocol V2 bootloader without compatibility options', async () => {
|
|
3300
|
+
const device = Device.fromDescriptor({
|
|
3301
|
+
path: 'usb-path',
|
|
3302
|
+
protocolType: 'V2',
|
|
3303
|
+
} as any);
|
|
3304
|
+
const typedCall = jest
|
|
3305
|
+
.fn()
|
|
3306
|
+
.mockResolvedValueOnce({
|
|
3307
|
+
type: 'DeviceInfo',
|
|
3308
|
+
message: {
|
|
3309
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3310
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
3311
|
+
},
|
|
3312
|
+
})
|
|
3313
|
+
.mockResolvedValueOnce({
|
|
3314
|
+
type: 'ProtocolInfo',
|
|
3315
|
+
message: {
|
|
3316
|
+
version: 1,
|
|
3317
|
+
build_fingerprint: '',
|
|
3318
|
+
supported_messages: [],
|
|
3319
|
+
protobuf_definition: null,
|
|
3320
|
+
},
|
|
3321
|
+
});
|
|
3322
|
+
(device as any).commands = { typedCall };
|
|
3323
|
+
|
|
3324
|
+
await device.initialize();
|
|
3325
|
+
|
|
3326
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
3327
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
3328
|
+
expect(device.features).toMatchObject({
|
|
3329
|
+
mode: 'bootloader',
|
|
3330
|
+
bootloaderMode: true,
|
|
3331
|
+
initialized: null,
|
|
3332
|
+
});
|
|
3333
|
+
});
|
|
3334
|
+
|
|
3335
|
+
test('uses DeviceStatusGet to identify App mode for legacy ProtocolInfo', async () => {
|
|
3336
|
+
const device = Device.fromDescriptor({
|
|
3337
|
+
path: 'usb-path',
|
|
3338
|
+
protocolType: 'V2',
|
|
3339
|
+
} as any);
|
|
3340
|
+
const typedCall = jest
|
|
3341
|
+
.fn()
|
|
3342
|
+
.mockResolvedValueOnce({
|
|
3343
|
+
type: 'ProtocolInfo',
|
|
3344
|
+
message: {
|
|
3345
|
+
version: 1,
|
|
3346
|
+
build_fingerprint: '',
|
|
3347
|
+
supported_messages: [],
|
|
3348
|
+
protobuf_definition: null,
|
|
3349
|
+
},
|
|
3350
|
+
})
|
|
3351
|
+
.mockResolvedValueOnce({
|
|
3352
|
+
type: 'DeviceStatus',
|
|
3353
|
+
message: { init_states: true, unlocked: true },
|
|
3354
|
+
});
|
|
3355
|
+
(device as any).commands = { typedCall };
|
|
3356
|
+
|
|
3357
|
+
await device.probeProtocolV2RuntimeState(
|
|
3358
|
+
{
|
|
3359
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3360
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3361
|
+
},
|
|
3362
|
+
5000
|
|
3363
|
+
);
|
|
3364
|
+
|
|
3365
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3366
|
+
1,
|
|
3367
|
+
'ProtocolInfoRequest',
|
|
3368
|
+
'ProtocolInfo',
|
|
3369
|
+
{ eventless_wallet_session: true },
|
|
3370
|
+
{ timeoutMs: 5000 }
|
|
3371
|
+
);
|
|
3372
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3373
|
+
2,
|
|
3374
|
+
'DeviceStatusGet',
|
|
3375
|
+
'DeviceStatus',
|
|
3376
|
+
{},
|
|
3377
|
+
{ timeoutMs: 5000 }
|
|
3378
|
+
);
|
|
3379
|
+
expect(device.features).toMatchObject({
|
|
3380
|
+
mode: 'normal',
|
|
3381
|
+
bootloaderMode: false,
|
|
3382
|
+
initialized: true,
|
|
3383
|
+
unlocked: true,
|
|
3384
|
+
});
|
|
3385
|
+
});
|
|
3386
|
+
|
|
3387
|
+
test('maps legacy bootloader metadata without calling DeviceStatusGet', async () => {
|
|
3388
|
+
const device = Device.fromDescriptor({
|
|
3389
|
+
path: 'usb-path',
|
|
3390
|
+
protocolType: 'V2',
|
|
3391
|
+
} as any);
|
|
3392
|
+
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
3393
|
+
type: 'ProtocolInfo',
|
|
3394
|
+
message: {
|
|
3395
|
+
version: 1,
|
|
3396
|
+
build_fingerprint: '',
|
|
3397
|
+
supported_messages: [],
|
|
3398
|
+
protobuf_definition: null,
|
|
3399
|
+
},
|
|
3400
|
+
});
|
|
3401
|
+
(device as any).commands = { typedCall };
|
|
3402
|
+
|
|
3403
|
+
await device.probeProtocolV2RuntimeState({
|
|
3404
|
+
hw: { Device_type: DeviceType.NEO, serial_no: 'NEOSERIAL' },
|
|
3405
|
+
main_mcu: { bootloader: { version: '0.2.0' } },
|
|
3406
|
+
});
|
|
3407
|
+
|
|
3408
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
3409
|
+
eventless_wallet_session: true,
|
|
3410
|
+
});
|
|
3411
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
3412
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
3413
|
+
expect(device.features).toMatchObject({
|
|
3414
|
+
deviceType: 'neo',
|
|
3415
|
+
mode: 'bootloader',
|
|
3416
|
+
bootloaderMode: true,
|
|
3417
|
+
initialized: null,
|
|
3418
|
+
});
|
|
3419
|
+
});
|
|
3420
|
+
|
|
3421
|
+
test('maps legacy romloader metadata without calling DeviceStatusGet', async () => {
|
|
3422
|
+
const device = Device.fromDescriptor({
|
|
3423
|
+
path: 'usb-path',
|
|
3424
|
+
protocolType: 'V2',
|
|
3425
|
+
} as any);
|
|
3426
|
+
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
3427
|
+
type: 'ProtocolInfo',
|
|
3428
|
+
message: {
|
|
3429
|
+
version: 1,
|
|
3430
|
+
build_fingerprint: '',
|
|
3431
|
+
supported_messages: [],
|
|
3432
|
+
protobuf_definition: null,
|
|
3433
|
+
},
|
|
3434
|
+
});
|
|
3435
|
+
(device as any).commands = { typedCall };
|
|
3436
|
+
|
|
3437
|
+
await device.probeProtocolV2RuntimeState({
|
|
3438
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3439
|
+
main_mcu: { romloader: { version: '1.0.0' } },
|
|
3440
|
+
});
|
|
3441
|
+
|
|
3442
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
3443
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
3444
|
+
expect(device.features).toMatchObject({
|
|
3445
|
+
deviceType: 'pro2',
|
|
3446
|
+
mode: 'romloader',
|
|
3447
|
+
bootloaderMode: true,
|
|
3448
|
+
initialized: null,
|
|
3449
|
+
});
|
|
3450
|
+
});
|
|
3451
|
+
|
|
3197
3452
|
test('does not reinterpret a state update error as runtime detection failure', async () => {
|
|
3198
3453
|
const device = Device.fromDescriptor({
|
|
3199
3454
|
path: 'usb-path',
|
|
@@ -3219,7 +3474,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3219
3474
|
await expect(
|
|
3220
3475
|
device.probeProtocolV2RuntimeState({
|
|
3221
3476
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3222
|
-
|
|
3477
|
+
main_mcu: { application: { version: '1.0.0' } },
|
|
3223
3478
|
})
|
|
3224
3479
|
).rejects.toThrow('state update failed');
|
|
3225
3480
|
|
|
@@ -3244,7 +3499,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3244
3499
|
type: 'DeviceInfo',
|
|
3245
3500
|
message: {
|
|
3246
3501
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3247
|
-
|
|
3502
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3248
3503
|
},
|
|
3249
3504
|
})
|
|
3250
3505
|
.mockResolvedValueOnce({
|
|
@@ -3259,7 +3514,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3259
3514
|
type: 'DeviceInfo',
|
|
3260
3515
|
message: {
|
|
3261
3516
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3262
|
-
|
|
3517
|
+
main_mcu: { application: { version: '1.2.4' } },
|
|
3263
3518
|
},
|
|
3264
3519
|
})
|
|
3265
3520
|
.mockResolvedValueOnce({
|
|
@@ -3291,7 +3546,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3291
3546
|
{
|
|
3292
3547
|
targets: {
|
|
3293
3548
|
hw: true,
|
|
3294
|
-
|
|
3549
|
+
main_mcu: true,
|
|
3295
3550
|
coprocessor: true,
|
|
3296
3551
|
},
|
|
3297
3552
|
types: {
|
|
@@ -3312,7 +3567,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3312
3567
|
type: 'DeviceInfo',
|
|
3313
3568
|
message: {
|
|
3314
3569
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3315
|
-
|
|
3570
|
+
main_mcu: { application: { version: '1.2.4' } },
|
|
3316
3571
|
},
|
|
3317
3572
|
})
|
|
3318
3573
|
.mockResolvedValueOnce({
|
|
@@ -3389,7 +3644,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3389
3644
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
3390
3645
|
{
|
|
3391
3646
|
hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
|
|
3392
|
-
|
|
3647
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3393
3648
|
status: { init_states: true },
|
|
3394
3649
|
}
|
|
3395
3650
|
);
|
|
@@ -3429,7 +3684,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3429
3684
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
3430
3685
|
{
|
|
3431
3686
|
hw: { serial_no: 'PR2SERIAL' },
|
|
3432
|
-
|
|
3687
|
+
main_mcu: { application: { version: '4.15.0' } },
|
|
3433
3688
|
status: { passphrase_enabled: false },
|
|
3434
3689
|
}
|
|
3435
3690
|
);
|
|
@@ -3476,7 +3731,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3476
3731
|
(device as any).features = normalizeProtocolV2Features(
|
|
3477
3732
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
3478
3733
|
{
|
|
3479
|
-
|
|
3734
|
+
main_mcu: { application: { version: '1.2.3' } },
|
|
3480
3735
|
status: { unlocked: false },
|
|
3481
3736
|
}
|
|
3482
3737
|
);
|
|
@@ -3905,7 +4160,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3905
4160
|
type: 'DeviceInfo',
|
|
3906
4161
|
message: {
|
|
3907
4162
|
hw: { serial_no: 'BLE-PRO2-SERIAL' },
|
|
3908
|
-
|
|
4163
|
+
main_mcu: {
|
|
3909
4164
|
bootloader: { version: '0.0.0' },
|
|
3910
4165
|
application: { version: '0.0.0' },
|
|
3911
4166
|
},
|
|
@@ -3957,7 +4212,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3957
4212
|
{
|
|
3958
4213
|
targets: {
|
|
3959
4214
|
hw: true,
|
|
3960
|
-
|
|
4215
|
+
main_mcu: true,
|
|
3961
4216
|
coprocessor: true,
|
|
3962
4217
|
se1: true,
|
|
3963
4218
|
se2: true,
|
|
@@ -4098,7 +4353,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4098
4353
|
).toEqual([3, 4]);
|
|
4099
4354
|
});
|
|
4100
4355
|
|
|
4101
|
-
test('
|
|
4356
|
+
test('keeps external-only resource updates on the prepared host path', async () => {
|
|
4102
4357
|
const method = new FirmwareUpdateV4({
|
|
4103
4358
|
id: 1,
|
|
4104
4359
|
payload: {
|
|
@@ -4108,6 +4363,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4108
4363
|
},
|
|
4109
4364
|
});
|
|
4110
4365
|
method.init();
|
|
4366
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('external-only' as never);
|
|
4111
4367
|
|
|
4112
4368
|
(method as any).device = stubDevice({
|
|
4113
4369
|
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
@@ -4135,57 +4391,183 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4135
4391
|
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
4136
4392
|
});
|
|
4137
4393
|
|
|
4138
|
-
test('
|
|
4394
|
+
test('downloads Protocol V2 resource archives in SDK-managed mode', async () => {
|
|
4395
|
+
const resourceArchiveBinary = new Uint8Array([1, 2, 3, 4]).buffer;
|
|
4396
|
+
const applicationP1Binary = new Uint8Array([5, 6, 7, 8]).buffer;
|
|
4397
|
+
const applicationP1InstallItem = {
|
|
4398
|
+
fileName: 'application_p1.bin',
|
|
4399
|
+
binary: applicationP1Binary,
|
|
4400
|
+
targetId: 4,
|
|
4401
|
+
kind: 'firmware',
|
|
4402
|
+
};
|
|
4403
|
+
const resourceArchiveSha256 = bytesToHex(sha256(new Uint8Array(resourceArchiveBinary)));
|
|
4139
4404
|
const method = new FirmwareUpdateV4({
|
|
4140
4405
|
id: 1,
|
|
4141
4406
|
payload: {
|
|
4142
4407
|
method: 'firmwareUpdateV4',
|
|
4408
|
+
platform: 'ext',
|
|
4409
|
+
targetsToUpdate: ['app_v1', 'resource'],
|
|
4143
4410
|
},
|
|
4144
4411
|
});
|
|
4412
|
+
method.init();
|
|
4413
|
+
|
|
4145
4414
|
(method as any).device = stubDevice({
|
|
4146
|
-
originalDescriptor: { id: 'usb-id', path: '
|
|
4147
|
-
features: {
|
|
4415
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4416
|
+
features: {
|
|
4417
|
+
deviceType: 'pro2',
|
|
4418
|
+
firmwareVersion: '1.0.0',
|
|
4419
|
+
mode: 'normal',
|
|
4420
|
+
bootloaderMode: false,
|
|
4421
|
+
capabilities: [],
|
|
4422
|
+
},
|
|
4423
|
+
getCurrentDeviceType: () => 'pro2',
|
|
4148
4424
|
isBootloader: () => false,
|
|
4149
|
-
|
|
4150
|
-
mode: 'bootloader',
|
|
4151
|
-
bootloaderMode: true,
|
|
4152
|
-
}),
|
|
4153
|
-
});
|
|
4154
|
-
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4155
|
-
message: 'Device rebooted successfully',
|
|
4156
|
-
});
|
|
4157
|
-
(method as any).checkDeviceToBootloader = jest.fn();
|
|
4158
|
-
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
4159
|
-
if (requestType === 'DeviceInfoGet') {
|
|
4160
|
-
return Promise.resolve({
|
|
4161
|
-
type: 'DeviceInfo',
|
|
4162
|
-
message: protocolV2BootloaderDeviceInfo,
|
|
4163
|
-
});
|
|
4164
|
-
}
|
|
4165
|
-
if (requestType === 'ProtocolInfoRequest') {
|
|
4166
|
-
return Promise.resolve({
|
|
4167
|
-
type: 'ProtocolInfo',
|
|
4168
|
-
message: protocolV2BootloaderInfo,
|
|
4169
|
-
});
|
|
4170
|
-
}
|
|
4171
|
-
return Promise.reject(new Error(`unexpected call ${requestType}`));
|
|
4425
|
+
isRomloader: () => false,
|
|
4172
4426
|
});
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4427
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4428
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
|
|
4429
|
+
bootloaderBinary: null,
|
|
4430
|
+
fwBinaryMap: [
|
|
4431
|
+
{
|
|
4432
|
+
fileName: applicationP1InstallItem.fileName,
|
|
4433
|
+
binary: applicationP1Binary,
|
|
4434
|
+
targetId: applicationP1InstallItem.targetId,
|
|
4435
|
+
},
|
|
4436
|
+
],
|
|
4437
|
+
installItems: [applicationP1InstallItem],
|
|
4176
4438
|
});
|
|
4177
|
-
|
|
4178
|
-
(method as any).
|
|
4179
|
-
(method as any).
|
|
4180
|
-
|
|
4439
|
+
const release = jest.fn();
|
|
4440
|
+
(method as any).prepareProtocolV2LocalMemoryHost = jest.fn().mockResolvedValue({ release });
|
|
4441
|
+
(method as any).runProtocolV2PreparedArtifacts = jest
|
|
4442
|
+
.fn()
|
|
4443
|
+
.mockResolvedValue('sdk-managed-resource-result');
|
|
4181
4444
|
method.postTipMessage = jest.fn();
|
|
4445
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('sdk-managed' as never);
|
|
4446
|
+
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue({
|
|
4447
|
+
archiveUrl: 'https://example.com/pro2-resource.zip',
|
|
4448
|
+
archiveSize: resourceArchiveBinary.byteLength,
|
|
4449
|
+
archiveSha256: resourceArchiveSha256,
|
|
4450
|
+
});
|
|
4451
|
+
const getSysResourceBinarySpy = jest
|
|
4452
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
4453
|
+
.mockResolvedValue({ binary: resourceArchiveBinary });
|
|
4182
4454
|
|
|
4183
|
-
await (method
|
|
4455
|
+
await expect(method.run()).resolves.toBe('sdk-managed-resource-result');
|
|
4184
4456
|
|
|
4185
|
-
expect(
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4457
|
+
expect(forceReloadDataSpy).toHaveBeenCalledWith({
|
|
4458
|
+
requireResources: true,
|
|
4459
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
4460
|
+
});
|
|
4461
|
+
expect(getSysResourceBinarySpy).toHaveBeenCalledWith('https://example.com/pro2-resource.zip');
|
|
4462
|
+
expect((method as any).params.resourceArchiveBinary).toBe(resourceArchiveBinary);
|
|
4463
|
+
expect((method as any).prepareProtocolV2LocalMemoryHost).toHaveBeenCalledWith({
|
|
4464
|
+
features: expect.objectContaining({ deviceType: 'pro2' }),
|
|
4465
|
+
firmwareType: EFirmwareType.Universal,
|
|
4466
|
+
availableInstallItems: [applicationP1InstallItem],
|
|
4467
|
+
});
|
|
4468
|
+
expect((method as any).runProtocolV2PreparedArtifacts).toHaveBeenCalledTimes(1);
|
|
4469
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
4470
|
+
});
|
|
4471
|
+
|
|
4472
|
+
test('preserves SDK-managed resource installation errors after preparation', async () => {
|
|
4473
|
+
const resourceArchiveBinary = new Uint8Array([1, 2, 3, 4]).buffer;
|
|
4474
|
+
const installError = ERRORS.TypedError(
|
|
4475
|
+
HardwareErrorCode.FirmwareError,
|
|
4476
|
+
'device rejected the prepared resource installation'
|
|
4477
|
+
);
|
|
4478
|
+
const method = new FirmwareUpdateV4({
|
|
4479
|
+
id: 1,
|
|
4480
|
+
payload: {
|
|
4481
|
+
method: 'firmwareUpdateV4',
|
|
4482
|
+
platform: 'ext',
|
|
4483
|
+
targetsToUpdate: ['resource'],
|
|
4484
|
+
},
|
|
4485
|
+
});
|
|
4486
|
+
method.init();
|
|
4487
|
+
|
|
4488
|
+
(method as any).device = stubDevice({
|
|
4489
|
+
originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
|
|
4490
|
+
features: {
|
|
4491
|
+
deviceType: 'pro2',
|
|
4492
|
+
firmwareVersion: '1.0.0',
|
|
4493
|
+
mode: 'normal',
|
|
4494
|
+
bootloaderMode: false,
|
|
4495
|
+
capabilities: [],
|
|
4496
|
+
},
|
|
4497
|
+
getCurrentDeviceType: () => 'pro2',
|
|
4498
|
+
isBootloader: () => false,
|
|
4499
|
+
isRomloader: () => false,
|
|
4500
|
+
});
|
|
4501
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4502
|
+
(method as any).downloadRemoteProtocolV2ResourceArchive = jest
|
|
4503
|
+
.fn()
|
|
4504
|
+
.mockResolvedValue(resourceArchiveBinary);
|
|
4505
|
+
const release = jest.fn();
|
|
4506
|
+
(method as any).prepareProtocolV2LocalMemoryHost = jest.fn().mockResolvedValue({ release });
|
|
4507
|
+
(method as any).runProtocolV2PreparedArtifacts = jest.fn().mockRejectedValue(installError);
|
|
4508
|
+
method.postTipMessage = jest.fn();
|
|
4509
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('sdk-managed' as never);
|
|
4510
|
+
|
|
4511
|
+
await expect(method.run()).rejects.toBe(installError);
|
|
4512
|
+
|
|
4513
|
+
expect(forceReloadDataSpy).toHaveBeenCalledWith({
|
|
4514
|
+
requireResources: true,
|
|
4515
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
4516
|
+
});
|
|
4517
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
4518
|
+
});
|
|
4519
|
+
|
|
4520
|
+
test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
|
|
4521
|
+
const method = new FirmwareUpdateV4({
|
|
4522
|
+
id: 1,
|
|
4523
|
+
payload: {
|
|
4524
|
+
method: 'firmwareUpdateV4',
|
|
4525
|
+
},
|
|
4526
|
+
});
|
|
4527
|
+
(method as any).device = stubDevice({
|
|
4528
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
4529
|
+
features: { bootloader_mode: false, capabilities: [] },
|
|
4530
|
+
isBootloader: () => false,
|
|
4531
|
+
probeProtocolV2RuntimeState: jest.fn().mockResolvedValue({
|
|
4532
|
+
mode: 'bootloader',
|
|
4533
|
+
bootloaderMode: true,
|
|
4534
|
+
}),
|
|
4535
|
+
});
|
|
4536
|
+
(method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
|
|
4537
|
+
message: 'Device rebooted successfully',
|
|
4538
|
+
});
|
|
4539
|
+
(method as any).checkDeviceToBootloader = jest.fn();
|
|
4540
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
4541
|
+
if (requestType === 'DeviceInfoGet') {
|
|
4542
|
+
return Promise.resolve({
|
|
4543
|
+
type: 'DeviceInfo',
|
|
4544
|
+
message: protocolV2BootloaderDeviceInfo,
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
4548
|
+
return Promise.resolve({
|
|
4549
|
+
type: 'ProtocolInfo',
|
|
4550
|
+
message: protocolV2BootloaderInfo,
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4553
|
+
return Promise.reject(new Error(`unexpected call ${requestType}`));
|
|
4554
|
+
});
|
|
4555
|
+
const reconnectProtocolV2Device = jest.fn().mockImplementation(() => {
|
|
4556
|
+
(method as any).device.isBootloader = () => true;
|
|
4557
|
+
return Promise.resolve();
|
|
4558
|
+
});
|
|
4559
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4560
|
+
(method as any).device.getCommands = () => ({ typedCall });
|
|
4561
|
+
(method as any).protocolV2ExpectedSerialNumber = 'PR9999999999';
|
|
4562
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4563
|
+
method.postTipMessage = jest.fn();
|
|
4564
|
+
|
|
4565
|
+
await (method as any).enterProtocolV2BootloaderMode();
|
|
4566
|
+
|
|
4567
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('AutoRebootToBootloader');
|
|
4568
|
+
expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Bootloader);
|
|
4569
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
4570
|
+
expect((method as any).checkDeviceToBootloader).not.toHaveBeenCalled();
|
|
4189
4571
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4190
4572
|
});
|
|
4191
4573
|
|
|
@@ -4697,7 +5079,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4697
5079
|
type: 'DeviceInfo',
|
|
4698
5080
|
message: {
|
|
4699
5081
|
hw: { serial_no: 'PR2SERIAL' },
|
|
4700
|
-
|
|
5082
|
+
main_mcu: { application: { version: '1.0.0' } },
|
|
4701
5083
|
},
|
|
4702
5084
|
});
|
|
4703
5085
|
(method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
|
|
@@ -4824,59 +5206,88 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4824
5206
|
);
|
|
4825
5207
|
});
|
|
4826
5208
|
|
|
4827
|
-
test('
|
|
5209
|
+
test('stages targets before sending the empty Protocol V2 install request', async () => {
|
|
4828
5210
|
const method = new FirmwareUpdateV4({
|
|
4829
5211
|
id: 1,
|
|
4830
5212
|
payload: {
|
|
4831
5213
|
method: 'firmwareUpdateV4',
|
|
4832
5214
|
},
|
|
4833
5215
|
});
|
|
4834
|
-
const
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
Object.assign(
|
|
4838
|
-
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4839
|
-
{ name: 'NotFoundError' }
|
|
4840
|
-
)
|
|
4841
|
-
);
|
|
5216
|
+
const targets = [{ target_id: 4, path: 'vol1:firmware.bin' }];
|
|
5217
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5218
|
+
const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
|
|
4842
5219
|
|
|
4843
5220
|
(method as any).device = stubDevice({
|
|
4844
|
-
getCommands: () => ({ typedCall }),
|
|
5221
|
+
getCommands: () => ({ typedCall, call }),
|
|
5222
|
+
});
|
|
5223
|
+
method.postTipMessage = jest.fn();
|
|
5224
|
+
method.postProgressMessage = jest.fn();
|
|
5225
|
+
|
|
5226
|
+
await expect(
|
|
5227
|
+
(method as any).protocolV2StartFirmwareUpdate({ targets })
|
|
5228
|
+
).resolves.toBeUndefined();
|
|
5229
|
+
|
|
5230
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
5231
|
+
expect(call).toHaveBeenCalledWith(
|
|
5232
|
+
'DeviceFirmwareUpdateRequest',
|
|
5233
|
+
{},
|
|
5234
|
+
{ returnAfterWrite: true }
|
|
5235
|
+
);
|
|
5236
|
+
expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
|
|
5237
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
5238
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
|
|
5239
|
+
});
|
|
5240
|
+
|
|
5241
|
+
test('does not send the install request when Protocol V2 staging fails', async () => {
|
|
5242
|
+
const method = new FirmwareUpdateV4({
|
|
5243
|
+
id: 1,
|
|
5244
|
+
payload: {
|
|
5245
|
+
method: 'firmwareUpdateV4',
|
|
5246
|
+
},
|
|
5247
|
+
});
|
|
5248
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('stage failed'));
|
|
5249
|
+
const call = jest.fn();
|
|
5250
|
+
|
|
5251
|
+
(method as any).device = stubDevice({
|
|
5252
|
+
getCommands: () => ({ typedCall, call }),
|
|
4845
5253
|
});
|
|
4846
5254
|
method.postTipMessage = jest.fn();
|
|
4847
5255
|
method.postProgressMessage = jest.fn();
|
|
4848
5256
|
|
|
4849
5257
|
await expect(
|
|
4850
5258
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
4851
|
-
targets: [{ target_id: 4, path: 'vol1:
|
|
5259
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
4852
5260
|
})
|
|
4853
|
-
).rejects.toThrow();
|
|
5261
|
+
).rejects.toThrow('stage failed');
|
|
4854
5262
|
|
|
5263
|
+
expect(call).not.toHaveBeenCalled();
|
|
4855
5264
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
4856
5265
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
4857
5266
|
});
|
|
4858
5267
|
|
|
4859
|
-
test('does not
|
|
5268
|
+
test('does not enter install state when writing the empty request fails', async () => {
|
|
4860
5269
|
const method = new FirmwareUpdateV4({
|
|
4861
5270
|
id: 1,
|
|
4862
5271
|
payload: {
|
|
4863
5272
|
method: 'firmwareUpdateV4',
|
|
4864
5273
|
},
|
|
4865
5274
|
});
|
|
4866
|
-
const typedCall = jest.fn().
|
|
5275
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5276
|
+
const call = jest.fn().mockRejectedValue(new Error('write failed'));
|
|
4867
5277
|
|
|
4868
5278
|
(method as any).device = stubDevice({
|
|
4869
|
-
getCommands: () => ({ typedCall }),
|
|
5279
|
+
getCommands: () => ({ typedCall, call }),
|
|
4870
5280
|
});
|
|
4871
5281
|
method.postTipMessage = jest.fn();
|
|
4872
5282
|
method.postProgressMessage = jest.fn();
|
|
4873
5283
|
|
|
4874
5284
|
await expect(
|
|
4875
5285
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
4876
|
-
targets: [{ target_id: 4, path: '
|
|
5286
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
4877
5287
|
})
|
|
4878
|
-
).rejects.toThrow('
|
|
5288
|
+
).rejects.toThrow('write failed');
|
|
4879
5289
|
|
|
5290
|
+
expect(call).toHaveBeenCalledTimes(1);
|
|
4880
5291
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
4881
5292
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
4882
5293
|
});
|
|
@@ -4924,25 +5335,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4924
5335
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4925
5336
|
});
|
|
4926
5337
|
|
|
4927
|
-
test('accepts
|
|
5338
|
+
test('accepts the terminal Success returned by the install request', async () => {
|
|
4928
5339
|
const method = new FirmwareUpdateV4({
|
|
4929
5340
|
id: 1,
|
|
4930
5341
|
payload: {
|
|
4931
5342
|
method: 'firmwareUpdateV4',
|
|
4932
5343
|
},
|
|
4933
5344
|
});
|
|
4934
|
-
const typedCall = jest
|
|
4935
|
-
.fn()
|
|
4936
|
-
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
5345
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
4937
5346
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4938
5347
|
|
|
4939
5348
|
(method as any).device = stubDevice({
|
|
4940
5349
|
getCommands: () => ({ typedCall }),
|
|
4941
5350
|
});
|
|
4942
5351
|
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4943
|
-
|
|
4944
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4945
|
-
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
5352
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4946
5353
|
method.postProgressMessage = jest.fn();
|
|
4947
5354
|
|
|
4948
5355
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
@@ -4951,21 +5358,20 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4951
5358
|
|
|
4952
5359
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4953
5360
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
|
|
4954
|
-
expect((method as any).
|
|
5361
|
+
expect((method as any).protocolV2FinalStatusVerified).toBe(true);
|
|
4955
5362
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4956
5363
|
});
|
|
4957
5364
|
|
|
4958
|
-
test('
|
|
5365
|
+
test('rejects normal mode without install ACK, target status, or a version change', async () => {
|
|
4959
5366
|
const method = new FirmwareUpdateV4({
|
|
4960
5367
|
id: 1,
|
|
4961
5368
|
payload: {
|
|
4962
5369
|
method: 'firmwareUpdateV4',
|
|
4963
5370
|
},
|
|
4964
5371
|
});
|
|
4965
|
-
const typedCall = jest
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
});
|
|
5372
|
+
const typedCall = jest
|
|
5373
|
+
.fn()
|
|
5374
|
+
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
4969
5375
|
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4970
5376
|
let now = 0;
|
|
4971
5377
|
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
@@ -4983,14 +5389,48 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4983
5389
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
4984
5390
|
method.postProgressMessage = jest.fn();
|
|
4985
5391
|
|
|
5392
|
+
await expect(
|
|
5393
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5394
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5395
|
+
])
|
|
5396
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.FirmwareError });
|
|
5397
|
+
|
|
5398
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5399
|
+
});
|
|
5400
|
+
|
|
5401
|
+
test('accepts ACK-less normal mode after the requested target version changes', async () => {
|
|
5402
|
+
const method = new FirmwareUpdateV4({
|
|
5403
|
+
id: 1,
|
|
5404
|
+
payload: {
|
|
5405
|
+
method: 'firmwareUpdateV4',
|
|
5406
|
+
},
|
|
5407
|
+
});
|
|
5408
|
+
const typedCall = jest
|
|
5409
|
+
.fn()
|
|
5410
|
+
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
5411
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
5412
|
+
const probeProtocolV2RuntimeState = jest.fn().mockResolvedValue({
|
|
5413
|
+
mode: 'normal',
|
|
5414
|
+
bootloaderMode: false,
|
|
5415
|
+
firmwareVersion: '2.0.0',
|
|
5416
|
+
});
|
|
5417
|
+
|
|
5418
|
+
(method as any).device = stubDevice({
|
|
5419
|
+
getCommands: () => ({ typedCall }),
|
|
5420
|
+
probeProtocolV2RuntimeState,
|
|
5421
|
+
});
|
|
5422
|
+
(method as any).protocolV2InstallBaselineVersions = new Map([[4, '1.0.0']]);
|
|
5423
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5424
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5425
|
+
method.postProgressMessage = jest.fn();
|
|
5426
|
+
|
|
4986
5427
|
await expect(
|
|
4987
5428
|
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
4988
5429
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
4989
5430
|
])
|
|
4990
5431
|
).resolves.toBeUndefined();
|
|
4991
5432
|
|
|
4992
|
-
expect(
|
|
4993
|
-
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
5433
|
+
expect(probeProtocolV2RuntimeState).toHaveBeenCalledWith(deviceInfo, 5000);
|
|
4994
5434
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4995
5435
|
});
|
|
4996
5436
|
|
|
@@ -5232,7 +5672,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5232
5672
|
);
|
|
5233
5673
|
});
|
|
5234
5674
|
|
|
5235
|
-
test('polls target status after
|
|
5675
|
+
test('polls target status after the install request and finishes when all targets complete', async () => {
|
|
5236
5676
|
const method = new FirmwareUpdateV4({
|
|
5237
5677
|
id: 1,
|
|
5238
5678
|
payload: {
|
|
@@ -5267,7 +5707,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5267
5707
|
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
5268
5708
|
expect(typedCall).toHaveBeenCalledWith(
|
|
5269
5709
|
'DeviceFirmwareUpdateStatusGet',
|
|
5270
|
-
'DeviceFirmwareUpdateStatus',
|
|
5710
|
+
['DeviceFirmwareUpdateStatus', 'Success'],
|
|
5271
5711
|
expect.anything(),
|
|
5272
5712
|
expect.anything()
|
|
5273
5713
|
);
|
|
@@ -5543,7 +5983,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5543
5983
|
});
|
|
5544
5984
|
expect(typedCall).toHaveBeenCalledWith(
|
|
5545
5985
|
'DeviceFirmwareUpdateStatusGet',
|
|
5546
|
-
'DeviceFirmwareUpdateStatus',
|
|
5986
|
+
['DeviceFirmwareUpdateStatus', 'Success'],
|
|
5547
5987
|
{
|
|
5548
5988
|
fields: {
|
|
5549
5989
|
status: true,
|
|
@@ -5555,7 +5995,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5555
5995
|
);
|
|
5556
5996
|
});
|
|
5557
5997
|
|
|
5558
|
-
test('
|
|
5998
|
+
test('preserves component-first target order for the firmware-owned loader handoff', async () => {
|
|
5559
5999
|
const method = new FirmwareUpdateV4({
|
|
5560
6000
|
id: 1,
|
|
5561
6001
|
payload: {
|
|
@@ -5584,54 +6024,72 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5584
6024
|
.mockResolvedValue(undefined);
|
|
5585
6025
|
|
|
5586
6026
|
await (method as any).executeProtocolV2Update({
|
|
5587
|
-
|
|
5588
|
-
|
|
6027
|
+
// The active bootloader installs its component runners in records order and
|
|
6028
|
+
// leaves the bootloader record pending for romloader after reboot. Keep boot
|
|
6029
|
+
// deliberately after components to prove Core does not synthesize boot-first.
|
|
6030
|
+
installItems: [
|
|
5589
6031
|
{
|
|
5590
|
-
fileName: '
|
|
5591
|
-
binary: new Uint8Array([
|
|
5592
|
-
targetId:
|
|
6032
|
+
fileName: 'application_p1.bin',
|
|
6033
|
+
binary: new Uint8Array([8]).buffer,
|
|
6034
|
+
targetId: 4,
|
|
6035
|
+
kind: 'firmware',
|
|
5593
6036
|
},
|
|
5594
6037
|
{
|
|
5595
6038
|
fileName: 'se01.bin',
|
|
5596
6039
|
binary: new Uint8Array([7]).buffer,
|
|
5597
6040
|
targetId: 7,
|
|
6041
|
+
kind: 'firmware',
|
|
5598
6042
|
},
|
|
5599
6043
|
{
|
|
5600
|
-
fileName: '
|
|
5601
|
-
binary: new Uint8Array([
|
|
5602
|
-
targetId:
|
|
6044
|
+
fileName: 'bootloader.bin',
|
|
6045
|
+
binary: new Uint8Array([4, 5]).buffer,
|
|
6046
|
+
targetId: 3,
|
|
6047
|
+
kind: 'bootloader',
|
|
6048
|
+
},
|
|
6049
|
+
{
|
|
6050
|
+
fileName: 'coprocessor.bin',
|
|
6051
|
+
binary: new Uint8Array([6]).buffer,
|
|
6052
|
+
targetId: 6,
|
|
6053
|
+
kind: 'firmware',
|
|
5603
6054
|
},
|
|
5604
6055
|
],
|
|
5605
6056
|
});
|
|
5606
6057
|
|
|
5607
6058
|
expect(writtenPaths).toEqual([
|
|
6059
|
+
'vol0:/application_p1.bin',
|
|
6060
|
+
'vol0:/se01.bin',
|
|
5608
6061
|
'vol0:/bootloader.bin',
|
|
5609
6062
|
'vol0:/coprocessor.bin',
|
|
5610
|
-
'vol0:/se01.bin',
|
|
5611
|
-
'vol0:/application_p1.bin',
|
|
5612
6063
|
]);
|
|
5613
6064
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledTimes(4);
|
|
5614
6065
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenNthCalledWith(
|
|
5615
6066
|
2,
|
|
5616
|
-
'vol0:/
|
|
6067
|
+
'vol0:/se01.bin',
|
|
5617
6068
|
1
|
|
5618
6069
|
);
|
|
5619
|
-
expect((method as any).
|
|
5620
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
5621
|
-
|
|
5622
|
-
});
|
|
5623
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
6070
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6071
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6072
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
5624
6073
|
targets: [
|
|
5625
|
-
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5626
|
-
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
5627
6074
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6075
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
6076
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6077
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5628
6078
|
],
|
|
5629
6079
|
});
|
|
6080
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalledTimes(1);
|
|
6081
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalledWith([
|
|
6082
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6083
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
6084
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6085
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
6086
|
+
]);
|
|
6087
|
+
expect((method as any).exitProtocolV2BootloaderToNormal).not.toHaveBeenCalled();
|
|
5630
6088
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
5631
|
-
expect((method as any).
|
|
6089
|
+
expect((method as any).completeProtocolV2FinalVerification).toHaveBeenCalledTimes(1);
|
|
5632
6090
|
});
|
|
5633
6091
|
|
|
5634
|
-
test('
|
|
6092
|
+
test('uses one global transfer range for resources and firmware', async () => {
|
|
5635
6093
|
const method = new FirmwareUpdateV4({
|
|
5636
6094
|
id: 1,
|
|
5637
6095
|
payload: {
|
|
@@ -5688,18 +6146,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5688
6146
|
],
|
|
5689
6147
|
});
|
|
5690
6148
|
|
|
5691
|
-
expect(method.
|
|
6149
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6150
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
5692
6151
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
5693
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
5694
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
6152
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5695
6153
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5696
6154
|
1,
|
|
5697
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
6155
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
5698
6156
|
);
|
|
5699
6157
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5700
6158
|
2,
|
|
5701
|
-
expect.objectContaining({ processedSize:
|
|
6159
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
5702
6160
|
);
|
|
6161
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
6162
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
6163
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6164
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
6165
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
6166
|
+
});
|
|
5703
6167
|
expect((method as any).isProtocolV2ResourceBundleUpToDate).toHaveBeenCalledWith(
|
|
5704
6168
|
expect.objectContaining({
|
|
5705
6169
|
version: [1, 2, 3],
|
|
@@ -5709,50 +6173,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5709
6173
|
);
|
|
5710
6174
|
});
|
|
5711
6175
|
|
|
5712
|
-
test('
|
|
5713
|
-
const method = new FirmwareUpdateV4({
|
|
5714
|
-
id: 1,
|
|
5715
|
-
payload: { method: 'firmwareUpdateV4' },
|
|
5716
|
-
});
|
|
5717
|
-
const phases = (method as any).buildProtocolV2ExecutionPhases({
|
|
5718
|
-
installSources: [{ kind: 'bootloader' }, { kind: 'component' }],
|
|
5719
|
-
resourceSources: [{ kind: 'resource' }],
|
|
5720
|
-
});
|
|
5721
|
-
|
|
5722
|
-
expect(phases.map((phase: { kind: string }) => phase.kind)).toEqual([
|
|
5723
|
-
'bootloader-install',
|
|
5724
|
-
'bootloader-verify',
|
|
5725
|
-
'resource-sync',
|
|
5726
|
-
'component-install',
|
|
5727
|
-
'final-verify',
|
|
5728
|
-
]);
|
|
5729
|
-
});
|
|
5730
|
-
|
|
5731
|
-
test('stages the mounted boot resource before any firmware install phase', () => {
|
|
5732
|
-
const method = new FirmwareUpdateV4({
|
|
5733
|
-
id: 1,
|
|
5734
|
-
payload: { method: 'firmwareUpdateV4' },
|
|
5735
|
-
});
|
|
5736
|
-
const bootResource = {
|
|
5737
|
-
devicePath: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
5738
|
-
};
|
|
5739
|
-
const phases = (method as any).buildProtocolV2ExecutionPhases({
|
|
5740
|
-
installSources: [{ kind: 'bootloader' }, { kind: 'component' }],
|
|
5741
|
-
resourceSources: [bootResource, { devicePath: 'vol0:/resource/images/images.okpkg' }],
|
|
5742
|
-
});
|
|
5743
|
-
|
|
5744
|
-
expect(phases.map((phase: { kind: string }) => phase.kind)).toEqual([
|
|
5745
|
-
'resource-sync',
|
|
5746
|
-
'bootloader-install',
|
|
5747
|
-
'bootloader-verify',
|
|
5748
|
-
'resource-sync',
|
|
5749
|
-
'component-install',
|
|
5750
|
-
'final-verify',
|
|
5751
|
-
]);
|
|
5752
|
-
expect(phases[0].resourceSources).toEqual([bootResource]);
|
|
5753
|
-
});
|
|
5754
|
-
|
|
5755
|
-
test('removes stale boot resource staging before a component-only reboot', async () => {
|
|
6176
|
+
test('removes stale boot resource staging before the combined transfer', async () => {
|
|
5756
6177
|
const method = new FirmwareUpdateV4({
|
|
5757
6178
|
id: 1,
|
|
5758
6179
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -5779,18 +6200,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5779
6200
|
events.push('transfer-component');
|
|
5780
6201
|
return Promise.resolve();
|
|
5781
6202
|
});
|
|
5782
|
-
(method as any).
|
|
5783
|
-
events.push('
|
|
5784
|
-
return Promise.resolve();
|
|
6203
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockImplementation(() => {
|
|
6204
|
+
events.push('final-verify');
|
|
6205
|
+
return Promise.resolve({});
|
|
5785
6206
|
});
|
|
5786
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5787
6207
|
|
|
5788
6208
|
await (method as any).executeProtocolV2SourceUpdate({
|
|
5789
6209
|
installSources: [{ kind: 'firmware' }],
|
|
5790
6210
|
resourceSources: [],
|
|
5791
6211
|
});
|
|
5792
6212
|
|
|
5793
|
-
expect(
|
|
6213
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6214
|
+
expect(events).toEqual(['delete-stale-staging', 'transfer-component', 'final-verify']);
|
|
5794
6215
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'FilesystemFileDelete', 'Success', {
|
|
5795
6216
|
path: 'vol0:/loaders/bootloader/boot_resource.okpkg.staging',
|
|
5796
6217
|
});
|
|
@@ -6762,9 +7183,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6762
7183
|
],
|
|
6763
7184
|
};
|
|
6764
7185
|
const zip = new JSZip();
|
|
6765
|
-
zip.file('manifest.json', JSON.stringify(manifest));
|
|
6766
|
-
zip.file('bundles/images/images.okpkg', imagesBinary);
|
|
6767
|
-
zip.file('loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
7186
|
+
zip.file('pro2-resource/manifest.json', JSON.stringify(manifest));
|
|
7187
|
+
zip.file('pro2-resource/bundles/images/images.okpkg', imagesBinary);
|
|
7188
|
+
zip.file('pro2-resource/loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
7189
|
+
zip.file('pro2-resource/build-info.txt', 'ignored');
|
|
6768
7190
|
const resourceArchiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
|
|
6769
7191
|
const applicationP1Binary = new Uint8Array([7, 8, 9]).buffer;
|
|
6770
7192
|
const bootloaderBinary = new Uint8Array([10, 11, 12]).buffer;
|
|
@@ -6840,6 +7262,39 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6840
7262
|
}
|
|
6841
7263
|
});
|
|
6842
7264
|
|
|
7265
|
+
test('accepts a local resource ZIP without manifest.json', async () => {
|
|
7266
|
+
const imagesBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
7267
|
+
const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
|
|
7268
|
+
const zip = new JSZip();
|
|
7269
|
+
zip.file('pro2-resource/bundles/images/images.okpkg', imagesBinary);
|
|
7270
|
+
zip.file('pro2-resource/loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
7271
|
+
zip.file('pro2-resource/build-info.txt', 'ignored');
|
|
7272
|
+
const method = new FirmwareUpdateV4({
|
|
7273
|
+
id: 1,
|
|
7274
|
+
payload: {
|
|
7275
|
+
method: 'firmwareUpdateV4',
|
|
7276
|
+
platform: 'web',
|
|
7277
|
+
targetsToUpdate: ['resource'],
|
|
7278
|
+
},
|
|
7279
|
+
});
|
|
7280
|
+
method.init();
|
|
7281
|
+
|
|
7282
|
+
await expect(
|
|
7283
|
+
(method as any).prepareProtocolV2LocalResourceArchive(
|
|
7284
|
+
await zip.generateAsync({ type: 'arraybuffer' })
|
|
7285
|
+
)
|
|
7286
|
+
).resolves.toMatchObject({
|
|
7287
|
+
materializedEntries: [
|
|
7288
|
+
{ entryName: 'manifest.json' },
|
|
7289
|
+
{ entryName: 'bundles/images/images.okpkg', binary: imagesBinary },
|
|
7290
|
+
{
|
|
7291
|
+
entryName: 'loaders/bootloader/boot_resource.okpkg',
|
|
7292
|
+
binary: bootResourceBinary,
|
|
7293
|
+
},
|
|
7294
|
+
],
|
|
7295
|
+
});
|
|
7296
|
+
});
|
|
7297
|
+
|
|
6843
7298
|
test('rejects a local resource ZIP whose file hash does not match its manifest', async () => {
|
|
6844
7299
|
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
6845
7300
|
const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
|
|
@@ -6930,18 +7385,57 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6930
7385
|
|
|
6931
7386
|
test('rejects an oversized ZIP entry before allocating its decompressed bytes', async () => {
|
|
6932
7387
|
const extractEntry = jest.fn();
|
|
6933
|
-
const
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
7388
|
+
const oversizedFileSize = 257 * 1024 * 1024;
|
|
7389
|
+
const manifestBinary = new TextEncoder().encode(
|
|
7390
|
+
JSON.stringify({
|
|
7391
|
+
files: [
|
|
7392
|
+
{
|
|
7393
|
+
archive_path: 'loaders/bootloader/boot_resource.okpkg',
|
|
7394
|
+
original_name: 'boot_resource.okpkg',
|
|
7395
|
+
device_path: 'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
7396
|
+
size: oversizedFileSize,
|
|
7397
|
+
sha256: '0'.repeat(64),
|
|
7398
|
+
signed: true,
|
|
7399
|
+
sig_algo: 'ed25519',
|
|
6941
7400
|
},
|
|
6942
|
-
|
|
7401
|
+
{
|
|
7402
|
+
archive_path: 'bundles/images/images.okpkg',
|
|
7403
|
+
original_name: 'images.okpkg',
|
|
7404
|
+
device_path: 'vol0:/bundles/images/images.okpkg',
|
|
7405
|
+
size: oversizedFileSize,
|
|
7406
|
+
sha256: '0'.repeat(64),
|
|
7407
|
+
signed: true,
|
|
7408
|
+
sig_algo: 'ed25519',
|
|
7409
|
+
},
|
|
7410
|
+
],
|
|
7411
|
+
})
|
|
7412
|
+
);
|
|
7413
|
+
const zipFiles = {
|
|
7414
|
+
'manifest.json': {
|
|
7415
|
+
name: 'manifest.json',
|
|
7416
|
+
dir: false,
|
|
7417
|
+
_data: {
|
|
7418
|
+
compressedSize: manifestBinary.byteLength,
|
|
7419
|
+
uncompressedSize: manifestBinary.byteLength,
|
|
6943
7420
|
},
|
|
7421
|
+
async: jest.fn().mockResolvedValue(manifestBinary.buffer),
|
|
7422
|
+
},
|
|
7423
|
+
'loaders/bootloader/boot_resource.okpkg': {
|
|
7424
|
+
name: 'loaders/bootloader/boot_resource.okpkg',
|
|
7425
|
+
dir: false,
|
|
7426
|
+
_data: { compressedSize: 1, uncompressedSize: oversizedFileSize },
|
|
7427
|
+
async: extractEntry,
|
|
6944
7428
|
},
|
|
7429
|
+
'bundles/images/images.okpkg': {
|
|
7430
|
+
name: 'bundles/images/images.okpkg',
|
|
7431
|
+
dir: false,
|
|
7432
|
+
_data: { compressedSize: 1, uncompressedSize: oversizedFileSize },
|
|
7433
|
+
async: extractEntry,
|
|
7434
|
+
},
|
|
7435
|
+
};
|
|
7436
|
+
const loadSpy = jest.spyOn(JSZip, 'loadAsync').mockResolvedValue({
|
|
7437
|
+
files: zipFiles,
|
|
7438
|
+
file: (name: string) => zipFiles[name as keyof typeof zipFiles] ?? null,
|
|
6945
7439
|
} as unknown as JSZip);
|
|
6946
7440
|
const method = new FirmwareUpdateV4({
|
|
6947
7441
|
id: 1,
|
|
@@ -6956,7 +7450,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6956
7450
|
try {
|
|
6957
7451
|
await expect(
|
|
6958
7452
|
(method as any).prepareProtocolV2LocalResourceArchive(new Uint8Array([1]).buffer)
|
|
6959
|
-
).rejects.toThrow('declared size
|
|
7453
|
+
).rejects.toThrow('declared size is invalid');
|
|
6960
7454
|
expect(extractEntry).not.toHaveBeenCalled();
|
|
6961
7455
|
} finally {
|
|
6962
7456
|
loadSpy.mockRestore();
|
|
@@ -7137,6 +7631,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7137
7631
|
method.postProgressMessage = jest.fn();
|
|
7138
7632
|
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
7139
7633
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
7634
|
+
(method as any).ensureProtocolV2BootResourceStagingIsEmpty = jest
|
|
7635
|
+
.fn()
|
|
7636
|
+
.mockResolvedValue(undefined);
|
|
7140
7637
|
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
7141
7638
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7142
7639
|
|
|
@@ -7263,6 +7760,86 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7263
7760
|
});
|
|
7264
7761
|
});
|
|
7265
7762
|
|
|
7763
|
+
test('sends one monotonic device transfer progress range across multiple files', async () => {
|
|
7764
|
+
const method = new FirmwareUpdateV4({
|
|
7765
|
+
id: 1,
|
|
7766
|
+
payload: {
|
|
7767
|
+
method: 'firmwareUpdateV4',
|
|
7768
|
+
},
|
|
7769
|
+
});
|
|
7770
|
+
const typedCall = jest.fn(
|
|
7771
|
+
(
|
|
7772
|
+
_name: string,
|
|
7773
|
+
_resType: string,
|
|
7774
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
7775
|
+
) =>
|
|
7776
|
+
Promise.resolve({
|
|
7777
|
+
type: 'FilesystemFile',
|
|
7778
|
+
message: {
|
|
7779
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
7780
|
+
},
|
|
7781
|
+
})
|
|
7782
|
+
);
|
|
7783
|
+
|
|
7784
|
+
(method as any).device = stubDevice({
|
|
7785
|
+
getCommands: () => ({ typedCall }),
|
|
7786
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7787
|
+
toMessageObject: () => ({ connectId: 'firmware-device' }),
|
|
7788
|
+
});
|
|
7789
|
+
method.postMessage = jest.fn();
|
|
7790
|
+
method.postTipMessage = jest.fn();
|
|
7791
|
+
method.postProgressMessage = jest.fn();
|
|
7792
|
+
(method as any).isProtocolV2ResourceBundleUpToDate = jest.fn().mockResolvedValue(false);
|
|
7793
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7794
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
7795
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
7796
|
+
.fn()
|
|
7797
|
+
.mockResolvedValue(undefined);
|
|
7798
|
+
|
|
7799
|
+
const resourceSource = await openFirmwareByteSource({
|
|
7800
|
+
binary: new Uint8Array(4097).buffer,
|
|
7801
|
+
});
|
|
7802
|
+
const firmwareSource = await openFirmwareByteSource({
|
|
7803
|
+
binary: new Uint8Array(4097).buffer,
|
|
7804
|
+
});
|
|
7805
|
+
try {
|
|
7806
|
+
await (method as any).executeProtocolV2TransferPhase({
|
|
7807
|
+
resourceSources: [
|
|
7808
|
+
{
|
|
7809
|
+
name: 'images.okpkg',
|
|
7810
|
+
source: resourceSource,
|
|
7811
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
7812
|
+
},
|
|
7813
|
+
],
|
|
7814
|
+
installSources: [
|
|
7815
|
+
{
|
|
7816
|
+
fileName: 'application_p1.bin',
|
|
7817
|
+
source: firmwareSource,
|
|
7818
|
+
targetId: 4,
|
|
7819
|
+
kind: 'firmware',
|
|
7820
|
+
},
|
|
7821
|
+
],
|
|
7822
|
+
});
|
|
7823
|
+
} finally {
|
|
7824
|
+
await resourceSource?.close();
|
|
7825
|
+
await firmwareSource?.close();
|
|
7826
|
+
}
|
|
7827
|
+
|
|
7828
|
+
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
7829
|
+
const deviceProgress = writePayloads.map(payload => payload.ui_percentage);
|
|
7830
|
+
expect(deviceProgress).toEqual([0, 50, 99, 100]);
|
|
7831
|
+
expect(deviceProgress.filter(progress => progress === 0)).toHaveLength(1);
|
|
7832
|
+
expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(1);
|
|
7833
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
7834
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
7835
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
7836
|
+
expect(
|
|
7837
|
+
(method.postProgressMessage as jest.Mock).mock.calls.filter(
|
|
7838
|
+
([progress, progressType]) => progress === 100 && progressType === 'transferData'
|
|
7839
|
+
)
|
|
7840
|
+
).toHaveLength(1);
|
|
7841
|
+
});
|
|
7842
|
+
|
|
7266
7843
|
// TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
|
|
7267
7844
|
// writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
|
|
7268
7845
|
// streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
|
|
@@ -7629,23 +8206,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7629
8206
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
|
|
7630
8207
|
});
|
|
7631
8208
|
|
|
7632
|
-
test('
|
|
8209
|
+
test('starts install progress only after Stage and the empty Request are written', async () => {
|
|
7633
8210
|
const method = new FirmwareUpdateV4({
|
|
7634
8211
|
id: 1,
|
|
7635
8212
|
payload: {
|
|
7636
8213
|
method: 'firmwareUpdateV4',
|
|
7637
8214
|
},
|
|
7638
8215
|
});
|
|
7639
|
-
let
|
|
7640
|
-
const typedCall = jest.fn().
|
|
8216
|
+
let resolveWrite: ((value: unknown) => void) | undefined;
|
|
8217
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
8218
|
+
const call = jest.fn().mockImplementation(
|
|
7641
8219
|
() =>
|
|
7642
8220
|
new Promise(resolve => {
|
|
7643
|
-
|
|
8221
|
+
resolveWrite = resolve;
|
|
7644
8222
|
})
|
|
7645
8223
|
);
|
|
7646
8224
|
|
|
7647
8225
|
(method as any).device = stubDevice({
|
|
7648
|
-
getCommands: () => ({ typedCall }),
|
|
8226
|
+
getCommands: () => ({ typedCall, call }),
|
|
7649
8227
|
});
|
|
7650
8228
|
method.postProgressMessage = jest.fn();
|
|
7651
8229
|
method.postTipMessage = jest.fn();
|
|
@@ -7653,21 +8231,31 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7653
8231
|
const startPromise = (method as any).protocolV2StartFirmwareUpdate({
|
|
7654
8232
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
7655
8233
|
});
|
|
7656
|
-
await Promise
|
|
8234
|
+
await new Promise<void>(resolve => {
|
|
8235
|
+
setImmediate(resolve);
|
|
8236
|
+
});
|
|
7657
8237
|
|
|
7658
|
-
expect(typedCall.mock.calls[0]
|
|
7659
|
-
|
|
8238
|
+
expect(typedCall.mock.calls[0]).toEqual([
|
|
8239
|
+
'DeviceFirmwareUpdateStage',
|
|
8240
|
+
'Success',
|
|
8241
|
+
{ targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
|
|
8242
|
+
]);
|
|
8243
|
+
expect(call).toHaveBeenCalledWith(
|
|
8244
|
+
'DeviceFirmwareUpdateRequest',
|
|
8245
|
+
{},
|
|
8246
|
+
{ returnAfterWrite: true }
|
|
8247
|
+
);
|
|
7660
8248
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
7661
8249
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
7662
8250
|
|
|
7663
|
-
|
|
8251
|
+
resolveWrite?.({ type: 'WriteCompleted', message: {} });
|
|
7664
8252
|
await startPromise;
|
|
7665
8253
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
7666
8254
|
expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
|
|
7667
8255
|
expect(method.postTipMessage).toHaveBeenCalledTimes(1);
|
|
7668
8256
|
});
|
|
7669
8257
|
|
|
7670
|
-
test('
|
|
8258
|
+
test('sends an empty Protocol V2 firmware install request without waiting for its response', async () => {
|
|
7671
8259
|
const method = new FirmwareUpdateV4({
|
|
7672
8260
|
id: 1,
|
|
7673
8261
|
payload: {
|
|
@@ -7678,9 +8266,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7678
8266
|
type: 'Success',
|
|
7679
8267
|
message: { message: 'accepted' },
|
|
7680
8268
|
});
|
|
8269
|
+
const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
|
|
7681
8270
|
|
|
7682
8271
|
(method as any).device = stubDevice({
|
|
7683
|
-
getCommands: () => ({ typedCall }),
|
|
8272
|
+
getCommands: () => ({ typedCall, call }),
|
|
7684
8273
|
});
|
|
7685
8274
|
method.postProgressMessage = jest.fn();
|
|
7686
8275
|
method.postTipMessage = jest.fn();
|
|
@@ -7689,7 +8278,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7689
8278
|
targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
|
|
7690
8279
|
});
|
|
7691
8280
|
|
|
7692
|
-
expect(typedCall.mock.calls[0][
|
|
8281
|
+
expect(typedCall.mock.calls[0][0]).toBe('DeviceFirmwareUpdateStage');
|
|
8282
|
+
expect(call).toHaveBeenCalledWith(
|
|
8283
|
+
'DeviceFirmwareUpdateRequest',
|
|
8284
|
+
{},
|
|
8285
|
+
{ returnAfterWrite: true }
|
|
8286
|
+
);
|
|
7693
8287
|
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
7694
8288
|
});
|
|
7695
8289
|
});
|
|
@@ -8763,7 +9357,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
8763
9357
|
info: {
|
|
8764
9358
|
version: undefined,
|
|
8765
9359
|
serial_number: 'PR2SERIAL',
|
|
8766
|
-
|
|
9360
|
+
factory_burn_in_completed: true,
|
|
8767
9361
|
factory_test_completed: undefined,
|
|
8768
9362
|
manufacture_time: undefined,
|
|
8769
9363
|
},
|
|
@@ -9013,7 +9607,7 @@ describe('Protocol V2 raw device info method', () => {
|
|
|
9013
9607
|
'DeviceInfoGet',
|
|
9014
9608
|
'DeviceInfo',
|
|
9015
9609
|
{
|
|
9016
|
-
targets: { hw: true,
|
|
9610
|
+
targets: { hw: true, main_mcu: true, coprocessor: true },
|
|
9017
9611
|
types: { version: true, specific: true },
|
|
9018
9612
|
},
|
|
9019
9613
|
{ timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }
|