@onekeyfe/hd-core 1.2.0-alpha.107 → 1.2.0-alpha.108
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__/deviceSettings.test.ts +0 -1
- package/__tests__/deviceUploadNft.test.ts +33 -4
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +178 -0
- package/__tests__/get-device-state.test.ts +52 -13
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/protocol-v2.test.ts +762 -124
- 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 +10 -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/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 +11 -14
- package/dist/index.js +455 -200
- package/dist/protocols/protocol-v2/features.d.ts +9 -1
- 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/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.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 +326 -174
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/helpers/base64Data.ts +85 -0
- 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/device/Device.ts +53 -31
- package/src/device/DeviceCommands.ts +4 -14
- package/src/device/DevicePool.ts +6 -2
- package/src/events/logBlockEvent.ts +14 -1
- package/src/protocols/protocol-v2/features.ts +19 -2
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/types/api/protocolV2.ts +1 -1
- 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 = {
|
|
@@ -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
|
+
fw: { bootloader: { version: '0.2.0' } },
|
|
921
|
+
})
|
|
922
|
+
).toBe('bootloader');
|
|
923
|
+
expect(
|
|
924
|
+
getProtocolV2RuntimeMode(legacyProtocolInfo, {
|
|
925
|
+
fw: { romloader: { version: '1.0.0' } },
|
|
926
|
+
})
|
|
927
|
+
).toBe('romloader');
|
|
928
|
+
expect(
|
|
929
|
+
getProtocolV2RuntimeMode(legacyProtocolInfo, {
|
|
930
|
+
fw: {
|
|
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';
|
|
@@ -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',
|
|
@@ -3194,6 +3296,159 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
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
|
+
fw: { 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
|
+
fw: { 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
|
+
fw: { 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
|
+
fw: { 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',
|
|
@@ -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,48 +4391,174 @@ 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
|
-
}),
|
|
4425
|
+
isRomloader: () => false,
|
|
4153
4426
|
});
|
|
4154
|
-
(method as any).
|
|
4155
|
-
|
|
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],
|
|
4156
4438
|
});
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
message: protocolV2BootloaderInfo,
|
|
4169
|
-
});
|
|
4170
|
-
}
|
|
4171
|
-
return Promise.reject(new Error(`unexpected call ${requestType}`));
|
|
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');
|
|
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,
|
|
4172
4450
|
});
|
|
4173
|
-
const
|
|
4174
|
-
(
|
|
4175
|
-
|
|
4451
|
+
const getSysResourceBinarySpy = jest
|
|
4452
|
+
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
4453
|
+
.mockResolvedValue({ binary: resourceArchiveBinary });
|
|
4454
|
+
|
|
4455
|
+
await expect(method.run()).resolves.toBe('sdk-managed-resource-result');
|
|
4456
|
+
|
|
4457
|
+
expect(forceReloadDataSpy).toHaveBeenCalledWith({
|
|
4458
|
+
requireResources: true,
|
|
4459
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
4176
4460
|
});
|
|
4177
|
-
(
|
|
4178
|
-
(method as any).
|
|
4179
|
-
(method as any).
|
|
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';
|
|
4180
4562
|
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
4181
4563
|
method.postTipMessage = jest.fn();
|
|
4182
4564
|
|
|
@@ -4249,6 +4631,43 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4249
4631
|
expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
|
|
4250
4632
|
});
|
|
4251
4633
|
|
|
4634
|
+
test('keeps an ambiguous legacy runtime on the direct loader update path', async () => {
|
|
4635
|
+
const method = new FirmwareUpdateV4({
|
|
4636
|
+
id: 1,
|
|
4637
|
+
payload: {
|
|
4638
|
+
method: 'firmwareUpdateV4',
|
|
4639
|
+
},
|
|
4640
|
+
});
|
|
4641
|
+
(method as any).device = stubDevice({
|
|
4642
|
+
originalDescriptor: { id: 'usb-id', path: 'legacy-path', protocolType: 'V2' },
|
|
4643
|
+
state: {
|
|
4644
|
+
raw: {
|
|
4645
|
+
protocolV2ProtocolInfo: {
|
|
4646
|
+
version: 1,
|
|
4647
|
+
build_fingerprint: '',
|
|
4648
|
+
supported_messages: [],
|
|
4649
|
+
protobuf_definition: null,
|
|
4650
|
+
},
|
|
4651
|
+
},
|
|
4652
|
+
},
|
|
4653
|
+
features: {
|
|
4654
|
+
deviceType: 'pro2',
|
|
4655
|
+
mode: 'bootloader',
|
|
4656
|
+
bootloaderMode: true,
|
|
4657
|
+
capabilities: [],
|
|
4658
|
+
},
|
|
4659
|
+
isBootloader: () => true,
|
|
4660
|
+
isRomloader: () => false,
|
|
4661
|
+
});
|
|
4662
|
+
(method as any).protocolV2Reboot = jest.fn();
|
|
4663
|
+
method.postTipMessage = jest.fn();
|
|
4664
|
+
|
|
4665
|
+
await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
|
|
4666
|
+
|
|
4667
|
+
expect((method as any).protocolV2LegacyDirectUpdate).toBe(true);
|
|
4668
|
+
expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
|
|
4669
|
+
});
|
|
4670
|
+
|
|
4252
4671
|
test('keeps Protocol V2 romloader active before firmware transfer', async () => {
|
|
4253
4672
|
const method = new FirmwareUpdateV4({
|
|
4254
4673
|
id: 1,
|
|
@@ -4881,6 +5300,87 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4881
5300
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
4882
5301
|
});
|
|
4883
5302
|
|
|
5303
|
+
test.each([
|
|
5304
|
+
'Failure_InvalidMessage,Handler not registered',
|
|
5305
|
+
'Failure_InvalidMessage,Unsupported message',
|
|
5306
|
+
])('reboots a legacy App when the direct update endpoint is unavailable: %s', async message => {
|
|
5307
|
+
const method = new FirmwareUpdateV4({
|
|
5308
|
+
id: 1,
|
|
5309
|
+
payload: {
|
|
5310
|
+
method: 'firmwareUpdateV4',
|
|
5311
|
+
},
|
|
5312
|
+
});
|
|
5313
|
+
const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
|
|
5314
|
+
const appTypedCall = jest.fn().mockRejectedValueOnce(new Error(message));
|
|
5315
|
+
const bootloaderTypedCall = jest
|
|
5316
|
+
.fn()
|
|
5317
|
+
.mockResolvedValueOnce({ type: 'Success', message: { message: '' } });
|
|
5318
|
+
let activeCommands = { typedCall: appTypedCall };
|
|
5319
|
+
|
|
5320
|
+
(method as any).device = stubDevice({
|
|
5321
|
+
getCommands: () => activeCommands,
|
|
5322
|
+
});
|
|
5323
|
+
(method as any).protocolV2LegacyDirectUpdate = true;
|
|
5324
|
+
(method as any).rebootProtocolV2ToBootloader = jest.fn().mockImplementation(() => {
|
|
5325
|
+
activeCommands = { typedCall: bootloaderTypedCall };
|
|
5326
|
+
return Promise.resolve(true);
|
|
5327
|
+
});
|
|
5328
|
+
method.postTipMessage = jest.fn();
|
|
5329
|
+
method.postProgressMessage = jest.fn();
|
|
5330
|
+
|
|
5331
|
+
await expect((method as any).protocolV2StartFirmwareUpdate({ targets })).resolves.toEqual({
|
|
5332
|
+
type: 'Success',
|
|
5333
|
+
message: { message: '' },
|
|
5334
|
+
});
|
|
5335
|
+
|
|
5336
|
+
expect(appTypedCall).toHaveBeenCalledTimes(1);
|
|
5337
|
+
expect(appTypedCall).toHaveBeenCalledWith(
|
|
5338
|
+
'DeviceFirmwareUpdateRequest',
|
|
5339
|
+
'Success',
|
|
5340
|
+
{ targets },
|
|
5341
|
+
{ timeoutMs: 180000 }
|
|
5342
|
+
);
|
|
5343
|
+
expect(bootloaderTypedCall).toHaveBeenCalledTimes(1);
|
|
5344
|
+
expect(bootloaderTypedCall).toHaveBeenCalledWith(
|
|
5345
|
+
'DeviceFirmwareUpdateRequest',
|
|
5346
|
+
'Success',
|
|
5347
|
+
{ targets },
|
|
5348
|
+
{ timeoutMs: 180000 }
|
|
5349
|
+
);
|
|
5350
|
+
expect((method as any).rebootProtocolV2ToBootloader).toHaveBeenCalledTimes(1);
|
|
5351
|
+
expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
|
|
5352
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
|
|
5353
|
+
});
|
|
5354
|
+
|
|
5355
|
+
test('does not replay a legacy direct update after a link failure', async () => {
|
|
5356
|
+
const method = new FirmwareUpdateV4({
|
|
5357
|
+
id: 1,
|
|
5358
|
+
payload: {
|
|
5359
|
+
method: 'firmwareUpdateV4',
|
|
5360
|
+
},
|
|
5361
|
+
});
|
|
5362
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
5363
|
+
|
|
5364
|
+
(method as any).device = stubDevice({
|
|
5365
|
+
getCommands: () => ({ typedCall }),
|
|
5366
|
+
});
|
|
5367
|
+
(method as any).protocolV2LegacyDirectUpdate = true;
|
|
5368
|
+
(method as any).rebootProtocolV2ToBootloader = jest.fn();
|
|
5369
|
+
method.postTipMessage = jest.fn();
|
|
5370
|
+
method.postProgressMessage = jest.fn();
|
|
5371
|
+
|
|
5372
|
+
await expect(
|
|
5373
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
5374
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5375
|
+
})
|
|
5376
|
+
).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
|
|
5377
|
+
|
|
5378
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
5379
|
+
expect((method as any).rebootProtocolV2ToBootloader).not.toHaveBeenCalled();
|
|
5380
|
+
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5381
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5382
|
+
});
|
|
5383
|
+
|
|
4884
5384
|
test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
|
|
4885
5385
|
const method = new FirmwareUpdateV4({
|
|
4886
5386
|
id: 1,
|
|
@@ -4943,6 +5443,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4943
5443
|
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
4944
5444
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4945
5445
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
5446
|
+
(method as any).protocolV2InstallAckReceived = true;
|
|
4946
5447
|
method.postProgressMessage = jest.fn();
|
|
4947
5448
|
|
|
4948
5449
|
await (method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
@@ -4981,6 +5482,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4981
5482
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
4982
5483
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
4983
5484
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
5485
|
+
(method as any).protocolV2InstallAckReceived = true;
|
|
4984
5486
|
method.postProgressMessage = jest.fn();
|
|
4985
5487
|
|
|
4986
5488
|
await expect(
|
|
@@ -4994,6 +5496,78 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4994
5496
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4995
5497
|
});
|
|
4996
5498
|
|
|
5499
|
+
test('rejects normal mode without install ACK, target status, or a version change', async () => {
|
|
5500
|
+
const method = new FirmwareUpdateV4({
|
|
5501
|
+
id: 1,
|
|
5502
|
+
payload: {
|
|
5503
|
+
method: 'firmwareUpdateV4',
|
|
5504
|
+
},
|
|
5505
|
+
});
|
|
5506
|
+
const typedCall = jest
|
|
5507
|
+
.fn()
|
|
5508
|
+
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
5509
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
5510
|
+
let now = 0;
|
|
5511
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
5512
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
|
|
5513
|
+
now += 31 * 1000;
|
|
5514
|
+
callback();
|
|
5515
|
+
return 0 as any;
|
|
5516
|
+
}) as typeof setTimeout);
|
|
5517
|
+
|
|
5518
|
+
(method as any).device = stubDevice({
|
|
5519
|
+
getCommands: () => ({ typedCall }),
|
|
5520
|
+
});
|
|
5521
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5522
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5523
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
5524
|
+
method.postProgressMessage = jest.fn();
|
|
5525
|
+
|
|
5526
|
+
await expect(
|
|
5527
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5528
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5529
|
+
])
|
|
5530
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.FirmwareError });
|
|
5531
|
+
|
|
5532
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5533
|
+
});
|
|
5534
|
+
|
|
5535
|
+
test('accepts ACK-less normal mode after the requested target version changes', async () => {
|
|
5536
|
+
const method = new FirmwareUpdateV4({
|
|
5537
|
+
id: 1,
|
|
5538
|
+
payload: {
|
|
5539
|
+
method: 'firmwareUpdateV4',
|
|
5540
|
+
},
|
|
5541
|
+
});
|
|
5542
|
+
const typedCall = jest
|
|
5543
|
+
.fn()
|
|
5544
|
+
.mockRejectedValue(new Error('Failure: Handler not registered for this message'));
|
|
5545
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
5546
|
+
const probeProtocolV2RuntimeState = jest.fn().mockResolvedValue({
|
|
5547
|
+
mode: 'normal',
|
|
5548
|
+
bootloaderMode: false,
|
|
5549
|
+
firmwareVersion: '2.0.0',
|
|
5550
|
+
});
|
|
5551
|
+
|
|
5552
|
+
(method as any).device = stubDevice({
|
|
5553
|
+
getCommands: () => ({ typedCall }),
|
|
5554
|
+
probeProtocolV2RuntimeState,
|
|
5555
|
+
});
|
|
5556
|
+
(method as any).protocolV2InstallBaselineVersions = new Map([[4, '1.0.0']]);
|
|
5557
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5558
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5559
|
+
method.postProgressMessage = jest.fn();
|
|
5560
|
+
|
|
5561
|
+
await expect(
|
|
5562
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete([
|
|
5563
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
5564
|
+
])
|
|
5565
|
+
).resolves.toBeUndefined();
|
|
5566
|
+
|
|
5567
|
+
expect(probeProtocolV2RuntimeState).toHaveBeenCalledWith(deviceInfo, 5000);
|
|
5568
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5569
|
+
});
|
|
5570
|
+
|
|
4997
5571
|
test.each([
|
|
4998
5572
|
[{ mode: 'normal', bootloaderMode: false }, true],
|
|
4999
5573
|
[{ mode: 'bootloader', bootloaderMode: true }, false],
|
|
@@ -5555,7 +6129,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5555
6129
|
);
|
|
5556
6130
|
});
|
|
5557
6131
|
|
|
5558
|
-
test('
|
|
6132
|
+
test('preserves component-first target order for the firmware-owned loader handoff', async () => {
|
|
5559
6133
|
const method = new FirmwareUpdateV4({
|
|
5560
6134
|
id: 1,
|
|
5561
6135
|
payload: {
|
|
@@ -5584,54 +6158,72 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5584
6158
|
.mockResolvedValue(undefined);
|
|
5585
6159
|
|
|
5586
6160
|
await (method as any).executeProtocolV2Update({
|
|
5587
|
-
|
|
5588
|
-
|
|
6161
|
+
// The active bootloader installs its component runners in records order and
|
|
6162
|
+
// leaves the bootloader record pending for romloader after reboot. Keep boot
|
|
6163
|
+
// deliberately after components to prove Core does not synthesize boot-first.
|
|
6164
|
+
installItems: [
|
|
5589
6165
|
{
|
|
5590
|
-
fileName: '
|
|
5591
|
-
binary: new Uint8Array([
|
|
5592
|
-
targetId:
|
|
6166
|
+
fileName: 'application_p1.bin',
|
|
6167
|
+
binary: new Uint8Array([8]).buffer,
|
|
6168
|
+
targetId: 4,
|
|
6169
|
+
kind: 'firmware',
|
|
5593
6170
|
},
|
|
5594
6171
|
{
|
|
5595
6172
|
fileName: 'se01.bin',
|
|
5596
6173
|
binary: new Uint8Array([7]).buffer,
|
|
5597
6174
|
targetId: 7,
|
|
6175
|
+
kind: 'firmware',
|
|
5598
6176
|
},
|
|
5599
6177
|
{
|
|
5600
|
-
fileName: '
|
|
5601
|
-
binary: new Uint8Array([
|
|
5602
|
-
targetId:
|
|
6178
|
+
fileName: 'bootloader.bin',
|
|
6179
|
+
binary: new Uint8Array([4, 5]).buffer,
|
|
6180
|
+
targetId: 3,
|
|
6181
|
+
kind: 'bootloader',
|
|
6182
|
+
},
|
|
6183
|
+
{
|
|
6184
|
+
fileName: 'coprocessor.bin',
|
|
6185
|
+
binary: new Uint8Array([6]).buffer,
|
|
6186
|
+
targetId: 6,
|
|
6187
|
+
kind: 'firmware',
|
|
5603
6188
|
},
|
|
5604
6189
|
],
|
|
5605
6190
|
});
|
|
5606
6191
|
|
|
5607
6192
|
expect(writtenPaths).toEqual([
|
|
6193
|
+
'vol0:/application_p1.bin',
|
|
6194
|
+
'vol0:/se01.bin',
|
|
5608
6195
|
'vol0:/bootloader.bin',
|
|
5609
6196
|
'vol0:/coprocessor.bin',
|
|
5610
|
-
'vol0:/se01.bin',
|
|
5611
|
-
'vol0:/application_p1.bin',
|
|
5612
6197
|
]);
|
|
5613
6198
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledTimes(4);
|
|
5614
6199
|
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenNthCalledWith(
|
|
5615
6200
|
2,
|
|
5616
|
-
'vol0:/
|
|
6201
|
+
'vol0:/se01.bin',
|
|
5617
6202
|
1
|
|
5618
6203
|
);
|
|
5619
|
-
expect((method as any).
|
|
5620
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
5621
|
-
|
|
5622
|
-
});
|
|
5623
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
6204
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6205
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6206
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
5624
6207
|
targets: [
|
|
5625
|
-
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5626
|
-
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
5627
6208
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6209
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
6210
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6211
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
5628
6212
|
],
|
|
5629
6213
|
});
|
|
6214
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalledTimes(1);
|
|
6215
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalledWith([
|
|
6216
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6217
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
6218
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6219
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
6220
|
+
]);
|
|
6221
|
+
expect((method as any).exitProtocolV2BootloaderToNormal).not.toHaveBeenCalled();
|
|
5630
6222
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
5631
|
-
expect((method as any).
|
|
6223
|
+
expect((method as any).completeProtocolV2FinalVerification).toHaveBeenCalledTimes(1);
|
|
5632
6224
|
});
|
|
5633
6225
|
|
|
5634
|
-
test('
|
|
6226
|
+
test('uses one global transfer range for resources and firmware', async () => {
|
|
5635
6227
|
const method = new FirmwareUpdateV4({
|
|
5636
6228
|
id: 1,
|
|
5637
6229
|
payload: {
|
|
@@ -5688,18 +6280,24 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5688
6280
|
],
|
|
5689
6281
|
});
|
|
5690
6282
|
|
|
5691
|
-
expect(method.
|
|
6283
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6284
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
5692
6285
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
5693
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
5694
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
6286
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
5695
6287
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5696
6288
|
1,
|
|
5697
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
6289
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
5698
6290
|
);
|
|
5699
6291
|
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
5700
6292
|
2,
|
|
5701
|
-
expect.objectContaining({ processedSize:
|
|
6293
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
5702
6294
|
);
|
|
6295
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
6296
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
6297
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
6298
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
6299
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
6300
|
+
});
|
|
5703
6301
|
expect((method as any).isProtocolV2ResourceBundleUpToDate).toHaveBeenCalledWith(
|
|
5704
6302
|
expect.objectContaining({
|
|
5705
6303
|
version: [1, 2, 3],
|
|
@@ -5709,50 +6307,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5709
6307
|
);
|
|
5710
6308
|
});
|
|
5711
6309
|
|
|
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 () => {
|
|
6310
|
+
test('removes stale boot resource staging before the combined transfer', async () => {
|
|
5756
6311
|
const method = new FirmwareUpdateV4({
|
|
5757
6312
|
id: 1,
|
|
5758
6313
|
payload: { method: 'firmwareUpdateV4' },
|
|
@@ -5779,18 +6334,18 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5779
6334
|
events.push('transfer-component');
|
|
5780
6335
|
return Promise.resolve();
|
|
5781
6336
|
});
|
|
5782
|
-
(method as any).
|
|
5783
|
-
events.push('
|
|
5784
|
-
return Promise.resolve();
|
|
6337
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockImplementation(() => {
|
|
6338
|
+
events.push('final-verify');
|
|
6339
|
+
return Promise.resolve({});
|
|
5785
6340
|
});
|
|
5786
|
-
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5787
6341
|
|
|
5788
6342
|
await (method as any).executeProtocolV2SourceUpdate({
|
|
5789
6343
|
installSources: [{ kind: 'firmware' }],
|
|
5790
6344
|
resourceSources: [],
|
|
5791
6345
|
});
|
|
5792
6346
|
|
|
5793
|
-
expect(
|
|
6347
|
+
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
6348
|
+
expect(events).toEqual(['delete-stale-staging', 'transfer-component', 'final-verify']);
|
|
5794
6349
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'FilesystemFileDelete', 'Success', {
|
|
5795
6350
|
path: 'vol0:/loaders/bootloader/boot_resource.okpkg.staging',
|
|
5796
6351
|
});
|
|
@@ -7137,6 +7692,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7137
7692
|
method.postProgressMessage = jest.fn();
|
|
7138
7693
|
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
7139
7694
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
7695
|
+
(method as any).ensureProtocolV2BootResourceStagingIsEmpty = jest
|
|
7696
|
+
.fn()
|
|
7697
|
+
.mockResolvedValue(undefined);
|
|
7140
7698
|
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
7141
7699
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7142
7700
|
|
|
@@ -7263,6 +7821,86 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
7263
7821
|
});
|
|
7264
7822
|
});
|
|
7265
7823
|
|
|
7824
|
+
test('sends one monotonic device transfer progress range across multiple files', async () => {
|
|
7825
|
+
const method = new FirmwareUpdateV4({
|
|
7826
|
+
id: 1,
|
|
7827
|
+
payload: {
|
|
7828
|
+
method: 'firmwareUpdateV4',
|
|
7829
|
+
},
|
|
7830
|
+
});
|
|
7831
|
+
const typedCall = jest.fn(
|
|
7832
|
+
(
|
|
7833
|
+
_name: string,
|
|
7834
|
+
_resType: string,
|
|
7835
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
7836
|
+
) =>
|
|
7837
|
+
Promise.resolve({
|
|
7838
|
+
type: 'FilesystemFile',
|
|
7839
|
+
message: {
|
|
7840
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
7841
|
+
},
|
|
7842
|
+
})
|
|
7843
|
+
);
|
|
7844
|
+
|
|
7845
|
+
(method as any).device = stubDevice({
|
|
7846
|
+
getCommands: () => ({ typedCall }),
|
|
7847
|
+
getCurrentDeviceType: () => 'pro2',
|
|
7848
|
+
toMessageObject: () => ({ connectId: 'firmware-device' }),
|
|
7849
|
+
});
|
|
7850
|
+
method.postMessage = jest.fn();
|
|
7851
|
+
method.postTipMessage = jest.fn();
|
|
7852
|
+
method.postProgressMessage = jest.fn();
|
|
7853
|
+
(method as any).isProtocolV2ResourceBundleUpToDate = jest.fn().mockResolvedValue(false);
|
|
7854
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
7855
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
7856
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
7857
|
+
.fn()
|
|
7858
|
+
.mockResolvedValue(undefined);
|
|
7859
|
+
|
|
7860
|
+
const resourceSource = await openFirmwareByteSource({
|
|
7861
|
+
binary: new Uint8Array(4097).buffer,
|
|
7862
|
+
});
|
|
7863
|
+
const firmwareSource = await openFirmwareByteSource({
|
|
7864
|
+
binary: new Uint8Array(4097).buffer,
|
|
7865
|
+
});
|
|
7866
|
+
try {
|
|
7867
|
+
await (method as any).executeProtocolV2TransferPhase({
|
|
7868
|
+
resourceSources: [
|
|
7869
|
+
{
|
|
7870
|
+
name: 'images.okpkg',
|
|
7871
|
+
source: resourceSource,
|
|
7872
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
7873
|
+
},
|
|
7874
|
+
],
|
|
7875
|
+
installSources: [
|
|
7876
|
+
{
|
|
7877
|
+
fileName: 'application_p1.bin',
|
|
7878
|
+
source: firmwareSource,
|
|
7879
|
+
targetId: 4,
|
|
7880
|
+
kind: 'firmware',
|
|
7881
|
+
},
|
|
7882
|
+
],
|
|
7883
|
+
});
|
|
7884
|
+
} finally {
|
|
7885
|
+
await resourceSource?.close();
|
|
7886
|
+
await firmwareSource?.close();
|
|
7887
|
+
}
|
|
7888
|
+
|
|
7889
|
+
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
7890
|
+
const deviceProgress = writePayloads.map(payload => payload.ui_percentage);
|
|
7891
|
+
expect(deviceProgress).toEqual([0, 50, 99, 100]);
|
|
7892
|
+
expect(deviceProgress.filter(progress => progress === 0)).toHaveLength(1);
|
|
7893
|
+
expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(1);
|
|
7894
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
7895
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
7896
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
7897
|
+
expect(
|
|
7898
|
+
(method.postProgressMessage as jest.Mock).mock.calls.filter(
|
|
7899
|
+
([progress, progressType]) => progress === 100 && progressType === 'transferData'
|
|
7900
|
+
)
|
|
7901
|
+
).toHaveLength(1);
|
|
7902
|
+
});
|
|
7903
|
+
|
|
7266
7904
|
// TODO(#850/#855): PR #855 added resume-on-retry and per-chunk retry on the
|
|
7267
7905
|
// writeProtocolV2File path. PR #850 replaced that path with FirmwareByteSource
|
|
7268
7906
|
// streaming (protocolV2SourceUpdateProcess), which restarts a failed transfer from
|