@onekeyfe/hd-core 1.2.0-alpha.99 → 1.2.1-alpha.0

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 (200) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +203 -27
  3. package/__tests__/base64Data.test.ts +69 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +48 -2
  5. package/__tests__/check-firmware-release-protocol-v2.test.ts +82 -13
  6. package/__tests__/device-connector-protocol.test.ts +21 -0
  7. package/__tests__/device-lifecycle-events.test.ts +427 -7
  8. package/__tests__/device-pool-state.test.ts +25 -0
  9. package/__tests__/device-settings.test.ts +237 -9
  10. package/__tests__/device-state-events.test.ts +2 -2
  11. package/__tests__/device-state-mapper.test.ts +61 -5
  12. package/__tests__/device-utils.test.ts +15 -1
  13. package/__tests__/device-wallet-session-store.test.ts +147 -21
  14. package/__tests__/deviceSettings.test.ts +7 -3
  15. package/__tests__/deviceUploadNft.test.ts +36 -4
  16. package/__tests__/firmware-update/device-update-bootloader.test.ts +13 -3
  17. package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
  18. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
  19. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
  20. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +63 -0
  21. package/__tests__/firmware-update/firmware-update-plan.test.ts +59 -55
  22. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
  23. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  24. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +979 -0
  25. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  26. package/__tests__/get-device-state.test.ts +60 -21
  27. package/__tests__/logBlockEvent.test.ts +13 -1
  28. package/__tests__/open-wallet-session.test.ts +127 -1
  29. package/__tests__/protocol-v2-firmware-targets.test.ts +23 -0
  30. package/__tests__/protocol-v2-resources.test.ts +185 -88
  31. package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
  32. package/__tests__/protocol-v2.test.ts +2199 -486
  33. package/__tests__/refresh-device-state.test.ts +3 -1
  34. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  35. package/__tests__/search-devices.test.ts +79 -32
  36. package/__tests__/ton-sign-message.test.ts +84 -0
  37. package/dist/api/BaseMethod.d.ts +2 -1
  38. package/dist/api/BaseMethod.d.ts.map +1 -1
  39. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  40. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  41. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV4.d.ts +20 -6
  44. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  45. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  46. package/dist/api/SearchDevices.d.ts.map +1 -1
  47. package/dist/api/UploadPortfolio.d.ts +1 -1
  48. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  49. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  50. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  51. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  52. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  53. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  54. package/dist/api/device/DeviceVerify.d.ts +1 -0
  55. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  56. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  57. package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
  58. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  59. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
  63. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  64. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  65. package/dist/api/firmware/getBinary.d.ts +3 -2
  66. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  67. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  68. package/dist/api/helpers/base64Data.d.ts +16 -0
  69. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  70. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
  71. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  72. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  73. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  74. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  75. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
  76. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  77. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  78. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  79. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  80. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  81. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  82. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  83. package/dist/core/index.d.ts +6 -1
  84. package/dist/core/index.d.ts.map +1 -1
  85. package/dist/data-manager/DataManager.d.ts +4 -3
  86. package/dist/data-manager/DataManager.d.ts.map +1 -1
  87. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  88. package/dist/device/Device.d.ts +16 -3
  89. package/dist/device/Device.d.ts.map +1 -1
  90. package/dist/device/DeviceCommands.d.ts +4 -4
  91. package/dist/device/DeviceCommands.d.ts.map +1 -1
  92. package/dist/device/DeviceConnector.d.ts +1 -1
  93. package/dist/device/DeviceConnector.d.ts.map +1 -1
  94. package/dist/device/DevicePool.d.ts.map +1 -1
  95. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  96. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  97. package/dist/events/logBlockEvent.d.ts.map +1 -1
  98. package/dist/events/ui-request.d.ts +3 -0
  99. package/dist/events/ui-request.d.ts.map +1 -1
  100. package/dist/index.d.ts +54 -84
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +2150 -1469
  103. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  104. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  105. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  106. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  107. package/dist/protocols/protocol-v2/resources.d.ts +12 -7
  108. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  109. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
  110. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  111. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  112. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  113. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  114. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  115. package/dist/types/api/protocolV2.d.ts +1 -1
  116. package/dist/types/api/protocolV2.d.ts.map +1 -1
  117. package/dist/types/device.d.ts.map +1 -1
  118. package/dist/types/settings.d.ts +3 -31
  119. package/dist/types/settings.d.ts.map +1 -1
  120. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  121. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  122. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  123. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  124. package/dist/utils/deviceSettings.d.ts +1 -0
  125. package/dist/utils/deviceSettings.d.ts.map +1 -1
  126. package/dist/utils/index.d.ts +1 -1
  127. package/dist/utils/index.d.ts.map +1 -1
  128. package/dist/utils/patch.d.ts +1 -1
  129. package/dist/utils/patch.d.ts.map +1 -1
  130. package/dist/utils/pro2Nft.d.ts +7 -0
  131. package/dist/utils/pro2Nft.d.ts.map +1 -1
  132. package/package.json +6 -4
  133. package/src/api/BaseMethod.ts +8 -1
  134. package/src/api/CheckAllFirmwareRelease.ts +2 -4
  135. package/src/api/CheckBLEFirmwareRelease.ts +2 -2
  136. package/src/api/CheckBootloaderRelease.ts +3 -2
  137. package/src/api/CheckFirmwareRelease.ts +2 -2
  138. package/src/api/FirmwareUpdateV2.ts +12 -9
  139. package/src/api/FirmwareUpdateV3.ts +29 -19
  140. package/src/api/FirmwareUpdateV4.ts +934 -739
  141. package/src/api/OpenWalletSession.ts +39 -9
  142. package/src/api/PromptWebDeviceAccess.ts +2 -2
  143. package/src/api/SearchDevices.ts +6 -3
  144. package/src/api/UploadPortfolio.ts +9 -2
  145. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  146. package/src/api/device/DeviceChangePin.ts +4 -1
  147. package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
  148. package/src/api/device/DeviceRebootToBootloader.ts +3 -1
  149. package/src/api/device/DeviceSettings.ts +34 -20
  150. package/src/api/device/DeviceVerify.ts +25 -0
  151. package/src/api/device/DeviceWipe.ts +4 -1
  152. package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
  153. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
  154. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  155. package/src/api/firmware/FirmwareUpdatePlan.ts +37 -77
  156. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +10 -6
  157. package/src/api/firmware/protocolV2Release.ts +7 -2
  158. package/src/api/helpers/base64Data.ts +85 -0
  159. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  160. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
  161. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  162. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  163. package/src/api/protocol-v2/DeviceReboot.ts +5 -0
  164. package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
  165. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
  166. package/src/api/protocol-v2/helpers.ts +8 -0
  167. package/src/api/ton/TonSignMessage.ts +5 -3
  168. package/src/core/index.ts +264 -55
  169. package/src/data/messages/messages-protocol-v2.json +140 -13
  170. package/src/data-manager/DataManager.ts +46 -39
  171. package/src/data-manager/TransportManager.ts +3 -1
  172. package/src/device/Device.ts +210 -44
  173. package/src/device/DeviceCommands.ts +27 -18
  174. package/src/device/DeviceConnector.ts +17 -3
  175. package/src/device/DevicePool.ts +9 -2
  176. package/src/device/DeviceStateMapper.ts +30 -24
  177. package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
  178. package/src/events/logBlockEvent.ts +14 -1
  179. package/src/events/ui-request.ts +3 -0
  180. package/src/index.ts +0 -10
  181. package/src/protocols/protocol-v2/features.ts +22 -5
  182. package/src/protocols/protocol-v2/index.ts +2 -0
  183. package/src/protocols/protocol-v2/resources.ts +110 -147
  184. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
  185. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  186. package/src/types/api/firmwareUpdate.ts +1 -1
  187. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  188. package/src/types/api/protocolV2.ts +1 -1
  189. package/src/types/device.ts +2 -1
  190. package/src/types/settings.ts +6 -34
  191. package/src/utils/deviceFeaturesCompat.ts +9 -4
  192. package/src/utils/deviceFeaturesUtils.ts +4 -0
  193. package/src/utils/deviceInfoUtils.ts +9 -2
  194. package/src/utils/deviceSettings.ts +11 -4
  195. package/src/utils/index.ts +1 -0
  196. package/src/utils/pro2Nft.ts +31 -8
  197. package/__tests__/firmware-memory-host.test.ts +0 -126
  198. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  199. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  200. package/src/api/firmware/FirmwareMemoryHost.ts +0 -143
@@ -328,8 +328,8 @@ export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan =>
328
328
  }
329
329
  assertExactKeys(
330
330
  artifact,
331
- ['artifactId', 'role', 'target', 'url', 'container', 'expectedSize', 'expectedSha256'],
332
- ['logicalName', 'targetVersion']
331
+ ['artifactId', 'role', 'target', 'url', 'container'],
332
+ ['logicalName', 'expectedSize', 'expectedSha256', 'targetVersion']
333
333
  );
334
334
  const artifactId = assertBoundedString(artifact.artifactId, 'artifact id', 160);
335
335
  if (artifactIds.has(artifactId)) {
@@ -348,15 +348,25 @@ export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan =>
348
348
  if (artifact.logicalName !== undefined) {
349
349
  assertBoundedString(artifact.logicalName, 'logical name', 256);
350
350
  }
351
- if (!Number.isSafeInteger(artifact.expectedSize) || (artifact.expectedSize as number) <= 0) {
351
+ if (
352
+ artifact.expectedSize !== undefined &&
353
+ (!Number.isSafeInteger(artifact.expectedSize) || (artifact.expectedSize as number) <= 0)
354
+ ) {
352
355
  return planError('Firmware update plan artifact size is invalid');
353
356
  }
354
357
  if (
355
- typeof artifact.expectedSha256 !== 'string' ||
356
- !/^[a-f0-9]{64}$/u.test(artifact.expectedSha256)
358
+ artifact.expectedSha256 !== undefined &&
359
+ (typeof artifact.expectedSha256 !== 'string' ||
360
+ !/^[a-f0-9]{64}$/u.test(artifact.expectedSha256))
357
361
  ) {
358
362
  return planError('Firmware update plan artifact digest is invalid');
359
363
  }
364
+ if (
365
+ plan.executor === 'v4' &&
366
+ (artifact.expectedSize === undefined || artifact.expectedSha256 === undefined)
367
+ ) {
368
+ return planError('Protocol V2 firmware artifact integrity metadata is invalid');
369
+ }
360
370
  if (artifact.targetVersion !== undefined) {
361
371
  assertBoundedString(artifact.targetVersion, 'target version', 64);
362
372
  }
@@ -644,47 +654,6 @@ const finalizeFirmwareUpdatePlan = ({
644
654
  });
645
655
  };
646
656
 
647
- export type ProtocolV2LocalFirmwareUpdatePlanArtifact = {
648
- artifactId: string;
649
- target: Exclude<FirmwareUpdateV4Target, 'boot_resources'>;
650
- container: 'raw' | 'zip';
651
- logicalName: string;
652
- expectedSize: number;
653
- expectedSha256: string;
654
- targetVersion?: string;
655
- };
656
-
657
- export const buildProtocolV2LocalFirmwareUpdatePlan = ({
658
- features,
659
- firmwareType,
660
- platform,
661
- artifacts,
662
- }: {
663
- features: Features;
664
- firmwareType: EFirmwareType;
665
- platform: FirmwareUpdatePlatform;
666
- artifacts: ProtocolV2LocalFirmwareUpdatePlanArtifact[];
667
- }): FirmwareUpdatePlan => {
668
- if (artifacts.length === 0) {
669
- return planError('Protocol V2 local firmware plan has no artifacts');
670
- }
671
- const plan = finalizeFirmwareUpdatePlan({
672
- features,
673
- firmwareType,
674
- platform,
675
- artifacts: artifacts.map(artifact => ({
676
- ...artifact,
677
- role: artifact.target === 'resource' ? 'resourceBundle' : 'component',
678
- url: `https://local-firmware.invalid/${encodeURIComponent(artifact.artifactId)}`,
679
- })),
680
- targetsToUpdate: artifacts.map(artifact => artifact.target),
681
- });
682
- if (plan.executor !== 'v4') {
683
- return planError('Protocol V2 local firmware plan requires executor v4');
684
- }
685
- return plan;
686
- };
687
-
688
657
  export const buildProtocolV2FirmwareUpdatePlan = ({
689
658
  features,
690
659
  firmwareType,
@@ -822,13 +791,10 @@ export const buildFirmwareUpdatePlan = ({
822
791
  } else {
823
792
  if (isUpgrade(bootloader) || forcedTargets.has('bootloader')) {
824
793
  const bootloaderRelease = asRelease(bootloader);
825
- const integrity = requireArtifactIntegrity(
826
- {
827
- size: bootloaderRelease?.bootloaderExpectedSize,
828
- sha256: bootloaderRelease?.bootloaderFingerprint,
829
- },
830
- 'Bootloader release'
831
- );
794
+ const integrity = asIntegrity({
795
+ size: bootloaderRelease?.bootloaderExpectedSize,
796
+ sha256: bootloaderRelease?.bootloaderFingerprint,
797
+ });
832
798
  artifacts.push({
833
799
  artifactId: 'bootloader',
834
800
  role: 'bootloader',
@@ -850,10 +816,10 @@ export const buildFirmwareUpdatePlan = ({
850
816
  });
851
817
  }
852
818
  if (shouldUpdateFirmware) {
853
- const integrity = requireArtifactIntegrity(
854
- { size: firmwareRelease.expectedSize, sha256: firmwareRelease.fingerprint },
855
- 'Firmware release'
856
- );
819
+ const integrity = asIntegrity({
820
+ size: firmwareRelease.expectedSize,
821
+ sha256: firmwareRelease.fingerprint,
822
+ });
857
823
  artifacts.push({
858
824
  artifactId: 'firmware',
859
825
  role: 'firmware',
@@ -873,19 +839,16 @@ export const buildFirmwareUpdatePlan = ({
873
839
  platform,
874
840
  });
875
841
  if (resourceUrl) {
876
- const integrity = requireArtifactIntegrity(
877
- {
878
- size:
879
- resourceUrl === asString(firmwareRelease.fullResource)
880
- ? firmwareRelease.fullResourceExpectedSize
881
- : firmwareRelease.resourceExpectedSize,
882
- sha256:
883
- resourceUrl === asString(firmwareRelease.fullResource)
884
- ? firmwareRelease.fullResourceFingerprint
885
- : firmwareRelease.resourceFingerprint,
886
- },
887
- 'Legacy resource archive'
888
- );
842
+ const integrity = asIntegrity({
843
+ size:
844
+ resourceUrl === asString(firmwareRelease.fullResource)
845
+ ? firmwareRelease.fullResourceExpectedSize
846
+ : firmwareRelease.resourceExpectedSize,
847
+ sha256:
848
+ resourceUrl === asString(firmwareRelease.fullResource)
849
+ ? firmwareRelease.fullResourceFingerprint
850
+ : firmwareRelease.resourceFingerprint,
851
+ });
889
852
  artifacts.push({
890
853
  artifactId: 'resource',
891
854
  role: 'resource',
@@ -899,13 +862,10 @@ export const buildFirmwareUpdatePlan = ({
899
862
  }
900
863
  if (isUpgrade(ble) || isRecoveryInstall(ble) || forcedTargets.has('ble')) {
901
864
  const bleRelease = asRelease(ble);
902
- const integrity = requireArtifactIntegrity(
903
- {
904
- size: bleRelease?.expectedSize,
905
- sha256: bleRelease?.fingerprintWeb ?? bleRelease?.fingerprint,
906
- },
907
- 'BLE release'
908
- );
865
+ const integrity = asIntegrity({
866
+ size: bleRelease?.expectedSize,
867
+ sha256: bleRelease?.fingerprintWeb ?? bleRelease?.fingerprint,
868
+ });
909
869
  artifacts.push({
910
870
  artifactId: 'ble',
911
871
  role: 'ble',
@@ -71,6 +71,13 @@ export const getFirmwareUpdateResourceName = (value: unknown): string => {
71
71
  return resourceName;
72
72
  };
73
73
 
74
+ const getPreparedEntryIdentity = (entryName: unknown): string => {
75
+ getFirmwareUpdateResourceName(entryName);
76
+ // Protocol V2 resource ZIPs can ship the same basename from different
77
+ // directories, such as rom/params.okpkg and bootloader/params.okpkg.
78
+ return (entryName as string).toLowerCase();
79
+ };
80
+
74
81
  const assertPreparedEntry = (value: unknown): FirmwareUpdatePreparedEntry => {
75
82
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
76
83
  return preparedPlanError('Firmware prepared plan entry must be an object');
@@ -141,11 +148,8 @@ const assertPreparedArtifacts = ({
141
148
  }
142
149
  if (
143
150
  materializedEntries &&
144
- new Set(
145
- materializedEntries.map(entry =>
146
- getFirmwareUpdateResourceName(entry.entryName).toLowerCase()
147
- )
148
- ).size !== materializedEntries.length
151
+ new Set(materializedEntries.map(entry => getPreparedEntryIdentity(entry.entryName))).size !==
152
+ materializedEntries.length
149
153
  ) {
150
154
  return preparedPlanError('Firmware prepared plan contains duplicate entry names');
151
155
  }
@@ -281,7 +285,7 @@ export const validateFirmwareUpdatePreparedPlan = (value: unknown): FirmwareUpda
281
285
  const materializedEntryNames =
282
286
  artifact.materializedEntries?.map(entry => {
283
287
  assertPreparedEntry(entry);
284
- return getFirmwareUpdateResourceName(entry.entryName).toLowerCase();
288
+ return getPreparedEntryIdentity(entry.entryName);
285
289
  }) ?? [];
286
290
  if (new Set(materializedEntryNames).size !== materializedEntryNames.length) {
287
291
  return preparedPlanError('Firmware prepared plan contains duplicate entry names');
@@ -77,11 +77,16 @@ export async function loadProtocolV2FirmwareReleaseContext({
77
77
  }
78
78
 
79
79
  const firmwareType = firmwareTypeParam ?? state.identity.firmwareType ?? EFirmwareType.Universal;
80
+ const normalizedFeatures: Features = {
81
+ ...features,
82
+ deviceType: state.identity.deviceType,
83
+ firmwareType,
84
+ };
80
85
  return {
81
86
  state: state as ProtocolV2FirmwareReleaseContext['state'],
82
- features,
87
+ features: normalizedFeatures,
83
88
  firmwareType,
84
- release: DataManager.getFirmwareLatestRelease(features, firmwareType),
89
+ release: DataManager.getFirmwareLatestRelease(normalizedFeatures, firmwareType),
85
90
  };
86
91
  }
87
92
 
@@ -0,0 +1,85 @@
1
+ import { Buffer } from 'buffer';
2
+ import { decode as decodeJpeg } from 'jpeg-js';
3
+
4
+ import { invalidParameter } from './filesystemValidation';
5
+
6
+ const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
7
+ const JPEG_CONTAINER_OVERHEAD_BYTES = 64 * 1024;
8
+ const JPEG_MAX_MEMORY_USAGE_IN_MB = 32;
9
+ const JPEG_MAX_RESOLUTION_IN_MP = 1;
10
+
11
+ export function decodeCanonicalBase64({
12
+ value,
13
+ parameterName,
14
+ maxBytes,
15
+ }: {
16
+ value: unknown;
17
+ parameterName: string;
18
+ maxBytes: number;
19
+ }): Uint8Array {
20
+ if (typeof value !== 'string' || value.length === 0) {
21
+ throw invalidParameter(`Parameter [${parameterName}] must be a non-empty Base64 string.`);
22
+ }
23
+ if (value.length > Math.ceil(maxBytes / 3) * 4) {
24
+ throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
25
+ }
26
+ if (value.length % 4 !== 0 || !BASE64_PATTERN.test(value)) {
27
+ throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
28
+ }
29
+
30
+ const decoded = Buffer.from(value, 'base64');
31
+ if (decoded.byteLength === 0 || decoded.byteLength > maxBytes) {
32
+ throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
33
+ }
34
+ if (decoded.toString('base64') !== value) {
35
+ throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
36
+ }
37
+ return Uint8Array.from(decoded);
38
+ }
39
+
40
+ export function decodeJpegBase64ToRgba({
41
+ jpegBase64,
42
+ parameterName,
43
+ expectedWidth,
44
+ expectedHeight,
45
+ }: {
46
+ jpegBase64: unknown;
47
+ parameterName: string;
48
+ expectedWidth: number;
49
+ expectedHeight: number;
50
+ }): { width: number; height: number; data: Uint8Array } {
51
+ const jpegBytes = decodeCanonicalBase64({
52
+ value: jpegBase64,
53
+ parameterName,
54
+ maxBytes: expectedWidth * expectedHeight * 8 + JPEG_CONTAINER_OVERHEAD_BYTES,
55
+ });
56
+ if (jpegBytes[0] !== 0xff || jpegBytes[1] !== 0xd8) {
57
+ throw invalidParameter(`Parameter [${parameterName}] must contain a JPEG image.`);
58
+ }
59
+
60
+ let decoded: { width: number; height: number; data: Uint8Array };
61
+ try {
62
+ decoded = decodeJpeg(jpegBytes, {
63
+ useTArray: true,
64
+ formatAsRGBA: true,
65
+ tolerantDecoding: false,
66
+ maxResolutionInMP: JPEG_MAX_RESOLUTION_IN_MP,
67
+ maxMemoryUsageInMB: JPEG_MAX_MEMORY_USAGE_IN_MB,
68
+ });
69
+ } catch {
70
+ throw invalidParameter(`Parameter [${parameterName}] must contain a valid JPEG image.`);
71
+ }
72
+
73
+ if (decoded.width !== expectedWidth || decoded.height !== expectedHeight) {
74
+ throw invalidParameter(
75
+ `Parameter [${parameterName}] must contain a ${expectedWidth}x${expectedHeight} JPEG image.`
76
+ );
77
+ }
78
+ const expectedLength = expectedWidth * expectedHeight * 4;
79
+ if (decoded.data.byteLength !== expectedLength) {
80
+ throw invalidParameter(
81
+ `Decoded parameter [${parameterName}] must contain ${expectedLength} RGBA bytes.`
82
+ );
83
+ }
84
+ return decoded;
85
+ }
@@ -25,7 +25,7 @@ export default class DeviceFactoryInfoSet extends BaseMethod<DeviceFactoryInfoSe
25
25
  info: {
26
26
  version: this.params.version,
27
27
  serial_number: this.params.serial_number,
28
- burn_in_completed: this.params.burn_in_completed,
28
+ factory_burn_in_completed: this.params.burn_in_completed,
29
29
  factory_test_completed: this.params.factory_test_completed,
30
30
  manufacture_time: this.params.manufacture_time,
31
31
  },
@@ -1,14 +1,6 @@
1
1
  import { BaseMethod } from '../BaseMethod';
2
- import {
3
- PROTOCOL_V2_FIRMWARE_UPDATE_OPTIONS,
4
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
5
- isProtocolV2DeviceDisconnectedError,
6
- normalizeFirmwareTargets,
7
- } from './helpers';
8
- import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
2
+ import { isProtocolV2DeviceDisconnectedError, normalizeFirmwareTargets } from './helpers';
9
3
 
10
- import type { KnownDevice } from '../../types';
11
- import type { MessageFromOneKey } from '@onekeyfe/hd-transport';
12
4
  import type { DeviceFirmwareUpdateParams } from './helpers';
13
5
 
14
6
  export default class DeviceFirmwareUpdate extends BaseMethod<DeviceFirmwareUpdateParams> {
@@ -31,27 +23,13 @@ export default class DeviceFirmwareUpdate extends BaseMethod<DeviceFirmwareUpdat
31
23
  async run() {
32
24
  const targets = normalizeFirmwareTargets(this.params);
33
25
  try {
34
- const res = await this.device.commands.typedCall(
26
+ await this.device.commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
27
+ await this.device.commands.call(
35
28
  'DeviceFirmwareUpdateRequest',
36
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
37
- {
38
- targets,
39
- },
40
- {
41
- ...PROTOCOL_V2_FIRMWARE_UPDATE_OPTIONS,
42
- onIntermediateResponse: (response: MessageFromOneKey) => {
43
- if (response.type !== 'DeviceFirmwareUpdateStatus') return;
44
- this.postMessage(
45
- createUiMessage(UI_REQUEST.FIRMWARE_PROGRESS, {
46
- device: this.device.toMessageObject() as KnownDevice,
47
- progress: 99,
48
- progressType: 'installingFirmware',
49
- })
50
- );
51
- },
52
- }
29
+ {},
30
+ { returnAfterWrite: true }
53
31
  );
54
- return res.message;
32
+ return { message: 'Device firmware update started' };
55
33
  } catch (error) {
56
34
  if (isProtocolV2DeviceDisconnectedError(error)) {
57
35
  return {
@@ -4,7 +4,13 @@ import { invalidParameter } from '../helpers/filesystemValidation';
4
4
  import type { DeviceFirmwareUpdateStatusGetParams } from './helpers';
5
5
  import type { DeviceFirmwareUpdateRecordFields } from '@onekeyfe/hd-transport';
6
6
 
7
- const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = ['status', 'payload_version', 'path'] as const;
7
+ const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = [
8
+ 'status',
9
+ 'progress_percent',
10
+ 'phase_info',
11
+ 'payload_version',
12
+ 'path',
13
+ ] as const;
8
14
 
9
15
  function normalizeStatusFields(
10
16
  fields: DeviceFirmwareUpdateStatusGetParams['fields']
@@ -5,7 +5,7 @@ import { invalidParameter } from '../helpers/filesystemValidation';
5
5
 
6
6
  export type DeviceInfoGetTargets = {
7
7
  hw?: boolean;
8
- fw?: boolean;
8
+ main_mcu?: boolean;
9
9
  coprocessor?: boolean;
10
10
  se1?: boolean;
11
11
  se2?: boolean;
@@ -27,7 +27,7 @@ export type DeviceInfoGetParams = {
27
27
 
28
28
  const TARGET_KEYS: (keyof DeviceInfoGetTargets)[] = [
29
29
  'hw',
30
- 'fw',
30
+ 'main_mcu',
31
31
  'coprocessor',
32
32
  'se1',
33
33
  'se2',
@@ -39,7 +39,7 @@ const TYPE_KEYS: (keyof DeviceInfoGetTypes)[] = ['version', 'build_id', 'hash',
39
39
 
40
40
  const DEFAULT_TARGETS: DeviceInfoGetTargets = {
41
41
  hw: true,
42
- fw: true,
42
+ main_mcu: true,
43
43
  coprocessor: true,
44
44
  };
45
45
 
@@ -1,3 +1,5 @@
1
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
+
1
3
  import { BaseMethod } from '../BaseMethod';
2
4
  import { normalizeRebootType } from './helpers';
3
5
 
@@ -11,6 +13,9 @@ export default class DeviceReboot extends BaseMethod<DeviceRebootParams> {
11
13
  init() {
12
14
  this.skipForceUpdateCheck = true;
13
15
  this.useDevicePassphraseState = false;
16
+ this.unlockPolicy = 'unlock-before-run';
17
+ // Device-management action: main PIN or Attach PIN may authorize reboot.
18
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
14
19
  this.params = {
15
20
  rebootType: this.payload.rebootType,
16
21
  reboot_type: this.payload.reboot_type,
@@ -1,4 +1,5 @@
1
1
  import { ERRORS, HardwareErrorCode, createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
4
5
  import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
@@ -7,21 +8,26 @@ import {
7
8
  PRO2_NFT_DEFAULT_PACE_MS,
8
9
  PRO2_NFT_DEFAULT_TIMEOUT_MS,
9
10
  PRO2_NFT_DIRECTORY,
11
+ PRO2_NFT_IMAGE_HEIGHT,
12
+ PRO2_NFT_IMAGE_WIDTH,
10
13
  PRO2_NFT_MAX_CHUNK_SIZE,
11
14
  PRO2_NFT_MAX_ITEMS,
12
15
  PRO2_NFT_MIN_CHUNK_SIZE,
16
+ PRO2_NFT_THUMBNAIL_HEIGHT,
17
+ PRO2_NFT_THUMBNAIL_WIDTH,
13
18
  type Pro2NftBundle,
14
- type Pro2NftImage,
15
- buildPro2NftBundle,
19
+ buildPro2NftBundleFromEncodedImages,
16
20
  getCompletePro2NftBasenames,
17
21
  } from '../../utils/pro2Nft';
22
+ import { encodePro2Image } from '../../utils/pro2Wallpaper';
18
23
  import { BaseMethod } from '../BaseMethod';
24
+ import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
19
25
  import { invalidParameter } from '../helpers/filesystemValidation';
20
26
  import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
21
27
 
22
28
  export type DeviceUploadNftParams = {
23
- image: Pro2NftImage;
24
- thumbnail: Pro2NftImage;
29
+ imageJpegBase64: string;
30
+ thumbnailJpegBase64: string;
25
31
  title: string;
26
32
  subtitle: string;
27
33
  timestampMs?: number;
@@ -54,8 +60,8 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
54
60
 
55
61
  init() {
56
62
  const {
57
- image,
58
- thumbnail,
63
+ imageJpegBase64,
64
+ thumbnailJpegBase64,
59
65
  title,
60
66
  subtitle,
61
67
  timestampMs = Date.now(),
@@ -79,9 +85,55 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
79
85
  throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
80
86
  }
81
87
 
82
- this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
83
- this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
84
- this.unlockPolicy = 'none';
88
+ const encodedImage = (() => {
89
+ const decoded = decodeJpegBase64ToRgba({
90
+ jpegBase64: imageJpegBase64,
91
+ parameterName: 'imageJpegBase64',
92
+ expectedWidth: PRO2_NFT_IMAGE_WIDTH,
93
+ expectedHeight: PRO2_NFT_IMAGE_HEIGHT,
94
+ });
95
+ return encodePro2Image({
96
+ width: PRO2_NFT_IMAGE_WIDTH,
97
+ height: PRO2_NFT_IMAGE_HEIGHT,
98
+ rgba: decoded.data,
99
+ alphaMode: 'black-background',
100
+ }).data;
101
+ })();
102
+ const encodedThumbnail = (() => {
103
+ const decoded = decodeJpegBase64ToRgba({
104
+ jpegBase64: thumbnailJpegBase64,
105
+ parameterName: 'thumbnailJpegBase64',
106
+ expectedWidth: PRO2_NFT_THUMBNAIL_WIDTH,
107
+ expectedHeight: PRO2_NFT_THUMBNAIL_HEIGHT,
108
+ });
109
+ return encodePro2Image({
110
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
111
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
112
+ rgba: decoded.data,
113
+ alphaMode: 'black-background',
114
+ }).data;
115
+ })();
116
+ this.bundle = buildPro2NftBundleFromEncodedImages({
117
+ image: encodedImage,
118
+ thumbnail: encodedThumbnail,
119
+ title,
120
+ subtitle,
121
+ timestampMs,
122
+ });
123
+ this.params = {
124
+ imageJpegBase64,
125
+ thumbnailJpegBase64,
126
+ title,
127
+ subtitle,
128
+ timestampMs,
129
+ chunkSize,
130
+ paceMs,
131
+ timeoutMs,
132
+ };
133
+ this.unlockPolicy = 'unlock-before-run';
134
+ // File writes and NftUpdate require an unlocked device. Either PIN may
135
+ // authorize this device-management action.
136
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
85
137
  this.skipForceUpdateCheck = true;
86
138
  this.useDevicePassphraseState = false;
87
139
  }
@@ -1,10 +1,15 @@
1
1
  import { blake2s } from '@noble/hashes/blake2s';
2
2
  import { bytesToHex } from '@noble/hashes/utils';
3
+ import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
4
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
3
5
 
4
6
  import { BaseMethod } from '../BaseMethod';
7
+ import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
5
8
  import { invalidParameter } from '../helpers/filesystemValidation';
6
9
  import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
7
10
  import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
11
+ import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
12
+ import { LoggerNames, getLogger } from '../../utils';
8
13
  import {
9
14
  PRO2_WALLPAPER_HEIGHT,
10
15
  PRO2_WALLPAPER_WIDTH,
@@ -13,9 +18,7 @@ import {
13
18
  } from '../../utils/pro2Wallpaper';
14
19
 
15
20
  export type DeviceUploadWallpaperParams = {
16
- width: number;
17
- height: number;
18
- rgba: Uint8Array | ArrayBuffer;
21
+ jpegBase64: string;
19
22
  fileName?: string;
20
23
  chunkSize?: number;
21
24
  };
@@ -29,6 +32,10 @@ export type DeviceUploadWallpaperResponse = {
29
32
 
30
33
  const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
31
34
  const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
35
+ const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
36
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
37
+ const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
38
+ const Log = getLogger(LoggerNames.Method);
32
39
 
33
40
  function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
34
41
  if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
@@ -54,31 +61,48 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
54
61
  private path = '';
55
62
 
56
63
  init() {
57
- const { width, height, rgba, fileName, chunkSize } = this.payload;
58
- if (width !== PRO2_WALLPAPER_WIDTH || height !== PRO2_WALLPAPER_HEIGHT) {
59
- throw invalidParameter(
60
- `Pro2 wallpaper dimensions must be ${PRO2_WALLPAPER_WIDTH}x${PRO2_WALLPAPER_HEIGHT}.`
61
- );
62
- }
63
- if (!(rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(rgba)) {
64
- throw invalidParameter('Parameter [rgba] must be an ArrayBuffer or Uint8Array.');
65
- }
64
+ const { jpegBase64, fileName, chunkSize } = this.payload;
66
65
  if (chunkSize !== undefined && (!Number.isInteger(chunkSize) || chunkSize <= 0)) {
67
66
  throw invalidParameter('Parameter [chunkSize] must be a positive integer.');
68
67
  }
69
68
 
70
- const rgbaBytes =
71
- rgba instanceof ArrayBuffer
72
- ? rgba
73
- : new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength);
74
- this.encoded = encodePro2Wallpaper({ width, height, rgba: rgbaBytes });
69
+ const decoded = decodeJpegBase64ToRgba({
70
+ jpegBase64,
71
+ parameterName: 'jpegBase64',
72
+ expectedWidth: PRO2_WALLPAPER_WIDTH,
73
+ expectedHeight: PRO2_WALLPAPER_HEIGHT,
74
+ });
75
+ this.encoded = encodePro2Wallpaper({
76
+ width: PRO2_WALLPAPER_WIDTH,
77
+ height: PRO2_WALLPAPER_HEIGHT,
78
+ rgba: decoded.data,
79
+ });
75
80
  this.path = `${WALLPAPER_DIRECTORY}/${normalizeFileName(fileName, this.encoded.data)}`;
76
- this.params = { width, height, rgba: rgbaBytes, fileName, chunkSize };
77
- this.unlockPolicy = 'none';
81
+ this.params = { jpegBase64, fileName, chunkSize };
82
+ this.unlockPolicy = 'unlock-before-run';
83
+ // File writes and wallpaper apply require an unlocked device. Either PIN
84
+ // may authorize this device-management action.
85
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
78
86
  this.skipForceUpdateCheck = true;
79
87
  this.useDevicePassphraseState = false;
80
88
  }
81
89
 
90
+ private async assertCapabilities() {
91
+ const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
92
+ const requiredMessageTypes = [
93
+ DEVICE_SETTINGS_SET_MESSAGE_TYPE,
94
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE,
95
+ FILESYSTEM_DIR_MAKE_MESSAGE_TYPE,
96
+ ];
97
+ if (
98
+ requiredMessageTypes.some(
99
+ messageType => !supportsProtocolV2Message(protocolInfo, messageType)
100
+ )
101
+ ) {
102
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
103
+ }
104
+ }
105
+
82
106
  private async ensureDirectory() {
83
107
  if (this.directoryReady) return;
84
108
  try {
@@ -119,11 +143,21 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
119
143
  async run(): Promise<DeviceUploadWallpaperResponse> {
120
144
  const { encoded } = this;
121
145
  if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
146
+ await this.assertCapabilities();
122
147
  await this.ensureDirectory();
123
148
  await this.upload();
124
149
  const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
125
150
  settings: { wallpaper_path: this.path },
126
151
  });
152
+ try {
153
+ await this.device.refreshProtocolV2SettingsAfterMutation();
154
+ } catch (error) {
155
+ // The wallpaper is already applied. A transient read-back failure must not
156
+ // make callers retry the completed file upload.
157
+ Log.warn('Protocol V2 wallpaper settings refresh failed after apply', {
158
+ error: error instanceof Error ? error.message : String(error),
159
+ });
160
+ }
127
161
  return {
128
162
  path: this.path,
129
163
  size: encoded.data.byteLength,
@@ -1,4 +1,5 @@
1
1
  import { DeviceRebootType } from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
3
 
3
4
  import { invalidParameter, validateNonEmptyString } from '../helpers/filesystemValidation';
4
5
  import { ProtocolV2FirmwareTargetType } from '../../protocols/protocol-v2/firmware';
@@ -103,6 +104,13 @@ export const getProtocolV2UnknownErrorText = (error: unknown) => {
103
104
  };
104
105
 
105
106
  export const isProtocolV2DeviceDisconnectedError = (error: unknown) => {
107
+ const errorCode =
108
+ error && typeof error === 'object'
109
+ ? (error as { errorCode?: number; code?: number }).errorCode ??
110
+ (error as { errorCode?: number; code?: number }).code
111
+ : undefined;
112
+ if (errorCode === HardwareErrorCode.BleDeviceDisconnected) return true;
113
+
106
114
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
107
115
  const compactMessage = message.replace(/\s+/g, '');
108
116
  return (