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

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 +116 -0
  6. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +89 -36
  7. package/__tests__/get-device-state.test.ts +8 -8
  8. package/__tests__/protocol-v2-resources.test.ts +0 -20
  9. package/__tests__/protocol-v2.test.ts +240 -219
  10. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts +3 -0
  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 +20 -5
  16. package/dist/index.js +230 -140
  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 +19 -4
  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 +29 -19
  26. package/src/api/FirmwareUpdateV4.ts +178 -85
  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 +14 -31
  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 +49 -10
  34. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -1
  35. package/src/types/settings.ts +19 -4
@@ -613,7 +613,7 @@ const protocolV2BootloaderDeviceInfo: ProtocolV2DeviceInfo = {
613
613
  hw: {
614
614
  serial_no: 'PR9999999999',
615
615
  },
616
- main_mcu: {
616
+ fw: {
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
- main_mcu: { application: { version: '1.0.0' } },
680
+ fw: { 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
- main_mcu: {
754
+ fw: {
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
- main_mcu: {
844
+ fw: {
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
- main_mcu: { bootloader: { version: '0.2.0' } },
866
+ fw: { 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
- main_mcu: {
879
+ fw: {
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
- main_mcu: { bootloader: { version: '0.2.0' } },
920
+ fw: { bootloader: { version: '0.2.0' } },
921
921
  })
922
922
  ).toBe('bootloader');
923
923
  expect(
924
924
  getProtocolV2RuntimeMode(legacyProtocolInfo, {
925
- main_mcu: { romloader: { version: '1.0.0' } },
925
+ fw: { romloader: { version: '1.0.0' } },
926
926
  })
927
927
  ).toBe('romloader');
928
928
  expect(
929
929
  getProtocolV2RuntimeMode(legacyProtocolInfo, {
930
- main_mcu: {
930
+ fw: {
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
- main_mcu: { application: { version: '1.2.3' } },
2001
+ fw: { 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
- main_mcu: { application: { version: '1.0.0' } },
2038
+ fw: { 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
- main_mcu: { application: { version: '4.15.0' } },
2403
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
2828
+ fw: { 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
- main_mcu: true,
2849
+ fw: 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
- main_mcu: {
2958
+ fw: {
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
- main_mcu: { application: { version: '1.2.3' } },
3012
+ fw: { 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
- main_mcu: true,
3047
+ fw: 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
- main_mcu: { bootloader: { version: '0.2.0' } },
3076
+ fw: { 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
- main_mcu: { romloader: { version: '1.0.0' } },
3126
+ fw: { 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
- main_mcu: { romloader: { version: '1.0.0' } },
3154
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3179
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3211
+ fw: { 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
- main_mcu: { bootloader: { version: '0.2.0' } },
3239
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3268
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3293
+ fw: { 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
- main_mcu: { bootloader: { version: '0.2.0' } },
3310
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3360
+ fw: { 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
- main_mcu: { bootloader: { version: '0.2.0' } },
3405
+ fw: { 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
- main_mcu: { romloader: { version: '1.0.0' } },
3439
+ fw: { 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
- main_mcu: { application: { version: '1.0.0' } },
3477
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3502
+ fw: { 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
- main_mcu: { application: { version: '1.2.4' } },
3517
+ fw: { 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
- main_mcu: true,
3549
+ fw: 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
- main_mcu: { application: { version: '1.2.4' } },
3570
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3647
+ fw: { 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
- main_mcu: { application: { version: '4.15.0' } },
3687
+ fw: { 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
- main_mcu: { application: { version: '1.2.3' } },
3734
+ fw: { 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
- main_mcu: {
4163
+ fw: {
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
- main_mcu: true,
4215
+ fw: true,
4216
4216
  coprocessor: true,
4217
4217
  se1: true,
4218
4218
  se2: true,
@@ -4631,6 +4631,43 @@ 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
+
4634
4671
  test('keeps Protocol V2 romloader active before firmware transfer', async () => {
4635
4672
  const method = new FirmwareUpdateV4({
4636
4673
  id: 1,
@@ -5079,7 +5116,7 @@ describe('Protocol V2 firmware update targets', () => {
5079
5116
  type: 'DeviceInfo',
5080
5117
  message: {
5081
5118
  hw: { serial_no: 'PR2SERIAL' },
5082
- main_mcu: { application: { version: '1.0.0' } },
5119
+ fw: { application: { version: '1.0.0' } },
5083
5120
  },
5084
5121
  });
5085
5122
  (method as any).protocolV2ExpectedSerialNumber = 'expected-serial';
@@ -5206,50 +5243,49 @@ describe('Protocol V2 firmware update targets', () => {
5206
5243
  );
5207
5244
  });
5208
5245
 
5209
- test('stages targets before sending the empty Protocol V2 install request', async () => {
5246
+ test('requires an explicit Protocol V2 update ACK before entering install state', async () => {
5210
5247
  const method = new FirmwareUpdateV4({
5211
5248
  id: 1,
5212
5249
  payload: {
5213
5250
  method: 'firmwareUpdateV4',
5214
5251
  },
5215
5252
  });
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: {} });
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
+ );
5219
5261
 
5220
5262
  (method as any).device = stubDevice({
5221
- getCommands: () => ({ typedCall, call }),
5263
+ getCommands: () => ({ typedCall }),
5222
5264
  });
5223
5265
  method.postTipMessage = jest.fn();
5224
5266
  method.postProgressMessage = jest.fn();
5225
5267
 
5226
5268
  await expect(
5227
- (method as any).protocolV2StartFirmwareUpdate({ targets })
5228
- ).resolves.toBeUndefined();
5269
+ (method as any).protocolV2StartFirmwareUpdate({
5270
+ targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
5271
+ })
5272
+ ).rejects.toThrow();
5229
5273
 
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');
5274
+ expect(method.postTipMessage).not.toHaveBeenCalled();
5275
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
5239
5276
  });
5240
5277
 
5241
- test('does not send the install request when Protocol V2 staging fails', async () => {
5278
+ test('does not poll Protocol V2 install status when the update ACK times out', async () => {
5242
5279
  const method = new FirmwareUpdateV4({
5243
5280
  id: 1,
5244
5281
  payload: {
5245
5282
  method: 'firmwareUpdateV4',
5246
5283
  },
5247
5284
  });
5248
- const typedCall = jest.fn().mockRejectedValue(new Error('stage failed'));
5249
- const call = jest.fn();
5285
+ const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
5250
5286
 
5251
5287
  (method as any).device = stubDevice({
5252
- getCommands: () => ({ typedCall, call }),
5288
+ getCommands: () => ({ typedCall }),
5253
5289
  });
5254
5290
  method.postTipMessage = jest.fn();
5255
5291
  method.postProgressMessage = jest.fn();
@@ -5258,26 +5294,78 @@ describe('Protocol V2 firmware update targets', () => {
5258
5294
  (method as any).protocolV2StartFirmwareUpdate({
5259
5295
  targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
5260
5296
  })
5261
- ).rejects.toThrow('stage failed');
5297
+ ).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
5262
5298
 
5263
- expect(call).not.toHaveBeenCalled();
5264
5299
  expect(method.postTipMessage).not.toHaveBeenCalled();
5265
5300
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5266
5301
  });
5267
5302
 
5268
- test('does not enter install state when writing the empty request fails', async () => {
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 () => {
5269
5356
  const method = new FirmwareUpdateV4({
5270
5357
  id: 1,
5271
5358
  payload: {
5272
5359
  method: 'firmwareUpdateV4',
5273
5360
  },
5274
5361
  });
5275
- const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5276
- const call = jest.fn().mockRejectedValue(new Error('write failed'));
5362
+ const typedCall = jest.fn().mockRejectedValue(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
5277
5363
 
5278
5364
  (method as any).device = stubDevice({
5279
- getCommands: () => ({ typedCall, call }),
5365
+ getCommands: () => ({ typedCall }),
5280
5366
  });
5367
+ (method as any).protocolV2LegacyDirectUpdate = true;
5368
+ (method as any).rebootProtocolV2ToBootloader = jest.fn();
5281
5369
  method.postTipMessage = jest.fn();
5282
5370
  method.postProgressMessage = jest.fn();
5283
5371
 
@@ -5285,9 +5373,10 @@ describe('Protocol V2 firmware update targets', () => {
5285
5373
  (method as any).protocolV2StartFirmwareUpdate({
5286
5374
  targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
5287
5375
  })
5288
- ).rejects.toThrow('write failed');
5376
+ ).rejects.toThrow('LIBUSB_TRANSFER_TIMED_OUT');
5289
5377
 
5290
- expect(call).toHaveBeenCalledTimes(1);
5378
+ expect(typedCall).toHaveBeenCalledTimes(1);
5379
+ expect((method as any).rebootProtocolV2ToBootloader).not.toHaveBeenCalled();
5291
5380
  expect(method.postTipMessage).not.toHaveBeenCalled();
5292
5381
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5293
5382
  });
@@ -5335,21 +5424,26 @@ describe('Protocol V2 firmware update targets', () => {
5335
5424
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5336
5425
  });
5337
5426
 
5338
- test('accepts the terminal Success returned by the install request', async () => {
5427
+ test('accepts a missing firmware status handler only after confirming normal mode', async () => {
5339
5428
  const method = new FirmwareUpdateV4({
5340
5429
  id: 1,
5341
5430
  payload: {
5342
5431
  method: 'firmwareUpdateV4',
5343
5432
  },
5344
5433
  });
5345
- const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5434
+ const typedCall = jest
5435
+ .fn()
5436
+ .mockRejectedValue(new Error('Failure: Handler not registered for this message'));
5346
5437
  const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
5347
5438
 
5348
5439
  (method as any).device = stubDevice({
5349
5440
  getCommands: () => ({ typedCall }),
5350
5441
  });
5351
5442
  (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
5352
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
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;
5353
5447
  method.postProgressMessage = jest.fn();
5354
5448
 
5355
5449
  await (method as any).waitForProtocolV2FirmwareUpdateComplete([
@@ -5358,7 +5452,47 @@ describe('Protocol V2 firmware update targets', () => {
5358
5452
 
5359
5453
  expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
5360
5454
  expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceFirmwareUpdateStatusGet']);
5361
- expect((method as any).protocolV2FinalStatusVerified).toBe(true);
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);
5362
5496
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5363
5497
  });
5364
5498
 
@@ -5672,7 +5806,7 @@ describe('Protocol V2 firmware update targets', () => {
5672
5806
  );
5673
5807
  });
5674
5808
 
5675
- test('polls target status after the install request and finishes when all targets complete', async () => {
5809
+ test('polls target status after update ACK and finishes when all targets complete', async () => {
5676
5810
  const method = new FirmwareUpdateV4({
5677
5811
  id: 1,
5678
5812
  payload: {
@@ -5707,7 +5841,7 @@ describe('Protocol V2 firmware update targets', () => {
5707
5841
  expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
5708
5842
  expect(typedCall).toHaveBeenCalledWith(
5709
5843
  'DeviceFirmwareUpdateStatusGet',
5710
- ['DeviceFirmwareUpdateStatus', 'Success'],
5844
+ 'DeviceFirmwareUpdateStatus',
5711
5845
  expect.anything(),
5712
5846
  expect.anything()
5713
5847
  );
@@ -5983,7 +6117,7 @@ describe('Protocol V2 firmware update targets', () => {
5983
6117
  });
5984
6118
  expect(typedCall).toHaveBeenCalledWith(
5985
6119
  'DeviceFirmwareUpdateStatusGet',
5986
- ['DeviceFirmwareUpdateStatus', 'Success'],
6120
+ 'DeviceFirmwareUpdateStatus',
5987
6121
  {
5988
6122
  fields: {
5989
6123
  status: true,
@@ -7183,10 +7317,9 @@ describe('Protocol V2 firmware update targets', () => {
7183
7317
  ],
7184
7318
  };
7185
7319
  const zip = new JSZip();
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');
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);
7190
7323
  const resourceArchiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
7191
7324
  const applicationP1Binary = new Uint8Array([7, 8, 9]).buffer;
7192
7325
  const bootloaderBinary = new Uint8Array([10, 11, 12]).buffer;
@@ -7262,39 +7395,6 @@ describe('Protocol V2 firmware update targets', () => {
7262
7395
  }
7263
7396
  });
7264
7397
 
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
-
7298
7398
  test('rejects a local resource ZIP whose file hash does not match its manifest', async () => {
7299
7399
  const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
7300
7400
  const bootResourceBinary = new Uint8Array([4, 5, 6]).buffer;
@@ -7385,57 +7485,18 @@ describe('Protocol V2 firmware update targets', () => {
7385
7485
 
7386
7486
  test('rejects an oversized ZIP entry before allocating its decompressed bytes', async () => {
7387
7487
  const extractEntry = jest.fn();
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',
7400
- },
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',
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,
7409
7496
  },
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,
7497
+ async: extractEntry,
7420
7498
  },
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,
7428
7499
  },
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,
7439
7500
  } as unknown as JSZip);
7440
7501
  const method = new FirmwareUpdateV4({
7441
7502
  id: 1,
@@ -7450,7 +7511,7 @@ describe('Protocol V2 firmware update targets', () => {
7450
7511
  try {
7451
7512
  await expect(
7452
7513
  (method as any).prepareProtocolV2LocalResourceArchive(new Uint8Array([1]).buffer)
7453
- ).rejects.toThrow('declared size is invalid');
7514
+ ).rejects.toThrow('declared size exceeds the allowed limit');
7454
7515
  expect(extractEntry).not.toHaveBeenCalled();
7455
7516
  } finally {
7456
7517
  loadSpy.mockRestore();
@@ -7746,7 +7807,7 @@ describe('Protocol V2 firmware update targets', () => {
7746
7807
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([4000, 97]);
7747
7808
  expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
7748
7809
  expect(writePayloads.every(payload => payload.append === false)).toBe(true);
7749
- expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 99]);
7810
+ expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
7750
7811
  expect(method.postMessage).toHaveBeenLastCalledWith({
7751
7812
  event: 'UI_EVENT',
7752
7813
  type: UI_REQUEST.FIRMWARE_PROGRESS,
@@ -7827,9 +7888,9 @@ describe('Protocol V2 firmware update targets', () => {
7827
7888
 
7828
7889
  const writePayloads = typedCall.mock.calls.map(call => call[2]);
7829
7890
  const deviceProgress = writePayloads.map(payload => payload.ui_percentage);
7830
- expect(deviceProgress).toEqual([0, 50, 99, 99]);
7891
+ expect(deviceProgress).toEqual([0, 50, 99, 100]);
7831
7892
  expect(deviceProgress.filter(progress => progress === 0)).toHaveLength(1);
7832
- expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(0);
7893
+ expect(deviceProgress.filter(progress => progress === 100)).toHaveLength(1);
7833
7894
  expect(method.postTipMessage).toHaveBeenCalledTimes(2);
7834
7895
  expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
7835
7896
  expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
@@ -8147,7 +8208,7 @@ describe('Protocol V2 firmware update targets', () => {
8147
8208
  const writePayloads = typedCall.mock.calls.map(call => call[2]);
8148
8209
  expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
8149
8210
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
8150
- expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 99]);
8211
+ expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
8151
8212
  expect(typedCall.mock.calls.map(call => call[3])).toEqual([
8152
8213
  expect.objectContaining({
8153
8214
  writeWithResponse: false,
@@ -8206,24 +8267,23 @@ describe('Protocol V2 firmware update targets', () => {
8206
8267
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
8207
8268
  });
8208
8269
 
8209
- test('starts install progress only after Stage and the empty Request are written', async () => {
8270
+ test('ends device confirmation and starts install progress only after Protocol V2 ACK', async () => {
8210
8271
  const method = new FirmwareUpdateV4({
8211
8272
  id: 1,
8212
8273
  payload: {
8213
8274
  method: 'firmwareUpdateV4',
8214
8275
  },
8215
8276
  });
8216
- let resolveWrite: ((value: unknown) => void) | undefined;
8217
- const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
8218
- const call = jest.fn().mockImplementation(
8277
+ let resolveRequest: ((value: unknown) => void) | undefined;
8278
+ const typedCall = jest.fn().mockImplementation(
8219
8279
  () =>
8220
8280
  new Promise(resolve => {
8221
- resolveWrite = resolve;
8281
+ resolveRequest = resolve;
8222
8282
  })
8223
8283
  );
8224
8284
 
8225
8285
  (method as any).device = stubDevice({
8226
- getCommands: () => ({ typedCall, call }),
8286
+ getCommands: () => ({ typedCall }),
8227
8287
  });
8228
8288
  method.postProgressMessage = jest.fn();
8229
8289
  method.postTipMessage = jest.fn();
@@ -8231,31 +8291,21 @@ describe('Protocol V2 firmware update targets', () => {
8231
8291
  const startPromise = (method as any).protocolV2StartFirmwareUpdate({
8232
8292
  targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
8233
8293
  });
8234
- await new Promise<void>(resolve => {
8235
- setImmediate(resolve);
8236
- });
8294
+ await Promise.resolve();
8237
8295
 
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
- );
8296
+ expect(typedCall.mock.calls[0][1]).toBe('Success');
8297
+ expect(typedCall.mock.calls[0][3]).toEqual(expect.objectContaining({ timeoutMs: 180_000 }));
8248
8298
  expect(method.postTipMessage).not.toHaveBeenCalled();
8249
8299
  expect(method.postProgressMessage).not.toHaveBeenCalled();
8250
8300
 
8251
- resolveWrite?.({ type: 'WriteCompleted', message: {} });
8301
+ resolveRequest?.({ type: 'Success', message: { message: 'ok' } });
8252
8302
  await startPromise;
8253
8303
  expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
8254
8304
  expect(method.postProgressMessage).toHaveBeenCalledWith(0, 'installingFirmware');
8255
8305
  expect(method.postTipMessage).toHaveBeenCalledTimes(1);
8256
8306
  });
8257
8307
 
8258
- test('sends an empty Protocol V2 firmware install request without waiting for its response', async () => {
8308
+ test('requests only a Success ACK for Protocol V2 firmware install', async () => {
8259
8309
  const method = new FirmwareUpdateV4({
8260
8310
  id: 1,
8261
8311
  payload: {
@@ -8266,10 +8316,9 @@ describe('Protocol V2 firmware update targets', () => {
8266
8316
  type: 'Success',
8267
8317
  message: { message: 'accepted' },
8268
8318
  });
8269
- const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
8270
8319
 
8271
8320
  (method as any).device = stubDevice({
8272
- getCommands: () => ({ typedCall, call }),
8321
+ getCommands: () => ({ typedCall }),
8273
8322
  });
8274
8323
  method.postProgressMessage = jest.fn();
8275
8324
  method.postTipMessage = jest.fn();
@@ -8278,12 +8327,7 @@ describe('Protocol V2 firmware update targets', () => {
8278
8327
  targets: [{ target_id: 4, path: 'vol1:firmware.bin' }],
8279
8328
  });
8280
8329
 
8281
- expect(typedCall.mock.calls[0][0]).toBe('DeviceFirmwareUpdateStage');
8282
- expect(call).toHaveBeenCalledWith(
8283
- 'DeviceFirmwareUpdateRequest',
8284
- {},
8285
- { returnAfterWrite: true }
8286
- );
8330
+ expect(typedCall.mock.calls[0][1]).toBe('Success');
8287
8331
  expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
8288
8332
  });
8289
8333
  });
@@ -9178,29 +9222,6 @@ describe('Protocol V2 protected method execution', () => {
9178
9222
  }
9179
9223
  );
9180
9224
 
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
-
9204
9225
  test('keeps Protocol V1 and lock-free methods outside the preflight path', async () => {
9205
9226
  for (const [isProtocolV2, unlockPolicy] of [
9206
9227
  [false, 'unlock-before-run'],
@@ -9380,7 +9401,7 @@ describe('Protocol V2 current low-level methods', () => {
9380
9401
  info: {
9381
9402
  version: undefined,
9382
9403
  serial_number: 'PR2SERIAL',
9383
- factory_burn_in_completed: true,
9404
+ burn_in_completed: true,
9384
9405
  factory_test_completed: undefined,
9385
9406
  manufacture_time: undefined,
9386
9407
  },
@@ -9630,7 +9651,7 @@ describe('Protocol V2 raw device info method', () => {
9630
9651
  'DeviceInfoGet',
9631
9652
  'DeviceInfo',
9632
9653
  {
9633
- targets: { hw: true, main_mcu: true, coprocessor: true },
9654
+ targets: { hw: true, fw: true, coprocessor: true },
9634
9655
  types: { version: true, specific: true },
9635
9656
  },
9636
9657
  { timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS }