@onekeyfe/hd-core 1.1.26 → 1.1.27-alpha.30

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 (156) hide show
  1. package/__tests__/protocol-v2.test.ts +940 -0
  2. package/dist/api/BaseMethod.d.ts +1 -3
  3. package/dist/api/BaseMethod.d.ts.map +1 -1
  4. package/dist/api/DirList.d.ts +10 -0
  5. package/dist/api/DirList.d.ts.map +1 -0
  6. package/dist/api/DirMake.d.ts +9 -0
  7. package/dist/api/DirMake.d.ts.map +1 -0
  8. package/dist/api/DirRemove.d.ts +9 -0
  9. package/dist/api/DirRemove.d.ts.map +1 -0
  10. package/dist/api/FileDelete.d.ts +9 -0
  11. package/dist/api/FileDelete.d.ts.map +1 -0
  12. package/dist/api/FileRead.d.ts +19 -0
  13. package/dist/api/FileRead.d.ts.map +1 -0
  14. package/dist/api/FileWrite.d.ts +23 -0
  15. package/dist/api/FileWrite.d.ts.map +1 -0
  16. package/dist/api/FirmwareUpdateV3.d.ts +1 -0
  17. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  18. package/dist/api/FirmwareUpdateV4.d.ts +32 -0
  19. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -0
  20. package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
  21. package/dist/api/PathInfo.d.ts +9 -0
  22. package/dist/api/PathInfo.d.ts.map +1 -0
  23. package/dist/api/SearchDevices.d.ts +2 -1
  24. package/dist/api/SearchDevices.d.ts.map +1 -1
  25. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  26. package/dist/api/device/DeviceRebootToBoardloader.d.ts +1 -1
  27. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  28. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  29. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +10 -2
  30. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  31. package/dist/api/index.d.ts +26 -0
  32. package/dist/api/index.d.ts.map +1 -1
  33. package/dist/api/protocol-v2/DevFirmwareUpdate.d.ts +7 -0
  34. package/dist/api/protocol-v2/DevFirmwareUpdate.d.ts.map +1 -0
  35. package/dist/api/protocol-v2/DevGetDeviceInfo.d.ts +7 -0
  36. package/dist/api/protocol-v2/DevGetDeviceInfo.d.ts.map +1 -0
  37. package/dist/api/protocol-v2/DevGetFirmwareUpdateStatus.d.ts +6 -0
  38. package/dist/api/protocol-v2/DevGetFirmwareUpdateStatus.d.ts.map +1 -0
  39. package/dist/api/protocol-v2/DevGetOnboardingStatus.d.ts +6 -0
  40. package/dist/api/protocol-v2/DevGetOnboardingStatus.d.ts.map +1 -0
  41. package/dist/api/protocol-v2/DevReboot.d.ts +7 -0
  42. package/dist/api/protocol-v2/DevReboot.d.ts.map +1 -0
  43. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +7 -0
  44. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +1 -0
  45. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +6 -0
  46. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts +6 -0
  48. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +1 -0
  49. package/dist/api/protocol-v2/FilesystemFormat.d.ts +6 -0
  50. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -0
  51. package/dist/api/protocol-v2/GetProtoVersion.d.ts +6 -0
  52. package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +1 -0
  53. package/dist/api/protocol-v2/Ping.d.ts +8 -0
  54. package/dist/api/protocol-v2/Ping.d.ts.map +1 -0
  55. package/dist/api/protocol-v2/helpers.d.ts +49 -0
  56. package/dist/api/protocol-v2/helpers.d.ts.map +1 -0
  57. package/dist/core/index.d.ts.map +1 -1
  58. package/dist/data-manager/DataManager.d.ts +4 -2
  59. package/dist/data-manager/DataManager.d.ts.map +1 -1
  60. package/dist/data-manager/TransportManager.d.ts +2 -1
  61. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  62. package/dist/device/Device.d.ts +5 -3
  63. package/dist/device/Device.d.ts.map +1 -1
  64. package/dist/device/DeviceCommands.d.ts +8 -8
  65. package/dist/device/DeviceCommands.d.ts.map +1 -1
  66. package/dist/device/DeviceConnector.d.ts +2 -1
  67. package/dist/device/DeviceConnector.d.ts.map +1 -1
  68. package/dist/events/ui-request.d.ts +8 -0
  69. package/dist/events/ui-request.d.ts.map +1 -1
  70. package/dist/index.d.ts +188 -20
  71. package/dist/index.js +15626 -753
  72. package/dist/inject.d.ts.map +1 -1
  73. package/dist/protocols/protocol-v2/features.d.ts +56 -0
  74. package/dist/protocols/protocol-v2/features.d.ts.map +1 -0
  75. package/dist/protocols/protocol-v2/firmware.d.ts +12 -0
  76. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -0
  77. package/dist/protocols/protocol-v2/index.d.ts +3 -0
  78. package/dist/protocols/protocol-v2/index.d.ts.map +1 -0
  79. package/dist/types/api/export.d.ts +1 -1
  80. package/dist/types/api/export.d.ts.map +1 -1
  81. package/dist/types/api/firmwareUpdate.d.ts +7 -0
  82. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  83. package/dist/types/api/index.d.ts +28 -1
  84. package/dist/types/api/index.d.ts.map +1 -1
  85. package/dist/types/api/protocolV2.d.ts +123 -0
  86. package/dist/types/api/protocolV2.d.ts.map +1 -0
  87. package/dist/types/api/searchDevices.d.ts +2 -2
  88. package/dist/types/api/searchDevices.d.ts.map +1 -1
  89. package/dist/types/device.d.ts +1 -1
  90. package/dist/types/device.d.ts.map +1 -1
  91. package/dist/types/params.d.ts +2 -0
  92. package/dist/types/params.d.ts.map +1 -1
  93. package/dist/types/settings.d.ts +1 -1
  94. package/dist/types/settings.d.ts.map +1 -1
  95. package/dist/utils/deviceInfoUtils.d.ts +1 -0
  96. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  97. package/dist/utils/index.d.ts +1 -1
  98. package/dist/utils/index.d.ts.map +1 -1
  99. package/dist/utils/patch.d.ts +1 -1
  100. package/dist/utils/patch.d.ts.map +1 -1
  101. package/dist/utils/versionUtils.d.ts +1 -1
  102. package/package.json +4 -4
  103. package/src/api/BaseMethod.ts +12 -60
  104. package/src/api/DirList.ts +25 -0
  105. package/src/api/DirMake.ts +20 -0
  106. package/src/api/DirRemove.ts +20 -0
  107. package/src/api/FileDelete.ts +20 -0
  108. package/src/api/FileRead.ts +158 -0
  109. package/src/api/FileWrite.ts +191 -0
  110. package/src/api/FirmwareUpdateV3.ts +21 -4
  111. package/src/api/FirmwareUpdateV4.ts +810 -0
  112. package/src/api/GetOnekeyFeatures.ts +75 -3
  113. package/src/api/PathInfo.ts +24 -0
  114. package/src/api/SearchDevices.ts +7 -2
  115. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +10 -9
  116. package/src/api/device/DeviceRebootToBoardloader.ts +10 -1
  117. package/src/api/device/DeviceRebootToBootloader.ts +10 -1
  118. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -4
  119. package/src/api/index.ts +28 -0
  120. package/src/api/protocol-v2/DevFirmwareUpdate.ts +33 -0
  121. package/src/api/protocol-v2/DevGetDeviceInfo.ts +35 -0
  122. package/src/api/protocol-v2/DevGetFirmwareUpdateStatus.ts +18 -0
  123. package/src/api/protocol-v2/DevGetOnboardingStatus.ts +18 -0
  124. package/src/api/protocol-v2/DevReboot.ts +22 -0
  125. package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +27 -0
  126. package/src/api/protocol-v2/FactoryGetDeviceInfo.ts +18 -0
  127. package/src/api/protocol-v2/FilesystemFixPermission.ts +14 -0
  128. package/src/api/protocol-v2/FilesystemFormat.ts +14 -0
  129. package/src/api/protocol-v2/GetProtoVersion.ts +14 -0
  130. package/src/api/protocol-v2/Ping.ts +16 -0
  131. package/src/api/protocol-v2/helpers.ts +138 -0
  132. package/src/core/index.ts +26 -4
  133. package/src/data/messages/messages-pro2.json +13102 -0
  134. package/src/data-manager/DataManager.ts +6 -2
  135. package/src/data-manager/TransportManager.ts +29 -3
  136. package/src/device/Device.ts +68 -8
  137. package/src/device/DeviceCommands.ts +162 -26
  138. package/src/device/DeviceConnector.ts +29 -4
  139. package/src/device/DevicePool.ts +1 -1
  140. package/src/events/ui-request.ts +8 -0
  141. package/src/inject.ts +42 -1
  142. package/src/protocols/protocol-v2/features.ts +266 -0
  143. package/src/protocols/protocol-v2/firmware.ts +26 -0
  144. package/src/protocols/protocol-v2/index.ts +2 -0
  145. package/src/types/api/export.ts +1 -0
  146. package/src/types/api/firmwareUpdate.ts +12 -0
  147. package/src/types/api/index.ts +63 -1
  148. package/src/types/api/protocolV2.ts +221 -0
  149. package/src/types/api/searchDevices.ts +2 -2
  150. package/src/types/device.ts +3 -1
  151. package/src/types/params.ts +7 -0
  152. package/src/types/settings.ts +1 -1
  153. package/src/utils/deviceInfoUtils.ts +14 -5
  154. package/src/utils/index.ts +1 -0
  155. package/__tests__/DeviceCommands.test.ts +0 -99
  156. package/__tests__/evmLedgerLegacySafety.test.ts +0 -261
@@ -0,0 +1,810 @@
1
+ import { ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
2
+ import {
3
+ DevRebootType,
4
+ PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
5
+ PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
6
+ } from '@onekeyfe/hd-transport';
7
+ import JSZip from 'jszip';
8
+
9
+ import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
10
+ import { validateParams } from './helpers/paramsValidator';
11
+ import {
12
+ LoggerNames,
13
+ getDeviceBLEFirmwareVersion,
14
+ getDeviceBootloaderVersion,
15
+ getDeviceFirmwareVersion,
16
+ getFirmwareType,
17
+ getLogger,
18
+ } from '../utils';
19
+ import { getBinary, getSysResourceBinary } from './firmware/getBinary';
20
+ import { DataManager } from '../data-manager';
21
+ import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
22
+ import { DevicePool } from '../device/DevicePool';
23
+ import {
24
+ ProtocolV2FirmwareTargetType,
25
+ getProtocolV2Features,
26
+ protocolV2FileNameToTargetId,
27
+ } from '../protocols/protocol-v2';
28
+ import { PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES } from './protocol-v2/helpers';
29
+
30
+ import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
31
+ import type { EFirmwareType } from '@onekeyfe/hd-shared';
32
+ import type { PROTO } from '../constants';
33
+ import type { TypedResponseMessage } from '../device/DeviceCommands';
34
+
35
+ const Log = getLogger(LoggerNames.Method);
36
+
37
+ const SESSION_ERROR = 'session not found';
38
+ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
39
+ const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
40
+ const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
41
+ const PROTOCOL_V2_TARGET_STATUS_FINISHED = 0;
42
+ const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
43
+ const PROTOCOL_V2_TARGET_STATUS_FAILED = 2;
44
+ const PROTOCOL_V2_CONNECT_PROTOCOL = 'V2';
45
+ const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = 'vol1:';
46
+ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
47
+
48
+ type ProtocolV2FirmwareUpdateStatusTarget = {
49
+ target_id: number;
50
+ status: number;
51
+ };
52
+
53
+ type ProtocolV2FirmwareUpdateStartResponse =
54
+ | TypedResponseMessage<'Success'>
55
+ | TypedResponseMessage<'DevFirmwareUpdateStatus'>
56
+ | undefined;
57
+
58
+ const getUnknownErrorText = (error: unknown) => {
59
+ if (!error) {
60
+ return '';
61
+ }
62
+ if (typeof error === 'string') {
63
+ return error;
64
+ }
65
+
66
+ const parts: string[] = [];
67
+ if (error instanceof Error) {
68
+ parts.push(error.name, error.message);
69
+ }
70
+
71
+ if (typeof error === 'object') {
72
+ const record = error as Record<string, unknown>;
73
+ for (const field of ['name', 'message', 'reason', 'code', 'errorCode', 'nativeErrorCode']) {
74
+ const value = record[field];
75
+ if (value !== undefined && value !== null) {
76
+ parts.push(String(value));
77
+ }
78
+ }
79
+ }
80
+
81
+ const stringified = String(error);
82
+ if (stringified && stringified !== '[object Object]') {
83
+ parts.push(stringified);
84
+ }
85
+
86
+ return parts.filter(Boolean).join(' ');
87
+ };
88
+
89
+ const isDeviceDisconnectedError = (error: unknown) => {
90
+ const message = getUnknownErrorText(error).toLowerCase();
91
+ const compactMessage = message.replace(/\s+/g, '');
92
+ return (
93
+ message.includes('device was disconnected') ||
94
+ message.includes('device disconnected') ||
95
+ message.includes('device disconnect') ||
96
+ message.includes('was disconnected') ||
97
+ message.includes('bledevicedisconnected') ||
98
+ message.includes('bleconnectederror') ||
99
+ message.includes('connected error is always runtime error') ||
100
+ message.includes('connection has timed out unexpectedly') ||
101
+ message.includes('connection error has occured') ||
102
+ message.includes('connection error has occurred') ||
103
+ message.includes('transferIn') ||
104
+ message.includes('transferin') ||
105
+ message.includes('usbdevice') ||
106
+ message.includes('multiplatformbleadapter') ||
107
+ message.includes('multipalformebleadapter') ||
108
+ compactMessage.includes('rxerrorerror6') ||
109
+ message.includes('rxerror error 6')
110
+ );
111
+ };
112
+
113
+ const isProtocolV2ReconnectProbeError = (error: unknown) => {
114
+ const message = getUnknownErrorText(error).toLowerCase();
115
+ return (
116
+ (message.includes('device protocol mismatch') && message.includes('expected v2')) ||
117
+ message.includes('did not respond to expected protocol')
118
+ );
119
+ };
120
+
121
+ const isProtocolV2PollingTransientError = (error: unknown) => {
122
+ const message = getUnknownErrorText(error).toLowerCase();
123
+ return (
124
+ isDeviceDisconnectedError(error) ||
125
+ isProtocolV2ReconnectProbeError(error) ||
126
+ (message.includes('response timeout') && message.includes('devgetfirmwareupdatestatus')) ||
127
+ message.includes('device not found') ||
128
+ message.includes('transportnotfound')
129
+ );
130
+ };
131
+
132
+ /**
133
+ * FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
134
+ *
135
+ * It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
136
+ * - upload uses FilesystemFileWrite
137
+ * - install uses DevFirmwareUpdate
138
+ * - completion reboots to normal, then polls Ping
139
+ */
140
+ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
141
+ init() {
142
+ this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
143
+ this.requireDeviceMode = [];
144
+ this.useDevicePassphraseState = false;
145
+ this.skipForceUpdateCheck = true;
146
+
147
+ const { payload } = this;
148
+
149
+ validateParams(payload, [
150
+ { name: 'bleVersion', type: 'array' },
151
+ { name: 'bleBinary', type: 'buffer' },
152
+ { name: 'chunkSize', type: 'number' },
153
+ { name: 'firmwareVersion', type: 'array' },
154
+ { name: 'firmwareBinary', type: 'buffer' },
155
+ { name: 'resourceBinary', type: 'buffer' },
156
+ { name: 'forcedUpdateRes', type: 'boolean' },
157
+ { name: 'bootloaderVersion', type: 'array' },
158
+ { name: 'bootloaderBinary', type: 'buffer' },
159
+ { name: 'firmwareType', type: 'string' },
160
+ { name: 'platform', type: 'string' },
161
+ ]);
162
+
163
+ this.params = {
164
+ bleBinary: payload.bleBinary,
165
+ chunkSize: payload.chunkSize,
166
+ firmwareBinary: payload.firmwareBinary,
167
+ forcedUpdateRes: payload.forcedUpdateRes,
168
+ bleVersion: payload.bleVersion,
169
+ bootloaderVersion: payload.bootloaderVersion,
170
+ bootloaderBinary: payload.bootloaderBinary,
171
+ firmwareVersion: payload.firmwareVersion,
172
+ resourceBinary: payload.resourceBinary,
173
+ firmwareType: payload.firmwareType,
174
+ platform: payload.platform,
175
+ };
176
+ }
177
+
178
+ private getProtocolV2FirmwareChunkSize() {
179
+ const payloadChunkSize = Number(this.params?.chunkSize);
180
+ const env = DataManager.getSettings('env');
181
+ const maxChunkSize =
182
+ this.params?.platform === 'native' || (env && DataManager.isBleConnect(env))
183
+ ? PROTOCOL_V2_BLE_FILE_CHUNK_SIZE
184
+ : PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
185
+ if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
186
+ return maxChunkSize;
187
+ }
188
+ return Math.min(
189
+ Math.max(Math.floor(payloadChunkSize), PROTOCOL_V2_MIN_FILE_CHUNK_SIZE),
190
+ maxChunkSize
191
+ );
192
+ }
193
+
194
+ async run() {
195
+ if (this.device.originalDescriptor?.protocolType !== 'V2') {
196
+ throw ERRORS.TypedError(
197
+ HardwareErrorCode.RuntimeError,
198
+ 'firmwareUpdateV4 requires a Protocol V2 device'
199
+ );
200
+ }
201
+
202
+ Log.debug('FirmwareUpdateV4 strategy: Protocol V2');
203
+ return this.runProtocolV2();
204
+ }
205
+
206
+ private async runProtocolV2() {
207
+ const { device } = this;
208
+ const { features } = device;
209
+
210
+ if (!features) {
211
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
212
+ }
213
+
214
+ const deviceFirmwareType = getFirmwareType(features);
215
+ const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
216
+
217
+ let resourceBinary: ArrayBuffer | null = null;
218
+ let fwBinaryMap: { fileName: string; binary: ArrayBuffer }[] = [];
219
+ let bootloaderBinary: ArrayBuffer | null = null;
220
+ try {
221
+ this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
222
+ resourceBinary = await this.prepareResourceBinary(firmwareType);
223
+ fwBinaryMap = await this.prepareFirmwareAndBleBinary(firmwareType);
224
+ bootloaderBinary = await this.prepareBootloaderBinary(firmwareType);
225
+ this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
226
+ } catch (err) {
227
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
228
+ }
229
+
230
+ if (!resourceBinary && !bootloaderBinary && fwBinaryMap.length === 0) {
231
+ throw ERRORS.TypedError(
232
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
233
+ 'No firmware to update'
234
+ );
235
+ }
236
+
237
+ await this.executeProtocolV2Update({
238
+ resourceBinary,
239
+ fwBinaryMap,
240
+ bootloaderBinary,
241
+ });
242
+
243
+ await this.exitProtocolV2BootloaderToNormal();
244
+
245
+ const versions = await this.waitForProtocolV2FinalFeatures();
246
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
247
+ DevicePool.resetState();
248
+
249
+ return versions;
250
+ }
251
+
252
+ private async prepareResourceBinary(firmwareType: EFirmwareType) {
253
+ if (this.params.resourceBinary) {
254
+ return this.params.resourceBinary;
255
+ }
256
+ const { features } = this.device;
257
+ if (!features) return null;
258
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
259
+ features,
260
+ forcedUpdateRes: this.params.forcedUpdateRes,
261
+ firmwareType,
262
+ });
263
+
264
+ if (resourceUrl) {
265
+ const resource = (await getSysResourceBinary(resourceUrl)).binary;
266
+ return resource;
267
+ }
268
+ Log.warn('No resource url found');
269
+ return null;
270
+ }
271
+
272
+ private async prepareBootloaderBinary(firmwareType: EFirmwareType): Promise<ArrayBuffer | null> {
273
+ if (this.params.bootloaderBinary) {
274
+ return this.params.bootloaderBinary;
275
+ }
276
+ const { features } = this.device;
277
+ if (!features) return null;
278
+
279
+ if (this.params.bootloaderVersion) {
280
+ const bootResourceUrl = DataManager.getBootloaderResource(features, firmwareType);
281
+ if (bootResourceUrl) {
282
+ const bootBinary = (await getSysResourceBinary(bootResourceUrl)).binary;
283
+ return bootBinary;
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+
289
+ private async prepareFirmwareAndBleBinary(firmwareType: EFirmwareType) {
290
+ const fwBinaryMap: { fileName: string; binary: ArrayBuffer }[] = [];
291
+ if (this.params.firmwareBinary) {
292
+ fwBinaryMap.push({
293
+ fileName: 'firmware.bin',
294
+ binary: this.params.firmwareBinary,
295
+ });
296
+ } else if (this.params.firmwareVersion) {
297
+ const { features } = this.device;
298
+ if (features) {
299
+ const firmwareBinary = (
300
+ await getBinary({
301
+ features,
302
+ version: this.params.firmwareVersion,
303
+ updateType: 'firmware',
304
+ isUpdateBootloader: false,
305
+ firmwareType,
306
+ })
307
+ ).binary;
308
+ fwBinaryMap.push({
309
+ fileName: 'firmware.bin',
310
+ binary: firmwareBinary,
311
+ });
312
+ }
313
+ }
314
+
315
+ if (this.params.bleBinary) {
316
+ fwBinaryMap.push({
317
+ fileName: 'ble-firmware.bin',
318
+ binary: this.params.bleBinary,
319
+ });
320
+ } else if (this.params.bleVersion) {
321
+ const { features } = this.device;
322
+ if (features) {
323
+ const bleBinary = await getBinary({
324
+ features,
325
+ version: this.params.bleVersion,
326
+ updateType: 'ble',
327
+ firmwareType,
328
+ });
329
+ fwBinaryMap.push({
330
+ fileName: 'ble-firmware.bin',
331
+ binary: bleBinary.binary,
332
+ });
333
+ }
334
+ }
335
+
336
+ return fwBinaryMap;
337
+ }
338
+
339
+ private async executeProtocolV2Update({
340
+ resourceBinary,
341
+ fwBinaryMap,
342
+ bootloaderBinary,
343
+ }: {
344
+ resourceBinary: ArrayBuffer | null;
345
+ fwBinaryMap: { fileName: string; binary: ArrayBuffer }[];
346
+ bootloaderBinary: ArrayBuffer | null;
347
+ }) {
348
+ let totalSize = 0;
349
+ let processedSize = 0;
350
+
351
+ if (resourceBinary) totalSize += resourceBinary.byteLength;
352
+ for (const fwbinary of fwBinaryMap) totalSize += fwbinary.binary.byteLength;
353
+ if (bootloaderBinary) totalSize += bootloaderBinary.byteLength;
354
+
355
+ this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
356
+ const transferStartTime = Date.now();
357
+
358
+ const targets: Array<{ target_id: number; path: string }> = [];
359
+
360
+ if (resourceBinary) {
361
+ const resourcePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}res/`;
362
+ await this.protocolV2CreateFolder(resourcePath);
363
+ const file = await JSZip.loadAsync(resourceBinary);
364
+ const files = Object.entries(file.files);
365
+ for (const [fileName, entry] of files) {
366
+ const name = fileName.split('/').pop();
367
+ if (!entry.dir && fileName.indexOf('__MACOSX') === -1 && name) {
368
+ const data = await entry.async('arraybuffer');
369
+ processedSize = await this.protocolV2CommonUpdateProcess({
370
+ payload: data,
371
+ filePath: `${resourcePath}${name}`,
372
+ processedSize,
373
+ totalSize,
374
+ transferStartTime,
375
+ });
376
+ }
377
+ }
378
+ targets.push({
379
+ target_id: ProtocolV2FirmwareTargetType.TARGET_RESOURCE,
380
+ path: resourcePath,
381
+ });
382
+ }
383
+
384
+ if (bootloaderBinary) {
385
+ const bootloaderPath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}bootloader.bin`;
386
+ processedSize = await this.protocolV2CommonUpdateProcess({
387
+ payload: bootloaderBinary,
388
+ filePath: bootloaderPath,
389
+ processedSize,
390
+ totalSize,
391
+ transferStartTime,
392
+ });
393
+ targets.push({
394
+ target_id: ProtocolV2FirmwareTargetType.TARGET_MAIN_BOOT,
395
+ path: bootloaderPath,
396
+ });
397
+ }
398
+
399
+ for (const fwbinary of fwBinaryMap) {
400
+ const firmwarePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${fwbinary.fileName}`;
401
+ processedSize = await this.protocolV2CommonUpdateProcess({
402
+ payload: fwbinary.binary,
403
+ filePath: firmwarePath,
404
+ processedSize,
405
+ totalSize,
406
+ transferStartTime,
407
+ });
408
+ targets.push({
409
+ target_id: protocolV2FileNameToTargetId(fwbinary.fileName),
410
+ path: firmwarePath,
411
+ });
412
+ }
413
+
414
+ this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
415
+ const startResponse = await this.protocolV2StartFirmwareUpdate({ targets });
416
+ await this.waitForProtocolV2FirmwareUpdateComplete(targets, startResponse);
417
+ }
418
+
419
+ private async queryProtocolV2FirmwareUpdateStatus() {
420
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
421
+ return typedCall(
422
+ 'DevGetFirmwareUpdateStatus',
423
+ 'DevFirmwareUpdateStatus',
424
+ {},
425
+ {
426
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
427
+ }
428
+ );
429
+ }
430
+
431
+ private async pingProtocolV2Device() {
432
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
433
+ await typedCall(
434
+ 'Ping',
435
+ 'Success',
436
+ { message: 'firmware-update' },
437
+ {
438
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
439
+ }
440
+ );
441
+ }
442
+
443
+ private assertProtocolV2TargetStatus(
444
+ statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
445
+ expectedTargetIds: Set<number>
446
+ ) {
447
+ const failedTarget = statusTargets.find(
448
+ target =>
449
+ expectedTargetIds.has(target.target_id) &&
450
+ target.status === PROTOCOL_V2_TARGET_STATUS_FAILED
451
+ );
452
+ if (failedTarget) {
453
+ throw ERRORS.TypedError(
454
+ HardwareErrorCode.FirmwareError,
455
+ `Protocol V2 firmware target ${failedTarget.target_id} failed`
456
+ );
457
+ }
458
+
459
+ const completedTargets = statusTargets.filter(
460
+ target =>
461
+ expectedTargetIds.has(target.target_id) &&
462
+ target.status === PROTOCOL_V2_TARGET_STATUS_FINISHED
463
+ );
464
+ if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
465
+ return true;
466
+ }
467
+
468
+ const inProgressTarget = statusTargets.find(
469
+ target =>
470
+ expectedTargetIds.has(target.target_id) &&
471
+ target.status === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS
472
+ );
473
+ if (inProgressTarget) {
474
+ this.postProgressMessage(99, 'installingFirmware');
475
+ }
476
+
477
+ return false;
478
+ }
479
+
480
+ private async waitForProtocolV2FirmwareUpdateComplete(
481
+ targets: Array<{ target_id: number; path: string }>,
482
+ startResponse?: ProtocolV2FirmwareUpdateStartResponse
483
+ ) {
484
+ const expectedTargetIds = new Set(targets.map(target => target.target_id));
485
+ if (startResponse?.type === 'Success') {
486
+ return;
487
+ }
488
+ if (startResponse?.type === 'DevFirmwareUpdateStatus') {
489
+ const statusTargets = (startResponse.message.targets ??
490
+ []) as ProtocolV2FirmwareUpdateStatusTarget[];
491
+ if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
492
+ return;
493
+ }
494
+ }
495
+
496
+ const startTime = Date.now();
497
+ let lastError: unknown;
498
+
499
+ while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
500
+ try {
501
+ const statusRes = await this.queryProtocolV2FirmwareUpdateStatus();
502
+ const statusTargets = (statusRes.message.targets ??
503
+ []) as ProtocolV2FirmwareUpdateStatusTarget[];
504
+ if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
505
+ return;
506
+ }
507
+ } catch (error) {
508
+ lastError = error;
509
+ if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
510
+ throw error;
511
+ }
512
+ Log.log('Protocol V2 firmware install status polling failed: ', error);
513
+ if (isProtocolV2PollingTransientError(error)) {
514
+ try {
515
+ await this.reconnectProtocolV2Device();
516
+ } catch (reconnectError) {
517
+ lastError = reconnectError;
518
+ Log.log(
519
+ 'Protocol V2 firmware install reconnect/status polling failed: ',
520
+ reconnectError
521
+ );
522
+ }
523
+ try {
524
+ await this.pingProtocolV2Device();
525
+ Log.log('Protocol V2 firmware status unavailable, Ping is ready');
526
+ return;
527
+ } catch (pingError) {
528
+ lastError = pingError;
529
+ Log.log('Protocol V2 firmware install Ping polling failed: ', pingError);
530
+ }
531
+ }
532
+ }
533
+ await wait(1000);
534
+ }
535
+
536
+ throw ERRORS.TypedError(
537
+ HardwareErrorCode.RuntimeError,
538
+ `Protocol V2 firmware update status timeout: ${this.normalizeErrorMessage(lastError)}`
539
+ );
540
+ }
541
+
542
+ private async exitProtocolV2BootloaderToNormal() {
543
+ this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
544
+ await this.protocolV2Reboot(DevRebootType.Normal);
545
+ }
546
+
547
+ private async waitForProtocolV2FinalFeatures() {
548
+ const features = await this.waitForProtocolV2ReconnectAndFeatures(
549
+ PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT
550
+ );
551
+ this.device._updateFeatures(features);
552
+
553
+ const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
554
+ const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
555
+ const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
556
+ if (firmwareVersion === '0.0.0') {
557
+ Log.warn(
558
+ 'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
559
+ );
560
+ }
561
+
562
+ return {
563
+ bootloaderVersion,
564
+ bleVersion,
565
+ firmwareVersion,
566
+ };
567
+ }
568
+
569
+ private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
570
+ const startTime = Date.now();
571
+ let lastError: unknown;
572
+
573
+ while (Date.now() - startTime < timeout) {
574
+ try {
575
+ await this.reconnectProtocolV2Device();
576
+ const features = await getProtocolV2Features({
577
+ commands: this.device.getCommands(),
578
+ descriptor: this.device.originalDescriptor,
579
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
580
+ });
581
+ return features;
582
+ } catch (error) {
583
+ lastError = error;
584
+ Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
585
+ await wait(1000);
586
+ }
587
+ }
588
+
589
+ throw ERRORS.TypedError(
590
+ HardwareErrorCode.DeviceNotFound,
591
+ `Protocol V2 final features not ready within ${timeout / 1000}s: ${this.normalizeErrorMessage(
592
+ lastError
593
+ )}`
594
+ );
595
+ }
596
+
597
+ private async reconnectProtocolV2Device() {
598
+ if (this.isBleReconnect()) {
599
+ await this.acquireProtocolV2BleDevice();
600
+ return;
601
+ }
602
+
603
+ const deviceDiff = await this.device.deviceConnector?.enumerate();
604
+ const devicesDescriptor = deviceDiff?.descriptors ?? [];
605
+
606
+ const { deviceList } = await DevicePool.getDevices(devicesDescriptor, this.connectId);
607
+ if (deviceList.length !== 1) {
608
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
609
+ }
610
+
611
+ this.device.updateFromCache(deviceList[0]);
612
+ await this.device.acquire();
613
+ this.device.commands.disposed = false;
614
+ this.device.getCommands().mainId = this.device.mainId ?? '';
615
+ }
616
+
617
+ private async protocolV2CreateFolder(path: string) {
618
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
619
+ await typedCall('FilesystemDirMake', 'Success', { path });
620
+ }
621
+
622
+ private async protocolV2CommonUpdateProcess({
623
+ payload,
624
+ filePath,
625
+ processedSize,
626
+ totalSize,
627
+ transferStartTime = Date.now(),
628
+ }: PROTO.FirmwareUpload & {
629
+ filePath: string;
630
+ processedSize?: number;
631
+ totalSize?: number;
632
+ transferStartTime?: number;
633
+ }) {
634
+ const chunkSize = this.getProtocolV2FirmwareChunkSize();
635
+ let offset = 0;
636
+ const getUploadProgress = (fileOffset: number) => {
637
+ if (totalSize !== undefined && processedSize !== undefined) {
638
+ return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
639
+ }
640
+ return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
641
+ };
642
+
643
+ while (offset < payload.byteLength) {
644
+ const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
645
+ const chunkLength = chunkEnd - offset;
646
+ const chunk = payload.slice(offset, chunkEnd);
647
+ const overwrite = offset === 0;
648
+ const progress = getUploadProgress(chunkEnd);
649
+
650
+ const writeRes = await this.fileWriteWithRetry(
651
+ filePath,
652
+ payload.byteLength,
653
+ offset,
654
+ chunk,
655
+ overwrite,
656
+ progress
657
+ );
658
+ const nextOffset = writeRes.message.processed_byte ?? offset + chunkLength;
659
+ if (nextOffset <= offset || nextOffset > payload.byteLength) {
660
+ throw ERRORS.TypedError(
661
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
662
+ `invalid processed_byte ${nextOffset} for offset ${offset}`
663
+ );
664
+ }
665
+ offset = nextOffset;
666
+ const elapsedMs = Date.now() - transferStartTime;
667
+ const transferredBytes =
668
+ totalSize !== undefined && processedSize !== undefined
669
+ ? Math.min(processedSize + offset, totalSize)
670
+ : offset;
671
+ const totalBytes = totalSize ?? payload.byteLength;
672
+ this.postProgressMessage(getUploadProgress(offset), 'transferData', {
673
+ transferredBytes,
674
+ totalBytes,
675
+ rateBytesPerSecond:
676
+ elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
677
+ elapsedMs,
678
+ });
679
+ }
680
+
681
+ return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
682
+ }
683
+
684
+ private async fileWriteWithRetry(
685
+ filePath: string,
686
+ totalFileSize: number,
687
+ offset: number,
688
+ chunk: ArrayBuffer | Buffer,
689
+ overwrite: boolean,
690
+ progress: number | null
691
+ ): Promise<TypedResponseMessage<'FilesystemFile'>> {
692
+ const writeFunc = async () => {
693
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
694
+ const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
695
+ file: {
696
+ path: filePath,
697
+ offset,
698
+ total_size: totalFileSize,
699
+ data: chunk,
700
+ },
701
+ overwrite,
702
+ append: false,
703
+ ui_percentage: progress ?? undefined,
704
+ });
705
+ if (writeRes.type !== 'FilesystemFile') {
706
+ if ((writeRes as any).type === 'CallMethodError') {
707
+ if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
708
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
709
+ }
710
+ }
711
+ throw ERRORS.TypedError(
712
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
713
+ 'transfer data error'
714
+ );
715
+ }
716
+ return writeRes;
717
+ };
718
+
719
+ let retryCount = 10;
720
+ while (retryCount > 0) {
721
+ try {
722
+ const result = await writeFunc();
723
+ return result;
724
+ } catch (error) {
725
+ Log.error(`fileWrite error: `, error);
726
+ retryCount--;
727
+ if (retryCount === 0) {
728
+ throw ERRORS.TypedError(
729
+ HardwareErrorCode.EmmcFileWriteFirmwareError,
730
+ 'transfer data error'
731
+ );
732
+ }
733
+ const env = DataManager.getSettings('env');
734
+ if (DataManager.isBleConnect(env)) {
735
+ await wait(3000);
736
+ await this.acquireProtocolV2BleDevice();
737
+ await this.device.initialize();
738
+ }
739
+ await wait(2000);
740
+ }
741
+ }
742
+ throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
743
+ }
744
+
745
+ private async acquireProtocolV2BleDevice() {
746
+ await this.device.deviceConnector?.acquire(
747
+ this.device.originalDescriptor.id,
748
+ null,
749
+ true,
750
+ PROTOCOL_V2_CONNECT_PROTOCOL
751
+ );
752
+ }
753
+
754
+ private async protocolV2StartFirmwareUpdate({
755
+ targets,
756
+ }: {
757
+ targets: Array<{ target_id: number; path: string }>;
758
+ }) {
759
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
760
+ let response: ProtocolV2FirmwareUpdateStartResponse;
761
+ try {
762
+ response = await typedCall(
763
+ 'DevFirmwareUpdate',
764
+ PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
765
+ {
766
+ targets,
767
+ },
768
+ {
769
+ intermediateTypes: ['DevFirmwareInstallProgress'],
770
+ onIntermediateResponse: (response: { message?: { progress?: number } }) => {
771
+ const progress = Number(response.message?.progress);
772
+ if (Number.isFinite(progress)) {
773
+ this.postProgressMessage(Math.min(progress, 99), 'installingFirmware');
774
+ }
775
+ },
776
+ }
777
+ );
778
+ } catch (error) {
779
+ if (isDeviceDisconnectedError(error)) {
780
+ Log.log('Rebooting device');
781
+ } else {
782
+ throw error;
783
+ }
784
+ }
785
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
786
+ return response;
787
+ }
788
+
789
+ private async protocolV2Reboot(rebootType: DevRebootType) {
790
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
791
+ try {
792
+ const res = await typedCall('DevReboot', 'Success', {
793
+ reboot_type: rebootType,
794
+ });
795
+ return res.message;
796
+ } catch (error) {
797
+ if (isDeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
798
+ return { message: 'Device rebooted successfully' };
799
+ }
800
+ throw error;
801
+ }
802
+ }
803
+
804
+ private normalizeErrorMessage(error: unknown): string {
805
+ if (!error) {
806
+ return '';
807
+ }
808
+ return getUnknownErrorText(error);
809
+ }
810
+ }