@onekeyfe/hd-core 1.2.8-alpha.120 → 1.2.8-alpha.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/protocol-v2.test.ts +0 -542
- package/dist/api/FirmwareUpdateV4.d.ts +0 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/index.js +46 -225
- package/dist/utils/logger.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +61 -314
- package/src/utils/logger.ts +0 -4
- package/__tests__/logger.test.ts +0 -18
|
@@ -44,7 +44,6 @@ import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
|
|
|
44
44
|
import FirmwareUpdateV4, {
|
|
45
45
|
assertProtocolV2FirmwareTargetsSupported,
|
|
46
46
|
assertProtocolV2ReconnectIdentity,
|
|
47
|
-
supportsProtocolV2BleFileWriteWindow,
|
|
48
47
|
} from '../src/api/FirmwareUpdateV4';
|
|
49
48
|
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
50
49
|
import GetOnekeyFeatures from '../src/api/GetOnekeyFeatures';
|
|
@@ -8884,526 +8883,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8884
8883
|
expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
|
|
8885
8884
|
});
|
|
8886
8885
|
|
|
8887
|
-
describe('RESC header check link recovery', () => {
|
|
8888
|
-
const currentHeader = {
|
|
8889
|
-
version: [1, 0, 0],
|
|
8890
|
-
payloadHash: '11'.repeat(64),
|
|
8891
|
-
headerHash: '22'.repeat(64),
|
|
8892
|
-
};
|
|
8893
|
-
const linkLost = () =>
|
|
8894
|
-
ERRORS.TypedError(
|
|
8895
|
-
HardwareErrorCode.RuntimeError,
|
|
8896
|
-
'Device protocol has not been detected for AA:BB'
|
|
8897
|
-
);
|
|
8898
|
-
const createResource = (name: string) => ({
|
|
8899
|
-
name,
|
|
8900
|
-
source: { size: 3, readAt: jest.fn(), close: jest.fn() },
|
|
8901
|
-
devicePath: `vol0:/bundles/${name}`,
|
|
8902
|
-
...currentHeader,
|
|
8903
|
-
});
|
|
8904
|
-
const createMethod = () => {
|
|
8905
|
-
const method = new FirmwareUpdateV4({
|
|
8906
|
-
id: 1,
|
|
8907
|
-
payload: {
|
|
8908
|
-
method: 'firmwareUpdateV4',
|
|
8909
|
-
},
|
|
8910
|
-
});
|
|
8911
|
-
method.postTipMessage = jest.fn();
|
|
8912
|
-
method.postProgressMessage = jest.fn();
|
|
8913
|
-
(method as any).protocolV2SourceUpdateProcess = jest.fn(
|
|
8914
|
-
({ processedSize }: { processedSize: number }) => Promise.resolve(processedSize + 3)
|
|
8915
|
-
);
|
|
8916
|
-
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
8917
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
8918
|
-
return method;
|
|
8919
|
-
};
|
|
8920
|
-
|
|
8921
|
-
test('reconnects and asks again when the link drops during a header check', async () => {
|
|
8922
|
-
const method = createMethod();
|
|
8923
|
-
(method as any).readProtocolV2DeviceFileHeader = jest
|
|
8924
|
-
.fn()
|
|
8925
|
-
.mockRejectedValueOnce(linkLost())
|
|
8926
|
-
.mockResolvedValue(currentHeader);
|
|
8927
|
-
|
|
8928
|
-
await (method as any).executeProtocolV2TransferPhase({
|
|
8929
|
-
installSources: [],
|
|
8930
|
-
resourceSources: [createResource('images.okpkg'), createResource('fonts.okpkg')],
|
|
8931
|
-
});
|
|
8932
|
-
|
|
8933
|
-
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
8934
|
-
expect((method as any).readProtocolV2DeviceFileHeader).toHaveBeenCalledTimes(3);
|
|
8935
|
-
expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
|
|
8936
|
-
});
|
|
8937
|
-
|
|
8938
|
-
test.each([
|
|
8939
|
-
['BLE disconnect', () => ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected)],
|
|
8940
|
-
['BLE timeout', () => ERRORS.TypedError(HardwareErrorCode.BleTimeoutError)],
|
|
8941
|
-
[
|
|
8942
|
-
'invalidated Protocol V2 link',
|
|
8943
|
-
() =>
|
|
8944
|
-
Object.assign(new Error('Protocol V2 link generation changed'), {
|
|
8945
|
-
name: 'ProtocolV2LinkError',
|
|
8946
|
-
code: 'generation',
|
|
8947
|
-
}),
|
|
8948
|
-
],
|
|
8949
|
-
[
|
|
8950
|
-
'released React Native BLE transport',
|
|
8951
|
-
() => new Error('React Native BLE transport released'),
|
|
8952
|
-
],
|
|
8953
|
-
['released Electron BLE transport', () => new Error('Electron BLE transport released')],
|
|
8954
|
-
['cleaned Electron BLE state', () => new Error('Electron BLE device state cleaned')],
|
|
8955
|
-
[
|
|
8956
|
-
'Electron BLE device that is no longer connected',
|
|
8957
|
-
() => ERRORS.TypedError(HardwareErrorCode.TransportNotFound, 'Device AA:BB not connected'),
|
|
8958
|
-
],
|
|
8959
|
-
])('rethrows a %s from the header check for recovery', async (_label, createError) => {
|
|
8960
|
-
const method = createMethod();
|
|
8961
|
-
const error = createError();
|
|
8962
|
-
(method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(error);
|
|
8963
|
-
|
|
8964
|
-
await expect(
|
|
8965
|
-
(method as any).isProtocolV2ResourceBundleUpToDate(createResource('images.okpkg'))
|
|
8966
|
-
).rejects.toBe(error);
|
|
8967
|
-
});
|
|
8968
|
-
|
|
8969
|
-
test('treats a device-side header read failure as stale without reconnecting', async () => {
|
|
8970
|
-
const method = createMethod();
|
|
8971
|
-
(method as any).readProtocolV2DeviceFileHeader = jest
|
|
8972
|
-
.fn()
|
|
8973
|
-
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Failure: busy'));
|
|
8974
|
-
|
|
8975
|
-
await (method as any).executeProtocolV2TransferPhase({
|
|
8976
|
-
installSources: [],
|
|
8977
|
-
resourceSources: [createResource('images.okpkg')],
|
|
8978
|
-
});
|
|
8979
|
-
|
|
8980
|
-
expect((method as any).recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8981
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
8982
|
-
});
|
|
8983
|
-
|
|
8984
|
-
test('limits header-check reconnects per transfer phase', async () => {
|
|
8985
|
-
const method = createMethod();
|
|
8986
|
-
(method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
|
|
8987
|
-
|
|
8988
|
-
await (method as any).executeProtocolV2TransferPhase({
|
|
8989
|
-
installSources: [],
|
|
8990
|
-
resourceSources: ['a.okpkg', 'b.okpkg', 'c.okpkg'].map(createResource),
|
|
8991
|
-
});
|
|
8992
|
-
|
|
8993
|
-
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(2);
|
|
8994
|
-
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(3);
|
|
8995
|
-
});
|
|
8996
|
-
|
|
8997
|
-
test.each([
|
|
8998
|
-
['no device to reconnect to', () => ERRORS.TypedError(HardwareErrorCode.DeviceNotFound)],
|
|
8999
|
-
[
|
|
9000
|
-
'an identity mismatch',
|
|
9001
|
-
() =>
|
|
9002
|
-
ERRORS.TypedError(
|
|
9003
|
-
HardwareErrorCode.DeviceNotFound,
|
|
9004
|
-
'Protocol V2 reconnect physical identity mismatch: expected A, received B'
|
|
9005
|
-
),
|
|
9006
|
-
],
|
|
9007
|
-
[
|
|
9008
|
-
'an unavailable identity',
|
|
9009
|
-
() =>
|
|
9010
|
-
ERRORS.TypedError(
|
|
9011
|
-
HardwareErrorCode.DeviceNotFound,
|
|
9012
|
-
'Protocol V2 reconnect physical identity unavailable: expected path A'
|
|
9013
|
-
),
|
|
9014
|
-
],
|
|
9015
|
-
[
|
|
9016
|
-
'a failed identity query',
|
|
9017
|
-
() => ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Failure: busy'),
|
|
9018
|
-
],
|
|
9019
|
-
])(
|
|
9020
|
-
'aborts instead of resending when the header-check reconnect fails with %s',
|
|
9021
|
-
async (_label, createError) => {
|
|
9022
|
-
const method = createMethod();
|
|
9023
|
-
const error = createError();
|
|
9024
|
-
(method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
|
|
9025
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockRejectedValue(error);
|
|
9026
|
-
|
|
9027
|
-
await expect(
|
|
9028
|
-
(method as any).executeProtocolV2TransferPhase({
|
|
9029
|
-
installSources: [],
|
|
9030
|
-
resourceSources: [createResource('images.okpkg'), createResource('fonts.okpkg')],
|
|
9031
|
-
})
|
|
9032
|
-
).rejects.toBe(error);
|
|
9033
|
-
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
9034
|
-
expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
|
|
9035
|
-
}
|
|
9036
|
-
);
|
|
9037
|
-
|
|
9038
|
-
test('writes nothing to a reconnected device whose identity query fails', async () => {
|
|
9039
|
-
const method = createMethod();
|
|
9040
|
-
// Run the real recovery: the session is replaced before the identity check.
|
|
9041
|
-
delete (method as any).recoverProtocolV2FileTransfer;
|
|
9042
|
-
const initialize = jest.fn();
|
|
9043
|
-
(method as any).device = stubDevice({ initialize });
|
|
9044
|
-
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
9045
|
-
(method as any).verifyProtocolV2ReconnectIdentity = jest
|
|
9046
|
-
.fn()
|
|
9047
|
-
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Failure: busy'));
|
|
9048
|
-
(method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
|
|
9049
|
-
|
|
9050
|
-
await expect(
|
|
9051
|
-
(method as any).executeProtocolV2TransferPhase({
|
|
9052
|
-
installSources: [],
|
|
9053
|
-
resourceSources: [createResource('images.okpkg')],
|
|
9054
|
-
})
|
|
9055
|
-
).rejects.toMatchObject({ message: expect.stringContaining('Failure: busy') });
|
|
9056
|
-
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
9057
|
-
expect(initialize).not.toHaveBeenCalled();
|
|
9058
|
-
expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
|
|
9059
|
-
});
|
|
9060
|
-
|
|
9061
|
-
test('reports a cancel that tears the header-check reconnect down as the cancel', async () => {
|
|
9062
|
-
const method = createMethod();
|
|
9063
|
-
delete (method as any).recoverProtocolV2FileTransfer;
|
|
9064
|
-
const abortController = new AbortController();
|
|
9065
|
-
method.abortSignal = abortController.signal;
|
|
9066
|
-
(method as any).reconnectProtocolV2Device = jest.fn(() => {
|
|
9067
|
-
abortController.abort();
|
|
9068
|
-
return Promise.reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
9069
|
-
});
|
|
9070
|
-
(method as any).readProtocolV2DeviceFileHeader = jest.fn().mockRejectedValue(linkLost());
|
|
9071
|
-
|
|
9072
|
-
await expect(
|
|
9073
|
-
(method as any).executeProtocolV2TransferPhase({
|
|
9074
|
-
installSources: [],
|
|
9075
|
-
resourceSources: [createResource('images.okpkg')],
|
|
9076
|
-
})
|
|
9077
|
-
).rejects.toMatchObject({ errorCode: HardwareErrorCode.CallQueueActionCancelled });
|
|
9078
|
-
expect((method as any).protocolV2SourceUpdateProcess).not.toHaveBeenCalled();
|
|
9079
|
-
});
|
|
9080
|
-
});
|
|
9081
|
-
|
|
9082
|
-
test.each([
|
|
9083
|
-
['1.0.4', '1.0.4', true],
|
|
9084
|
-
['1.0.10', '1.0.4', true],
|
|
9085
|
-
['1.0.3', '1.0.4', false],
|
|
9086
|
-
['0.0.0.1', '1.0.4', false],
|
|
9087
|
-
['1.0.4', '', false],
|
|
9088
|
-
] as const)(
|
|
9089
|
-
'gates the BLE file write window on bootloader %s with minimum %s',
|
|
9090
|
-
(bootloaderVersion, minBootloaderVersion, expected) => {
|
|
9091
|
-
expect(supportsProtocolV2BleFileWriteWindow(bootloaderVersion, minBootloaderVersion)).toBe(
|
|
9092
|
-
expected
|
|
9093
|
-
);
|
|
9094
|
-
}
|
|
9095
|
-
);
|
|
9096
|
-
|
|
9097
|
-
test('keeps released 1.0.2 and 1.0.3 bootloaders serial by default', () => {
|
|
9098
|
-
expect(supportsProtocolV2BleFileWriteWindow('1.0.4')).toBe(true);
|
|
9099
|
-
expect(supportsProtocolV2BleFileWriteWindow('1.0.3')).toBe(false);
|
|
9100
|
-
expect(supportsProtocolV2BleFileWriteWindow('1.0.2')).toBe(false);
|
|
9101
|
-
});
|
|
9102
|
-
|
|
9103
|
-
test.each([
|
|
9104
|
-
['react-native', 'bootloader', true],
|
|
9105
|
-
['desktop-web-ble', 'bootloader', true],
|
|
9106
|
-
['lowlevel', 'bootloader', false],
|
|
9107
|
-
['webusb', 'bootloader', false],
|
|
9108
|
-
['react-native', 'normal', false],
|
|
9109
|
-
] as const)(
|
|
9110
|
-
'enables the BLE file write window for %s in %s mode only with a supported bootloader',
|
|
9111
|
-
(env, mode, expected) => {
|
|
9112
|
-
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue(env);
|
|
9113
|
-
const method = new FirmwareUpdateV4({
|
|
9114
|
-
id: 1,
|
|
9115
|
-
payload: {
|
|
9116
|
-
method: 'firmwareUpdateV4',
|
|
9117
|
-
},
|
|
9118
|
-
});
|
|
9119
|
-
(method as any).device = stubDevice({
|
|
9120
|
-
features: { mode, bootloaderMode: mode === 'bootloader', bootloaderVersion: '1.0.4' },
|
|
9121
|
-
});
|
|
9122
|
-
|
|
9123
|
-
try {
|
|
9124
|
-
expect((method as any).shouldUseProtocolV2BleFileWriteWindow('1.0.4')).toBe(expected);
|
|
9125
|
-
expect((method as any).shouldUseProtocolV2BleFileWriteWindow('1.0.5')).toBe(false);
|
|
9126
|
-
} finally {
|
|
9127
|
-
getSettingsSpy.mockRestore();
|
|
9128
|
-
}
|
|
9129
|
-
}
|
|
9130
|
-
);
|
|
9131
|
-
|
|
9132
|
-
test.each([
|
|
9133
|
-
['1.0.2', false],
|
|
9134
|
-
['1.0.3', false],
|
|
9135
|
-
['1.0.4', true],
|
|
9136
|
-
// The parsed bootloader version drops prerelease tags.
|
|
9137
|
-
['1.0.4-rc.1', true],
|
|
9138
|
-
] as const)(
|
|
9139
|
-
'gates the default minimum on a running %s bootloader in React Native',
|
|
9140
|
-
(bootloaderVersion, expected) => {
|
|
9141
|
-
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native');
|
|
9142
|
-
const method = new FirmwareUpdateV4({
|
|
9143
|
-
id: 1,
|
|
9144
|
-
payload: {
|
|
9145
|
-
method: 'firmwareUpdateV4',
|
|
9146
|
-
},
|
|
9147
|
-
});
|
|
9148
|
-
(method as any).device = stubDevice({
|
|
9149
|
-
features: { mode: 'bootloader', bootloaderMode: true, bootloaderVersion },
|
|
9150
|
-
});
|
|
9151
|
-
|
|
9152
|
-
try {
|
|
9153
|
-
expect((method as any).shouldUseProtocolV2BleFileWriteWindow()).toBe(expected);
|
|
9154
|
-
} finally {
|
|
9155
|
-
getSettingsSpy.mockRestore();
|
|
9156
|
-
}
|
|
9157
|
-
}
|
|
9158
|
-
);
|
|
9159
|
-
|
|
9160
|
-
describe('windowed BLE file write', () => {
|
|
9161
|
-
const confirm = (request: { file: { offset: number; data: ArrayBuffer } }) => ({
|
|
9162
|
-
type: 'FilesystemFile',
|
|
9163
|
-
message: { processed_byte: request.file.offset + request.file.data.byteLength },
|
|
9164
|
-
});
|
|
9165
|
-
const streamAll = (requests: any[]) =>
|
|
9166
|
-
jest.fn(async (_name: string, _resType: string, first: any, options: any) => {
|
|
9167
|
-
// A serial write sends one chunk per call.
|
|
9168
|
-
if (!options.fileWritePipeline) {
|
|
9169
|
-
requests.push(first);
|
|
9170
|
-
return confirm(first);
|
|
9171
|
-
}
|
|
9172
|
-
const { next, onResponse } = options.fileWritePipeline;
|
|
9173
|
-
let request = first;
|
|
9174
|
-
let last;
|
|
9175
|
-
while (request) {
|
|
9176
|
-
requests.push(request);
|
|
9177
|
-
last = confirm(request);
|
|
9178
|
-
onResponse(last);
|
|
9179
|
-
request = await next();
|
|
9180
|
-
}
|
|
9181
|
-
return last;
|
|
9182
|
-
});
|
|
9183
|
-
const createMethod = (typedCall: jest.Mock) => {
|
|
9184
|
-
const method = new FirmwareUpdateV4({
|
|
9185
|
-
id: 1,
|
|
9186
|
-
payload: {
|
|
9187
|
-
method: 'firmwareUpdateV4',
|
|
9188
|
-
},
|
|
9189
|
-
});
|
|
9190
|
-
(method as any).device = stubDevice({ getCommands: () => ({ typedCall }) });
|
|
9191
|
-
(method as any).shouldUseProtocolV2BleFileWriteWindow = jest.fn().mockReturnValue(true);
|
|
9192
|
-
(method as any).getProtocolV2FirmwareChunkSize = jest.fn().mockReturnValue(1000);
|
|
9193
|
-
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
9194
|
-
method.postProgressMessage = jest.fn();
|
|
9195
|
-
return method;
|
|
9196
|
-
};
|
|
9197
|
-
const transfer = async (method: FirmwareUpdateV4) => {
|
|
9198
|
-
const source = await openFirmwareByteSource({ binary: new Uint8Array(2500).buffer });
|
|
9199
|
-
try {
|
|
9200
|
-
const processedSize: number = await (method as any).protocolV2SourceUpdateProcess({
|
|
9201
|
-
source,
|
|
9202
|
-
filePath: 'vol0:/application_p1.bin',
|
|
9203
|
-
processedSize: 500,
|
|
9204
|
-
totalSize: 3000,
|
|
9205
|
-
});
|
|
9206
|
-
return processedSize;
|
|
9207
|
-
} finally {
|
|
9208
|
-
await source?.close();
|
|
9209
|
-
}
|
|
9210
|
-
};
|
|
9211
|
-
|
|
9212
|
-
test('streams the file through one FileWrite call and reports confirmed chunks', async () => {
|
|
9213
|
-
const requests: any[] = [];
|
|
9214
|
-
const typedCall = streamAll(requests);
|
|
9215
|
-
const method = createMethod(typedCall);
|
|
9216
|
-
|
|
9217
|
-
await expect(transfer(method)).resolves.toBe(3000);
|
|
9218
|
-
|
|
9219
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
9220
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
9221
|
-
'FilesystemFileWrite',
|
|
9222
|
-
'FilesystemFile',
|
|
9223
|
-
expect.anything(),
|
|
9224
|
-
expect.objectContaining({
|
|
9225
|
-
writeWithResponse: false,
|
|
9226
|
-
fileWritePipeline: expect.objectContaining({
|
|
9227
|
-
next: expect.any(Function),
|
|
9228
|
-
onResponse: expect.any(Function),
|
|
9229
|
-
}),
|
|
9230
|
-
})
|
|
9231
|
-
);
|
|
9232
|
-
expect(requests.map(request => request.file.offset)).toEqual([0, 1000, 2000]);
|
|
9233
|
-
expect(requests.map(request => request.file.data.byteLength)).toEqual([1000, 1000, 500]);
|
|
9234
|
-
expect(requests.map(request => request.overwrite)).toEqual([true, false, false]);
|
|
9235
|
-
expect(requests.map(request => request.ui_percentage)).toEqual([50, 84, 99]);
|
|
9236
|
-
requests.forEach(request =>
|
|
9237
|
-
expect(request).toMatchObject({
|
|
9238
|
-
append: false,
|
|
9239
|
-
file: { total_size: 2500, path: 'vol0:/application_p1.bin' },
|
|
9240
|
-
})
|
|
9241
|
-
);
|
|
9242
|
-
const transferredBytes = (method.postProgressMessage as jest.Mock).mock.calls.map(
|
|
9243
|
-
([, , metrics]) => metrics.transferredBytes
|
|
9244
|
-
);
|
|
9245
|
-
expect(transferredBytes).toEqual([1500, 2500, 3000]);
|
|
9246
|
-
});
|
|
9247
|
-
|
|
9248
|
-
test('caps windowed staging writes at the generic BLE chunk', async () => {
|
|
9249
|
-
const requests: any[] = [];
|
|
9250
|
-
const method = createMethod(streamAll(requests));
|
|
9251
|
-
delete (method as any).getProtocolV2FirmwareChunkSize;
|
|
9252
|
-
(method as any).params = { platform: 'native', chunkSize: 4096 };
|
|
9253
|
-
|
|
9254
|
-
await expect(transfer(method)).resolves.toBe(3000);
|
|
9255
|
-
|
|
9256
|
-
expect(requests.map(request => request.file.offset)).toEqual([0, 1800]);
|
|
9257
|
-
expect(requests.map(request => request.file.data.byteLength)).toEqual([1800, 700]);
|
|
9258
|
-
});
|
|
9259
|
-
|
|
9260
|
-
test.each(['react-native', 'desktop-web-ble'] as const)(
|
|
9261
|
-
'bounds each windowed response wait in %s',
|
|
9262
|
-
async env => {
|
|
9263
|
-
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue(env);
|
|
9264
|
-
const typedCall = streamAll([]);
|
|
9265
|
-
|
|
9266
|
-
try {
|
|
9267
|
-
await transfer(createMethod(typedCall));
|
|
9268
|
-
} finally {
|
|
9269
|
-
getSettingsSpy.mockRestore();
|
|
9270
|
-
}
|
|
9271
|
-
|
|
9272
|
-
expect(typedCall.mock.calls[0][3]).toEqual(expect.objectContaining({ timeoutMs: 10_000 }));
|
|
9273
|
-
}
|
|
9274
|
-
);
|
|
9275
|
-
|
|
9276
|
-
const failOnceThenSucceed = (requests: any[]) =>
|
|
9277
|
-
jest
|
|
9278
|
-
.fn()
|
|
9279
|
-
.mockRejectedValueOnce(new Error('Protocol V2 file write response does not match'))
|
|
9280
|
-
.mockImplementation(streamAll(requests));
|
|
9281
|
-
|
|
9282
|
-
test('restarts the whole file serially after a failed windowed write', async () => {
|
|
9283
|
-
const requests: any[] = [];
|
|
9284
|
-
const typedCall = failOnceThenSucceed(requests);
|
|
9285
|
-
const method = createMethod(typedCall);
|
|
9286
|
-
|
|
9287
|
-
await expect(transfer(method)).resolves.toBe(3000);
|
|
9288
|
-
|
|
9289
|
-
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
9290
|
-
// One failed windowed call, then one serial call per chunk from offset 0.
|
|
9291
|
-
expect(typedCall).toHaveBeenCalledTimes(4);
|
|
9292
|
-
typedCall.mock.calls
|
|
9293
|
-
.slice(1)
|
|
9294
|
-
.forEach(call => expect(call[3]).not.toHaveProperty('fileWritePipeline'));
|
|
9295
|
-
expect(typedCall.mock.calls[1][2]).toEqual(
|
|
9296
|
-
expect.objectContaining({ overwrite: true, file: expect.objectContaining({ offset: 0 }) })
|
|
9297
|
-
);
|
|
9298
|
-
expect(requests.map(request => request.file.offset)).toEqual([0, 1000, 2000]);
|
|
9299
|
-
// The retry sizes its chunks for serial writes, not the window.
|
|
9300
|
-
const chunkSize = (method as any).getProtocolV2FirmwareChunkSize as jest.Mock;
|
|
9301
|
-
expect(chunkSize).toHaveBeenNthCalledWith(1, 'write', 'vol0:/application_p1.bin', true);
|
|
9302
|
-
expect(chunkSize).toHaveBeenNthCalledWith(2, 'write', 'vol0:/application_p1.bin', false);
|
|
9303
|
-
});
|
|
9304
|
-
|
|
9305
|
-
test('keeps later files serial once a windowed attempt failed', async () => {
|
|
9306
|
-
const typedCall = failOnceThenSucceed([]);
|
|
9307
|
-
const method = createMethod(typedCall);
|
|
9308
|
-
await transfer(method);
|
|
9309
|
-
typedCall.mockClear();
|
|
9310
|
-
|
|
9311
|
-
await expect(transfer(method)).resolves.toBe(3000);
|
|
9312
|
-
|
|
9313
|
-
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
9314
|
-
typedCall.mock.calls.forEach(call => expect(call[3]).not.toHaveProperty('fileWritePipeline'));
|
|
9315
|
-
expect((method as any).shouldUseProtocolV2BleFileWriteWindow).toHaveBeenCalledTimes(1);
|
|
9316
|
-
});
|
|
9317
|
-
|
|
9318
|
-
test('stops sending on cancel and raises it after the transport call returns', async () => {
|
|
9319
|
-
const abortController = new AbortController();
|
|
9320
|
-
const requests: any[] = [];
|
|
9321
|
-
const succeed = streamAll(requests);
|
|
9322
|
-
const typedCall = jest.fn((name: string, resType: string, first: any, options: any) =>
|
|
9323
|
-
succeed(name, resType, first, {
|
|
9324
|
-
...options,
|
|
9325
|
-
fileWritePipeline: {
|
|
9326
|
-
...options.fileWritePipeline,
|
|
9327
|
-
onResponse: (response: any) => {
|
|
9328
|
-
options.fileWritePipeline.onResponse(response);
|
|
9329
|
-
abortController.abort();
|
|
9330
|
-
},
|
|
9331
|
-
},
|
|
9332
|
-
})
|
|
9333
|
-
);
|
|
9334
|
-
const method = createMethod(typedCall);
|
|
9335
|
-
method.abortSignal = abortController.signal;
|
|
9336
|
-
|
|
9337
|
-
await expect(transfer(method)).rejects.toMatchObject({
|
|
9338
|
-
errorCode: HardwareErrorCode.CallQueueActionCancelled,
|
|
9339
|
-
});
|
|
9340
|
-
expect(requests.map(request => request.file.offset)).toEqual([0]);
|
|
9341
|
-
expect((method as any).recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
9342
|
-
});
|
|
9343
|
-
|
|
9344
|
-
test('raises a source read error after the windowed call and retries the file windowed', async () => {
|
|
9345
|
-
const requests: any[] = [];
|
|
9346
|
-
const typedCall = streamAll(requests);
|
|
9347
|
-
const method = createMethod(typedCall);
|
|
9348
|
-
const bytes = new Uint8Array(2500);
|
|
9349
|
-
let failed = false;
|
|
9350
|
-
const source = {
|
|
9351
|
-
size: bytes.length,
|
|
9352
|
-
readAt: jest.fn((offset: number, length: number) => {
|
|
9353
|
-
if (offset === 1000 && !failed) {
|
|
9354
|
-
failed = true;
|
|
9355
|
-
return Promise.reject(new Error('artifact read failed'));
|
|
9356
|
-
}
|
|
9357
|
-
return Promise.resolve(bytes.slice(offset, offset + length).buffer);
|
|
9358
|
-
}),
|
|
9359
|
-
close: jest.fn(),
|
|
9360
|
-
};
|
|
9361
|
-
|
|
9362
|
-
await expect(
|
|
9363
|
-
(method as any).protocolV2SourceUpdateProcess({
|
|
9364
|
-
source,
|
|
9365
|
-
filePath: 'vol0:/application_p1.bin',
|
|
9366
|
-
processedSize: 0,
|
|
9367
|
-
totalSize: 2500,
|
|
9368
|
-
})
|
|
9369
|
-
).resolves.toBe(2500);
|
|
9370
|
-
|
|
9371
|
-
// A host-side read error says nothing about the bootloader, so the retry keeps the window.
|
|
9372
|
-
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
9373
|
-
typedCall.mock.calls.forEach(call => expect(call[3]).toHaveProperty('fileWritePipeline'));
|
|
9374
|
-
expect((method as any).recoverProtocolV2FileTransfer).toHaveBeenCalledTimes(1);
|
|
9375
|
-
expect(requests.map(request => request.file.offset)).toEqual([0, 0, 1000, 2000]);
|
|
9376
|
-
});
|
|
9377
|
-
|
|
9378
|
-
test('rejects a windowed write that does not confirm the whole file', async () => {
|
|
9379
|
-
const typedCall = jest.fn((_name: string, _resType: string, first: any) =>
|
|
9380
|
-
Promise.resolve(confirm(first))
|
|
9381
|
-
);
|
|
9382
|
-
const method = createMethod(typedCall);
|
|
9383
|
-
const source = await openFirmwareByteSource({ binary: new Uint8Array(2500).buffer });
|
|
9384
|
-
|
|
9385
|
-
try {
|
|
9386
|
-
await expect(
|
|
9387
|
-
(method as any).writeProtocolV2SourceWindowed({
|
|
9388
|
-
source,
|
|
9389
|
-
filePath: 'vol0:/application_p1.bin',
|
|
9390
|
-
chunkSize: 1000,
|
|
9391
|
-
processedSize: 500,
|
|
9392
|
-
totalSize: 3000,
|
|
9393
|
-
onChunkConfirmed: jest.fn(),
|
|
9394
|
-
})
|
|
9395
|
-
).rejects.toMatchObject({
|
|
9396
|
-
errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
9397
|
-
message: expect.stringContaining('invalid processed_byte 1000 for file size 2500'),
|
|
9398
|
-
});
|
|
9399
|
-
} finally {
|
|
9400
|
-
await source?.close();
|
|
9401
|
-
}
|
|
9402
|
-
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
9403
|
-
expect((method as any).protocolV2FileWriteWindowDisabled).toBe(true);
|
|
9404
|
-
});
|
|
9405
|
-
});
|
|
9406
|
-
|
|
9407
8886
|
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
9408
8887
|
const method = new FirmwareUpdateV4({
|
|
9409
8888
|
id: 1,
|
|
@@ -10133,27 +9612,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
10133
9612
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
|
|
10134
9613
|
});
|
|
10135
9614
|
|
|
10136
|
-
test.each([
|
|
10137
|
-
[false, 'vol0:/application_p1.bin', 1960],
|
|
10138
|
-
[true, 'vol0:/application_p1.bin', 1800],
|
|
10139
|
-
[true, 'vol0:/bundles/images/animation.okpkg', 1800],
|
|
10140
|
-
])(
|
|
10141
|
-
'sizes BLE write chunks for fileWriteWindow=%s and %s',
|
|
10142
|
-
(fileWriteWindow, filePath, expected) => {
|
|
10143
|
-
const method = new FirmwareUpdateV4({
|
|
10144
|
-
id: 1,
|
|
10145
|
-
payload: {
|
|
10146
|
-
method: 'firmwareUpdateV4',
|
|
10147
|
-
},
|
|
10148
|
-
});
|
|
10149
|
-
(method as any).params = { platform: 'native', chunkSize: 4096 };
|
|
10150
|
-
|
|
10151
|
-
expect(
|
|
10152
|
-
(method as any).getProtocolV2FirmwareChunkSize('write', filePath, fileWriteWindow)
|
|
10153
|
-
).toBe(expected);
|
|
10154
|
-
}
|
|
10155
|
-
);
|
|
10156
|
-
|
|
10157
9615
|
test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
|
|
10158
9616
|
const method = new FirmwareUpdateV4({
|
|
10159
9617
|
id: 1,
|
|
@@ -2,7 +2,6 @@ import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
|
2
2
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
3
3
|
import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
|
|
4
4
|
export declare function assertProtocolV2FirmwareTargetsSupported(deviceType: EDeviceType | string | undefined, params: FirmwareUpdateV4Params, hasExplicitTargetSelection?: boolean): void;
|
|
5
|
-
export declare function supportsProtocolV2BleFileWriteWindow(bootloaderVersion: string, minBootloaderVersion?: string | undefined): boolean;
|
|
6
5
|
export declare const isProtocolV2FirmwareFingerprintValid: (binary: ArrayBuffer | Uint8Array, fingerprint: string | undefined) => boolean;
|
|
7
6
|
export declare const assertProtocolV2ReconnectIdentity: (expectedSerialNumber?: string, actualSerialNumber?: string, expectedPath?: string, actualPath?: string) => void;
|
|
8
7
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -26,7 +25,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
26
25
|
private protocolV2LastTransferProgress?;
|
|
27
26
|
private protocolV2LastTransferProgressAt;
|
|
28
27
|
private protocolV2LastTransferredBytes;
|
|
29
|
-
private protocolV2FileWriteWindowDisabled;
|
|
30
28
|
init(): void;
|
|
31
29
|
private getProtocolV2FirmwareChunkSize;
|
|
32
30
|
run(): Promise<{
|
|
@@ -70,7 +68,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
70
68
|
private getProtocolV2ResourceComparePath;
|
|
71
69
|
private readProtocolV2DeviceFileHeader;
|
|
72
70
|
private isProtocolV2ResourceBundleUpToDate;
|
|
73
|
-
private isProtocolV2ResourceBundleUpToDateWithRecovery;
|
|
74
71
|
private isProtocolV2BootloaderMode;
|
|
75
72
|
private isProtocolV2RomloaderMode;
|
|
76
73
|
private rebootProtocolV2ToBootloader;
|
|
@@ -102,8 +99,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
102
99
|
private ensureProtocolV2DeviceAcquired;
|
|
103
100
|
private isProtocolV2ReconnectIdentityError;
|
|
104
101
|
private fileWriteChunk;
|
|
105
|
-
private shouldUseProtocolV2BleFileWriteWindow;
|
|
106
|
-
private writeProtocolV2SourceWindowed;
|
|
107
102
|
private recoverProtocolV2FileTransfer;
|
|
108
103
|
private acquireProtocolV2BleDevice;
|
|
109
104
|
private protocolV2StartFirmwareUpdate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkD,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
|