@onekeyfe/hd-core 1.2.0-alpha.110 → 1.2.0-alpha.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/__tests__/DeviceCommands.test.ts +2 -2
  2. package/__tests__/device-lifecycle-events.test.ts +1 -1
  3. package/__tests__/device-state-mapper.test.ts +4 -4
  4. package/__tests__/device-utils.test.ts +1 -1
  5. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -54
  6. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +36 -89
  7. package/__tests__/get-device-state.test.ts +8 -8
  8. package/__tests__/protocol-v2-resources.test.ts +20 -0
  9. package/__tests__/protocol-v2.test.ts +219 -240
  10. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts +0 -3
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  14. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  15. package/dist/index.d.ts +5 -20
  16. package/dist/index.js +139 -226
  17. package/dist/protocols/protocol-v2/features.d.ts +4 -4
  18. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  19. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  20. package/dist/types/settings.d.ts +4 -19
  21. package/dist/types/settings.d.ts.map +1 -1
  22. package/dist/utils/patch.d.ts +1 -1
  23. package/dist/utils/patch.d.ts.map +1 -1
  24. package/package.json +4 -4
  25. package/src/api/FirmwareUpdateV3.ts +16 -23
  26. package/src/api/FirmwareUpdateV4.ts +85 -178
  27. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  28. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  29. package/src/data/messages/messages-protocol-v2.json +31 -14
  30. package/src/device/DeviceStateMapper.ts +15 -15
  31. package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
  32. package/src/protocols/protocol-v2/features.ts +6 -6
  33. package/src/protocols/protocol-v2/resources.ts +10 -49
  34. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +1 -0
  35. package/src/types/settings.ts +4 -19
@@ -613,7 +613,7 @@ const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
613
613
  hw: {
614
614
  serial_no: 'PR9999999999',
615
615
  },
616
- fw: {
616
+ main_mcu: {
617
617
  bootloader: {
618
618
  version: '0.0.1',
619
619
  },
@@ -677,7 +677,7 @@ describe('Protocol V2 feature adapter', () => {
677
677
  const deviceInfo: ProtocolV2DeviceInfo = {
678
678
  protocol_version: 1,
679
679
  hw: { serial_no: 'P2-STATIC' },
680
- fw: { application: { version: '1.0.0' } },
680
+ main_mcu: { application: { version: '1.0.0' } },
681
681
  coprocessor: { bt_adv_name: 'Pro2 STATIC' },
682
682
  };
683
683
  const deviceStatus: DeviceStatus = {
@@ -751,7 +751,7 @@ describe('Protocol V2 feature adapter', () => {
751
751
  Device_type: DeviceType.PRO2,
752
752
  serial_no: 'PR2SERIAL',
753
753
  },
754
- fw: {
754
+ main_mcu: {
755
755
  romloader: {
756
756
  version: '0.1.0',
757
757
  build_id: 'rom-build',
@@ -841,7 +841,7 @@ describe('Protocol V2 feature adapter', () => {
841
841
  hw: {
842
842
  serial_no: 'PR2BOOT',
843
843
  },
844
- fw: {
844
+ main_mcu: {
845
845
  bootloader: {
846
846
  version: '0.2.0',
847
847
  },
@@ -863,7 +863,7 @@ describe('Protocol V2 feature adapter', () => {
863
863
  const features = normalizeProtocolV2Features(descriptor as any, {
864
864
  protocol_version: 1,
865
865
  hw: { serial_no: 'PR2NORMAL' },
866
- fw: { bootloader: { version: '0.2.0' } },
866
+ main_mcu: { bootloader: { version: '0.2.0' } },
867
867
  });
868
868
 
869
869
  expect(features.mode).toBe('normal');
@@ -876,7 +876,7 @@ describe('Protocol V2 feature adapter', () => {
876
876
  hw: {
877
877
  serial_no: 'PR2ROM',
878
878
  },
879
- fw: {
879
+ main_mcu: {
880
880
  romloader: {
881
881
  version: '1.0.0',
882
882
  },
@@ -917,17 +917,17 @@ describe('Protocol V2 feature adapter', () => {
917
917
 
918
918
  expect(
919
919
  getProtocolV2RuntimeMode(legacyProtocolInfo, {
920
- fw: { bootloader: { version: '0.2.0' } },
920
+ main_mcu: { bootloader: { version: '0.2.0' } },
921
921
  })
922
922
  ).toBe('bootloader');
923
923
  expect(
924
924
  getProtocolV2RuntimeMode(legacyProtocolInfo, {
925
- fw: { romloader: { version: '1.0.0' } },
925
+ main_mcu: { romloader: { version: '1.0.0' } },
926
926
  })
927
927
  ).toBe('romloader');
928
928
  expect(
929
929
  getProtocolV2RuntimeMode(legacyProtocolInfo, {
930
- fw: {
930
+ main_mcu: {
931
931
  application: { version: '1.2.3' },
932
932
  bootloader: { version: '0.2.0' },
933
933
  },
@@ -1998,7 +1998,7 @@ describe('Protocol V2 feature adapter', () => {
1998
1998
  { ...descriptor, protocolType: 'V2' } as any,
1999
1999
  {
2000
2000
  protocol_version: 2,
2001
- fw: { application: { version: '1.2.3' } },
2001
+ main_mcu: { application: { version: '1.2.3' } },
2002
2002
  se1: { application: { version: '4.5.6' } },
2003
2003
  status: { unlocked: false, passphrase_enabled: false },
2004
2004
  }
@@ -2035,7 +2035,7 @@ describe('Protocol V2 feature adapter', () => {
2035
2035
  deviceInfo: {
2036
2036
  protocol_version: 1,
2037
2037
  hw: { serial_no: 'PR9999999999' },
2038
- fw: { application: { version: '1.0.0' } },
2038
+ main_mcu: { application: { version: '1.0.0' } },
2039
2039
  },
2040
2040
  deviceStatus: {
2041
2041
  device_id: 'firmware-reconnect-id',
@@ -2400,7 +2400,7 @@ describe('Protocol V2 feature adapter', () => {
2400
2400
  (device as any).features = {
2401
2401
  ...normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
2402
2402
  hw: { serial_no: 'PR2SERIAL' },
2403
- fw: { application: { version: '4.15.0' } },
2403
+ main_mcu: { application: { version: '4.15.0' } },
2404
2404
  status: { passphrase_enabled: true },
2405
2405
  }),
2406
2406
  unlocked: true,
@@ -2825,7 +2825,7 @@ describe('Protocol V2 feature adapter', () => {
2825
2825
  type: 'DeviceInfo',
2826
2826
  message: {
2827
2827
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
2828
- fw: { application: { version: '1.2.3' } },
2828
+ main_mcu: { application: { version: '1.2.3' } },
2829
2829
  },
2830
2830
  }),
2831
2831
  };
@@ -2846,7 +2846,7 @@ describe('Protocol V2 feature adapter', () => {
2846
2846
  {
2847
2847
  targets: {
2848
2848
  hw: true,
2849
- fw: true,
2849
+ main_mcu: true,
2850
2850
  coprocessor: true,
2851
2851
  },
2852
2852
  types: {
@@ -2955,7 +2955,7 @@ describe('Protocol V2 feature adapter', () => {
2955
2955
  const device = stubDevice({
2956
2956
  originalDescriptor: { protocolType: 'V2' },
2957
2957
  features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any, {
2958
- fw: {
2958
+ main_mcu: {
2959
2959
  application: {
2960
2960
  version: '4.14.0',
2961
2961
  },
@@ -3009,7 +3009,7 @@ describe('Protocol V2 feature adapter', () => {
3009
3009
  type: 'DeviceInfo',
3010
3010
  message: {
3011
3011
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3012
- fw: { application: { version: '1.2.3' } },
3012
+ main_mcu: { application: { version: '1.2.3' } },
3013
3013
  },
3014
3014
  })
3015
3015
  .mockResolvedValueOnce({
@@ -3044,7 +3044,7 @@ describe('Protocol V2 feature adapter', () => {
3044
3044
  {
3045
3045
  targets: {
3046
3046
  hw: true,
3047
- fw: true,
3047
+ main_mcu: true,
3048
3048
  coprocessor: true,
3049
3049
  },
3050
3050
  types: {
@@ -3073,7 +3073,7 @@ describe('Protocol V2 feature adapter', () => {
3073
3073
  type: 'DeviceInfo',
3074
3074
  message: {
3075
3075
  hw: { serial_no: 'PR2SERIAL' },
3076
- fw: { bootloader: { version: '0.2.0' } },
3076
+ main_mcu: { bootloader: { version: '0.2.0' } },
3077
3077
  se1: {},
3078
3078
  },
3079
3079
  })
@@ -3123,7 +3123,7 @@ describe('Protocol V2 feature adapter', () => {
3123
3123
 
3124
3124
  await device.probeProtocolV2RuntimeState({
3125
3125
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3126
- fw: { romloader: { version: '1.0.0' } },
3126
+ main_mcu: { romloader: { version: '1.0.0' } },
3127
3127
  });
3128
3128
 
3129
3129
  expect(typedCall).toHaveBeenCalledTimes(1);
@@ -3151,7 +3151,7 @@ describe('Protocol V2 feature adapter', () => {
3151
3151
  await expect(
3152
3152
  device.probeProtocolV2RuntimeState({
3153
3153
  hw: { Device_type: DeviceType.PRO, serial_no: 'PRO-SERIAL' },
3154
- fw: { romloader: { version: '1.0.0' } },
3154
+ main_mcu: { romloader: { version: '1.0.0' } },
3155
3155
  })
3156
3156
  ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceInitializeFailed });
3157
3157
 
@@ -3176,7 +3176,7 @@ describe('Protocol V2 feature adapter', () => {
3176
3176
 
3177
3177
  await device.probeProtocolV2RuntimeState({
3178
3178
  hw: { serial_no: 'PR2SERIAL' },
3179
- fw: { application: { version: '1.2.3' } },
3179
+ main_mcu: { application: { version: '1.2.3' } },
3180
3180
  });
3181
3181
 
3182
3182
  expect(typedCall).toHaveBeenCalledTimes(1);
@@ -3208,7 +3208,7 @@ describe('Protocol V2 feature adapter', () => {
3208
3208
 
3209
3209
  await device.probeProtocolV2RuntimeState({
3210
3210
  hw: { serial_no: 'PR2SERIAL' },
3211
- fw: { application: { version: '1.2.3' } },
3211
+ main_mcu: { application: { version: '1.2.3' } },
3212
3212
  });
3213
3213
 
3214
3214
  expect(typedCall).toHaveBeenCalledTimes(2);
@@ -3236,7 +3236,7 @@ describe('Protocol V2 feature adapter', () => {
3236
3236
 
3237
3237
  await device.probeProtocolV2RuntimeState({
3238
3238
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3239
- fw: { bootloader: { version: '0.2.0' } },
3239
+ main_mcu: { bootloader: { version: '0.2.0' } },
3240
3240
  });
3241
3241
 
3242
3242
  expect(device.features).toMatchObject({
@@ -3265,7 +3265,7 @@ describe('Protocol V2 feature adapter', () => {
3265
3265
  await expect(
3266
3266
  device.probeProtocolV2RuntimeState({
3267
3267
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3268
- fw: { application: { version: '1.2.3' } },
3268
+ main_mcu: { application: { version: '1.2.3' } },
3269
3269
  })
3270
3270
  ).rejects.toBe(linkError);
3271
3271
 
@@ -3290,7 +3290,7 @@ describe('Protocol V2 feature adapter', () => {
3290
3290
  await expect(
3291
3291
  device.probeProtocolV2RuntimeState({
3292
3292
  hw: { serial_no: 'PR2SERIAL' },
3293
- fw: { application: { version: '1.2.3' } },
3293
+ main_mcu: { application: { version: '1.2.3' } },
3294
3294
  })
3295
3295
  ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceInitializeFailed });
3296
3296
  expect(typedCall).toHaveBeenCalledTimes(1);
@@ -3307,7 +3307,7 @@ describe('Protocol V2 feature adapter', () => {
3307
3307
  type: 'DeviceInfo',
3308
3308
  message: {
3309
3309
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3310
- fw: { bootloader: { version: '0.2.0' } },
3310
+ main_mcu: { bootloader: { version: '0.2.0' } },
3311
3311
  },
3312
3312
  })
3313
3313
  .mockResolvedValueOnce({
@@ -3357,7 +3357,7 @@ describe('Protocol V2 feature adapter', () => {
3357
3357
  await device.probeProtocolV2RuntimeState(
3358
3358
  {
3359
3359
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3360
- fw: { application: { version: '1.2.3' } },
3360
+ main_mcu: { application: { version: '1.2.3' } },
3361
3361
  },
3362
3362
  5000
3363
3363
  );
@@ -3402,7 +3402,7 @@ describe('Protocol V2 feature adapter', () => {
3402
3402
 
3403
3403
  await device.probeProtocolV2RuntimeState({
3404
3404
  hw: { Device_type: DeviceType.NEO, serial_no: 'NEOSERIAL' },
3405
- fw: { bootloader: { version: '0.2.0' } },
3405
+ main_mcu: { bootloader: { version: '0.2.0' } },
3406
3406
  });
3407
3407
 
3408
3408
  expect(typedCall).toHaveBeenNthCalledWith(1, 'ProtocolInfoRequest', 'ProtocolInfo', {
@@ -3436,7 +3436,7 @@ describe('Protocol V2 feature adapter', () => {
3436
3436
 
3437
3437
  await device.probeProtocolV2RuntimeState({
3438
3438
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3439
- fw: { romloader: { version: '1.0.0' } },
3439
+ main_mcu: { romloader: { version: '1.0.0' } },
3440
3440
  });
3441
3441
 
3442
3442
  expect(typedCall).toHaveBeenCalledTimes(1);
@@ -3474,7 +3474,7 @@ describe('Protocol V2 feature adapter', () => {
3474
3474
  await expect(
3475
3475
  device.probeProtocolV2RuntimeState({
3476
3476
  hw: { serial_no: 'PR2SERIAL' },
3477
- fw: { application: { version: '1.0.0' } },
3477
+ main_mcu: { application: { version: '1.0.0' } },
3478
3478
  })
3479
3479
  ).rejects.toThrow('state update failed');
3480
3480
 
@@ -3499,7 +3499,7 @@ describe('Protocol V2 feature adapter', () => {
3499
3499
  type: 'DeviceInfo',
3500
3500
  message: {
3501
3501
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3502
- fw: { application: { version: '1.2.3' } },
3502
+ main_mcu: { application: { version: '1.2.3' } },
3503
3503
  },
3504
3504
  })
3505
3505
  .mockResolvedValueOnce({
@@ -3514,7 +3514,7 @@ describe('Protocol V2 feature adapter', () => {
3514
3514
  type: 'DeviceInfo',
3515
3515
  message: {
3516
3516
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3517
- fw: { application: { version: '1.2.4' } },
3517
+ main_mcu: { application: { version: '1.2.4' } },
3518
3518
  },
3519
3519
  })
3520
3520
  .mockResolvedValueOnce({
@@ -3546,7 +3546,7 @@ describe('Protocol V2 feature adapter', () => {
3546
3546
  {
3547
3547
  targets: {
3548
3548
  hw: true,
3549
- fw: true,
3549
+ main_mcu: true,
3550
3550
  coprocessor: true,
3551
3551
  },
3552
3552
  types: {
@@ -3567,7 +3567,7 @@ describe('Protocol V2 feature adapter', () => {
3567
3567
  type: 'DeviceInfo',
3568
3568
  message: {
3569
3569
  hw: { serial_no: 'PR2SERIAL' },
3570
- fw: { application: { version: '1.2.4' } },
3570
+ main_mcu: { application: { version: '1.2.4' } },
3571
3571
  },
3572
3572
  })
3573
3573
  .mockResolvedValueOnce({
@@ -3644,7 +3644,7 @@ describe('Protocol V2 feature adapter', () => {
3644
3644
  { ...descriptor, protocolType: 'V2' } as any,
3645
3645
  {
3646
3646
  hw: { Device_type: DeviceType.PRO2, serial_no: 'PR2SERIAL' },
3647
- fw: { application: { version: '1.2.3' } },
3647
+ main_mcu: { application: { version: '1.2.3' } },
3648
3648
  status: { init_states: true },
3649
3649
  }
3650
3650
  );
@@ -3684,7 +3684,7 @@ describe('Protocol V2 feature adapter', () => {
3684
3684
  { ...descriptor, protocolType: 'V2' } as any,
3685
3685
  {
3686
3686
  hw: { serial_no: 'PR2SERIAL' },
3687
- fw: { application: { version: '4.15.0' } },
3687
+ main_mcu: { application: { version: '4.15.0' } },
3688
3688
  status: { passphrase_enabled: false },
3689
3689
  }
3690
3690
  );
@@ -3731,7 +3731,7 @@ describe('Protocol V2 feature adapter', () => {
3731
3731
  (device as any).features = normalizeProtocolV2Features(
3732
3732
  { ...descriptor, protocolType: 'V2' } as any,
3733
3733
  {
3734
- fw: { application: { version: '1.2.3' } },
3734
+ main_mcu: { application: { version: '1.2.3' } },
3735
3735
  status: { unlocked: false },
3736
3736
  }
3737
3737
  );
@@ -4160,7 +4160,7 @@ describe('Protocol V2 firmware update targets', () => {
4160
4160
  type: 'DeviceInfo',
4161
4161
  message: {
4162
4162
  hw: { serial_no: 'BLE-PRO2-SERIAL' },
4163
- fw: {
4163
+ main_mcu: {
4164
4164
  bootloader: { version: '0.0.0' },
4165
4165
  application: { version: '0.0.0' },
4166
4166
  },
@@ -4212,7 +4212,7 @@ describe('Protocol V2 firmware update targets', () => {
4212
4212
  {
4213
4213
  targets: {
4214
4214
  hw: true,
4215
- fw: true,
4215
+ main_mcu: true,
4216
4216
  coprocessor: true,
4217
4217
  se1: true,
4218
4218
  se2: true,
@@ -4631,43 +4631,6 @@ describe('Protocol V2 firmware update targets', () => {
4631
4631
  expect(method.postTipMessage).toHaveBeenCalledWith('GoToBootloaderSuccess');
4632
4632
  });
4633
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
-
4671
4634
  test('keeps Protocol V2 romloader active before firmware transfer', async () => {
4672
4635
  const method = new FirmwareUpdateV4({
4673
4636
  id: 1,
@@ -5116,7 +5079,7 @@ describe('Protocol V2 firmware update targets', () => {
5116
5079
  type: 'DeviceInfo',
5117
5080
  message: {
5118
5081
  hw: { serial_no: 'PR2SERIAL' },
5119
- fw: { application: { version: '1.0.0' } },
5082
+ main_mcu: { application: { version: '1.0.0' } },
5120
5083
  },
5121
5084
  });
5122
5085
  (method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
@@ -5243,49 +5206,50 @@ describe('Protocol V2 firmware update targets', () => {
5243
5206
  );
5244
5207
  });
5245
5208
 
5246
- test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
5209
+ test('stages targets before sending the empty Protocol V2 install request', async () => {
5247
5210
  const method = new FirmwareUpdateV4({
5248
5211
  id: 1,
5249
5212
  payload: {
5250
5213
  method: 'firmwareUpdateV4',
5251
5214
  },
5252
5215
  });
5253
- const typedCall = jest
5254
- .fn()
5255
- .mockRejectedValue(
5256
- Object.assign(
5257
- new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
5258
- { name: 'NotFoundError' }
5259
- )
5260
- );
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: {} });
5261
5219
 
5262
5220
  (method as any).device = stubDevice({
5263
- getCommands: () => ({ typedCall }),
5221
+ getCommands: () => ({ typedCall, call }),
5264
5222
  });
5265
5223
  method.postTipMessage = jest.fn();
5266
5224
  method.postProgressMessage = jest.fn();
5267
5225
 
5268
5226
  await expect(
5269
- (method as any).protocolV2StartFirmwareUpdate({
5270
- targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
5271
- })
5272
- ).rejects.toThrow();
5227
+ (method as any).protocolV2StartFirmwareUpdate({ targets })
5228
+ ).resolves.toBeUndefined();
5273
5229
 
5274
- expect(method.postTipMessage).not.toHaveBeenCalled();
5275
- expect(method.postProgressMessage).not.toHaveBeenCalled();
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');
5276
5239
  });
5277
5240
 
5278
- test('does not poll Protocol V2 install status when the update ACK times out', async () => {
5241
+ test('does not send the install request when Protocol V2 staging fails', async () => {
5279
5242
  const method = new FirmwareUpdateV4({
5280
5243
  id: 1,
5281
5244
  payload: {
5282
5245
  method: 'firmwareUpdateV4',
5283
5246
  },
5284
5247
  });
5285
- const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
5248
+ const typedCall = jest.fn().mockRejectedValue(new Error('stage failed'));
5249
+ const call = jest.fn();
5286
5250
 
5287
5251
  (method as any).device = stubDevice({
5288
- getCommands: () => ({ typedCall }),
5252
+ getCommands: () => ({ typedCall, call }),
5289
5253
  });
5290
5254
  method.postTipMessage = jest.fn();
5291
5255
  method.postProgressMessage = jest.fn();
@@ -5294,78 +5258,26 @@ describe('Protocol V2 firmware update targets', () => {
5294
5258
  (method as any).protocolV2StartFirmwareUpdate({
5295
5259
  targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
5296
5260
  })
5297
- ).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
5261
+ ).rejects.toThrow('stage failed');
5298
5262
 
5263
+ expect(call).not.toHaveBeenCalled();
5299
5264
  expect(method.postTipMessage).not.toHaveBeenCalled();
5300
5265
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5301
5266
  });
5302
5267
 
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 () => {
5268
+ test('does not enter install state when writing the empty request fails', async () => {
5356
5269
  const method = new FirmwareUpdateV4({
5357
5270
  id: 1,
5358
5271
  payload: {
5359
5272
  method: 'firmwareUpdateV4',
5360
5273
  },
5361
5274
  });
5362
- const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
5275
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5276
+ const call = jest.fn().mockRejectedValue(new Error('write failed'));
5363
5277
 
5364
5278
  (method as any).device = stubDevice({
5365
- getCommands: () => ({ typedCall }),
5279
+ getCommands: () => ({ typedCall, call }),
5366
5280
  });
5367
- (method as any).protocolV2LegacyDirectUpdate = true;
5368
- (method as any).rebootProtocolV2ToBootloader = jest.fn();
5369
5281
  method.postTipMessage = jest.fn();
5370
5282
  method.postProgressMessage = jest.fn();
5371
5283
 
@@ -5373,10 +5285,9 @@ describe('Protocol V2 firmware update targets', () => {
5373
5285
  (method as any).protocolV2StartFirmwareUpdate({
5374
5286
  targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
5375
5287
  })
5376
- ).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
5288
+ ).rejects.toThrow('write failed');
5377
5289
 
5378
- expect(typedCall).toHaveBeenCalledTimes(1);
5379
- expect((method as any).rebootProtocolV2ToBootloader).not.toHaveBeenCalled();
5290
+ expect(call).toHaveBeenCalledTimes(1);
5380
5291
  expect(method.postTipMessage).not.toHaveBeenCalled();
5381
5292
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5382
5293
  });
@@ -5424,26 +5335,21 @@ describe('Protocol V2 firmware update targets', () => {
5424
5335
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5425
5336
  });
5426
5337
 
5427
- test('accepts a missing firmware status handler only after confirming normal mode', async () => {
5338
+ test('accepts the terminal Success returned by the install request', async () => {
5428
5339
  const method = new FirmwareUpdateV4({
5429
5340
  id: 1,
5430
5341
  payload: {
5431
5342
  method: 'firmwareUpdateV4',
5432
5343
  },
5433
5344
  });
5434
- const typedCall = jest
5435
- .fn()
5436
- .mockRejectedValue(new Error('Failure: Handler not registered for this message'));
5345
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5437
5346
  const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
5438
5347
 
5439
5348
  (method as any).device = stubDevice({
5440
5349
  getCommands: () => ({ typedCall }),
5441
5350
  });
5442
5351
  (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
5443
- const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
5444
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
5445
- (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
5446
- (method as any).protocolV2InstallAckReceived = true;
5352
+ (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
5447
5353
  method.postProgressMessage = jest.fn();
5448
5354
 
5449
5355
  await (method as any).waitForProtocolV2FirmwareUpdateComplete([
@@ -5452,47 +5358,7 @@ describe('Protocol V2 firmware update targets', () => {
5452
5358
 
5453
5359
  expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
5454
5360
  expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
5455
- expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
5456
- expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5457
- });
5458
-
5459
- test('accepts an empty firmware status dump only after confirming normal mode', async () => {
5460
- const method = new FirmwareUpdateV4({
5461
- id: 1,
5462
- payload: {
5463
- method: 'firmwareUpdateV4',
5464
- },
5465
- });
5466
- const typedCall = jest.fn().mockResolvedValue({
5467
- type: 'DeviceFirmwareUpdateStatus',
5468
- message: { records: [] },
5469
- });
5470
- const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
5471
- let now = 0;
5472
- jest.spyOn(Date, 'now').mockImplementation(() => now);
5473
- jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
5474
- now += 31 * 1000;
5475
- callback();
5476
- return 0 as any;
5477
- }) as typeof setTimeout);
5478
-
5479
- (method as any).device = stubDevice({
5480
- getCommands: () => ({ typedCall }),
5481
- });
5482
- (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
5483
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
5484
- (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
5485
- (method as any).protocolV2InstallAckReceived = true;
5486
- method.postProgressMessage = jest.fn();
5487
-
5488
- await expect(
5489
- (method as any).waitForProtocolV2FirmwareUpdateComplete([
5490
- { target_id: 4, path: 'vol0:/application_p1.bin' },
5491
- ])
5492
- ).resolves.toBeUndefined();
5493
-
5494
- expect(typedCall).toHaveBeenCalledTimes(1);
5495
- expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
5361
+ expect((method as any).protocolV2FinalStatusVerified).toBe(true);
5496
5362
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5497
5363
  });
5498
5364
 
@@ -5806,7 +5672,7 @@ describe('Protocol V2 firmware update targets', () => {
5806
5672
  );
5807
5673
  });
5808
5674
 
5809
- test('polls target status after update ACK and finishes when all targets complete', async () => {
5675
+ test('polls target status after the install request and finishes when all targets complete', async () => {
5810
5676
  const method = new FirmwareUpdateV4({
5811
5677
  id: 1,
5812
5678
  payload: {
@@ -5841,7 +5707,7 @@ describe('Protocol V2 firmware update targets', () => {
5841
5707
  expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
5842
5708
  expect(typedCall).toHaveBeenCalledWith(
5843
5709
  'DeviceFirmwareUpdateStatusGet',
5844
- 'DeviceFirmwareUpdateStatus',
5710
+ ['DeviceFirmwareUpdateStatus', 'Success'],
5845
5711
  expect.anything(),
5846
5712
  expect.anything()
5847
5713
  );
@@ -6117,7 +5983,7 @@ describe('Protocol V2 firmware update targets', () => {
6117
5983
  });
6118
5984
  expect(typedCall).toHaveBeenCalledWith(
6119
5985
  'DeviceFirmwareUpdateStatusGet',
6120
- 'DeviceFirmwareUpdateStatus',
5986
+ ['DeviceFirmwareUpdateStatus', 'Success'],
6121
5987
  {
6122
5988
  fields: {
6123
5989
  status: true,
@@ -7317,9 +7183,10 @@ describe('Protocol V2 firmware update targets', () => {
7317
7183
  ],
7318
7184
  };
7319
7185
  const zip = new JSZip();
7320
- zip.file('manifest.json', JSON.stringify(manifest));
7321
- zip.file('bundles/images/images.okpkg', imagesBinary);
7322
- 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');
7323
7190
  const resourceArchiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
7324
7191
  const applicationP1Binary = new Uint8Array([7, 8, 9]).buffer;
7325
7192
  const bootloaderBinary = new Uint8Array([10, 11, 12]).buffer;
@@ -7395,6 +7262,39 @@ describe('Protocol V2 firmware update targets', () => {
7395
7262
  }
7396
7263
  });
7397
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
+
7398
7298
  test('rejects a local resource ZIP whose file hash does not match its manifest', async () => {
7399
7299
  const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
7400
7300
  const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
@@ -7485,18 +7385,57 @@ describe('Protocol V2 firmware update targets', () => {
7485
7385
 
7486
7386
  test('rejects an oversized ZIP entry before allocating its decompressed bytes', async () => {
7487
7387
  const extractEntry = jest.fn();
7488
- const loadSpy = jest.spyOn(JSZip, 'loadAsync').mockResolvedValue({
7489
- files: {
7490
- 'manifest.json': {
7491
- name: 'manifest.json',
7492
- dir: false,
7493
- _data: {
7494
- compressedSize: 1,
7495
- uncompressedSize: 2 * 1024 * 1024,
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',
7496
7400
  },
7497
- async: extractEntry,
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,
7498
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,
7499
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,
7500
7439
  } as unknown as JSZip);
7501
7440
  const method = new FirmwareUpdateV4({
7502
7441
  id: 1,
@@ -7511,7 +7450,7 @@ describe('Protocol V2 firmware update targets', () => {
7511
7450
  try {
7512
7451
  await expect(
7513
7452
  (method as any).prepareProtocolV2LocalResourceArchive(new Uint8Array([1]).buffer)
7514
- ).rejects.toThrow('declared size exceeds the allowed limit');
7453
+ ).rejects.toThrow('declared size is invalid');
7515
7454
  expect(extractEntry).not.toHaveBeenCalled();
7516
7455
  } finally {
7517
7456
  loadSpy.mockRestore();
@@ -7807,7 +7746,7 @@ describe('Protocol V2 firmware update targets', () => {
7807
7746
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([4000, 97]);
7808
7747
  expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
7809
7748
  expect(writePayloads.every(payload => payload.append === false)).toBe(true);
7810
- expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
7749
+ expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 99]);
7811
7750
  expect(method.postMessage).toHaveBeenLastCalledWith({
7812
7751
  event: 'UI_EVENT',
7813
7752
  type: UI_REQUEST.FIRMWARE_PROGRESS,
@@ -7888,9 +7827,9 @@ describe('Protocol V2 firmware update targets', () => {
7888
7827
 
7889
7828
  const writePayloads = typedCall.mock.calls.map(call => call[2]);
7890
7829
  const deviceProgress = writePayloads.map(payload => payload.ui_percentage);
7891
- expect(deviceProgress).toEqual([0, 50, 99, 100]);
7830
+ expect(deviceProgress).toEqual([0, 50, 99, 99]);
7892
7831
  expect(deviceProgress.filter(progress => progress === 0)).toHaveLength(1);
7893
- expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(1);
7832
+ expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(0);
7894
7833
  expect(method.postTipMessage).toHaveBeenCalledTimes(2);
7895
7834
  expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
7896
7835
  expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
@@ -8208,7 +8147,7 @@ describe('Protocol V2 firmware update targets', () => {
8208
8147
  const writePayloads = typedCall.mock.calls.map(call => call[2]);
8209
8148
  expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
8210
8149
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
8211
- expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
8150
+ expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 99]);
8212
8151
  expect(typedCall.mock.calls.map(call => call[3])).toEqual([
8213
8152
  expect.objectContaining({
8214
8153
  writeWithResponse: false,
@@ -8267,23 +8206,24 @@ describe('Protocol V2 firmware update targets', () => {
8267
8206
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
8268
8207
  });
8269
8208
 
8270
- test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
8209
+ test('starts install progress only after Stage and the empty Request are written', async () => {
8271
8210
  const method = new FirmwareUpdateV4({
8272
8211
  id: 1,
8273
8212
  payload: {
8274
8213
  method: 'firmwareUpdateV4',
8275
8214
  },
8276
8215
  });
8277
- let resolveRequest: ((value: unknown) => void) | undefined;
8278
- const typedCall = jest.fn().mockImplementation(
8216
+ let resolveWrite: ((value: unknown) => void) | undefined;
8217
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
8218
+ const call = jest.fn().mockImplementation(
8279
8219
  () =>
8280
8220
  new Promise(resolve => {
8281
- resolveRequest = resolve;
8221
+ resolveWrite = resolve;
8282
8222
  })
8283
8223
  );
8284
8224
 
8285
8225
  (method as any).device = stubDevice({
8286
- getCommands: () => ({ typedCall }),
8226
+ getCommands: () => ({ typedCall, call }),
8287
8227
  });
8288
8228
  method.postProgressMessage = jest.fn();
8289
8229
  method.postTipMessage = jest.fn();
@@ -8291,21 +8231,31 @@ describe('Protocol V2 firmware update targets', () => {
8291
8231
  const startPromise = (method as any).protocolV2StartFirmwareUpdate({
8292
8232
  targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
8293
8233
  });
8294
- await Promise.resolve();
8234
+ await new Promise<void>(resolve => {
8235
+ setImmediate(resolve);
8236
+ });
8295
8237
 
8296
- expect(typedCall.mock.calls[0][1]).toBe('Success');
8297
- expect(typedCall.mock.calls[0][3]).toEqual(expect.objectContaining({ timeoutMs: 180_000 }));
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
+ );
8298
8248
  expect(method.postTipMessage).not.toHaveBeenCalled();
8299
8249
  expect(method.postProgressMessage).not.toHaveBeenCalled();
8300
8250
 
8301
- resolveRequest?.({ type: 'Success', message: { message: 'ok' } });
8251
+ resolveWrite?.({ type: 'WriteCompleted', message: {} });
8302
8252
  await startPromise;
8303
8253
  expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
8304
8254
  expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
8305
8255
  expect(method.postTipMessage).toHaveBeenCalledTimes(1);
8306
8256
  });
8307
8257
 
8308
- test('requests only a Success ACK for Protocol V2 firmware install', async () => {
8258
+ test('sends an empty Protocol V2 firmware install request without waiting for its response', async () => {
8309
8259
  const method = new FirmwareUpdateV4({
8310
8260
  id: 1,
8311
8261
  payload: {
@@ -8316,9 +8266,10 @@ describe('Protocol V2 firmware update targets', () => {
8316
8266
  type: 'Success',
8317
8267
  message: { message: 'accepted' },
8318
8268
  });
8269
+ const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
8319
8270
 
8320
8271
  (method as any).device = stubDevice({
8321
- getCommands: () => ({ typedCall }),
8272
+ getCommands: () => ({ typedCall, call }),
8322
8273
  });
8323
8274
  method.postProgressMessage = jest.fn();
8324
8275
  method.postTipMessage = jest.fn();
@@ -8327,7 +8278,12 @@ describe('Protocol V2 firmware update targets', () => {
8327
8278
  targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
8328
8279
  });
8329
8280
 
8330
- expect(typedCall.mock.calls[0][1]).toBe('Success');
8281
+ expect(typedCall.mock.calls[0][0]).toBe('DeviceFirmwareUpdateStage');
8282
+ expect(call).toHaveBeenCalledWith(
8283
+ 'DeviceFirmwareUpdateRequest',
8284
+ {},
8285
+ { returnAfterWrite: true }
8286
+ );
8331
8287
  expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
8332
8288
  });
8333
8289
  });
@@ -9222,6 +9178,29 @@ describe('Protocol V2 protected method execution', () => {
9222
9178
  }
9223
9179
  );
9224
9180
 
9181
+ test('skips DeviceStatus and unlock when Protocol V2 is not initialized', async () => {
9182
+ const method = {
9183
+ name: 'firmwareUpdateV4',
9184
+ unlockPolicy: 'unlock-before-run',
9185
+ run: jest.fn().mockResolvedValue({ message: 'updating' }),
9186
+ };
9187
+ const device = {
9188
+ state: { status: { initialized: false } },
9189
+ commands: { typedCall: jest.fn() },
9190
+ isProtocolV2: () => true,
9191
+ isBootloader: () => false,
9192
+ isRomloader: () => false,
9193
+ unlockDevice: jest.fn(),
9194
+ };
9195
+
9196
+ await expect(runMethodWithUnlockPolicy(method as any, device as any)).resolves.toEqual({
9197
+ message: 'updating',
9198
+ });
9199
+ expect(device.commands.typedCall).not.toHaveBeenCalled();
9200
+ expect(device.unlockDevice).not.toHaveBeenCalled();
9201
+ expect(method.run).toHaveBeenCalledTimes(1);
9202
+ });
9203
+
9225
9204
  test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
9226
9205
  for (const [isProtocolV2, unlockPolicy] of [
9227
9206
  [false, 'unlock-before-run'],
@@ -9401,7 +9380,7 @@ describe('Protocol V2 current low-level methods', () => {
9401
9380
  info: {
9402
9381
  version: undefined,
9403
9382
  serial_number: 'PR2SERIAL',
9404
- burn_in_completed: true,
9383
+ factory_burn_in_completed: true,
9405
9384
  factory_test_completed: undefined,
9406
9385
  manufacture_time: undefined,
9407
9386
  },
@@ -9651,7 +9630,7 @@ describe('Protocol V2 raw device info method', () => {
9651
9630
  'DeviceInfoGet',
9652
9631
  'DeviceInfo',
9653
9632
  {
9654
- targets: { hw: true, fw: true, coprocessor: true },
9633
+ targets: { hw: true, main_mcu: true, coprocessor: true },
9655
9634
  types: { version: true, specific: true },
9656
9635
  },
9657
9636
  { timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }