@onekeyfe/hd-core 1.2.0-alpha.97 → 1.2.0-alpha.98
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__/check-all-firmware-release-protocol-v2.test.ts +404 -84
- package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
- package/__tests__/firmware-memory-host.test.ts +112 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +13 -2
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +573 -0
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +232 -70
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
- package/__tests__/protocol-v2-resources.test.ts +148 -232
- package/__tests__/protocol-v2.test.ts +1236 -516
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +2 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +1 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +8 -8
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +6 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -1
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts +33 -0
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +108 -114
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1633 -967
- package/dist/protocols/protocol-v2/resources.d.ts +10 -28
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -12
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/settings.d.ts +35 -36
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +108 -80
- package/src/api/CheckBLEFirmwareRelease.ts +37 -5
- package/src/api/CheckBootloaderRelease.ts +35 -3
- package/src/api/CheckFirmwareRelease.ts +35 -7
- package/src/api/FirmwareUpdateV2.ts +108 -73
- package/src/api/FirmwareUpdateV3.ts +101 -56
- package/src/api/FirmwareUpdateV4.ts +885 -403
- package/src/api/UploadPortfolio.ts +18 -0
- package/src/api/device/DeviceUpdateBootloader.ts +37 -11
- package/src/api/firmware/FirmwareHostBinding.ts +16 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +142 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +282 -122
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +27 -3
- package/src/api/firmware/protocolV2Release.ts +168 -0
- package/src/data-manager/DataManager.ts +35 -19
- package/src/index.ts +10 -0
- package/src/protocols/protocol-v2/resources.ts +157 -341
- package/src/types/api/checkAllFirmwareRelease.ts +27 -12
- package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
- package/src/types/api/checkBootloaderRelease.ts +2 -6
- package/src/types/api/checkFirmwareRelease.ts +2 -13
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/deviceUpdateBootloader.ts +1 -0
- package/src/types/api/export.ts +6 -1
- package/src/types/api/firmwareUpdate.ts +12 -17
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/settings.ts +35 -61
- package/src/utils/deviceFeaturesUtils.ts +2 -2
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
4
|
|
|
5
5
|
import { DataManager } from '../src/data-manager';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
PROTOCOL_V2_RESOURCE_TYPES,
|
|
9
|
-
buildProtocolV2ResourceUpdatePlan,
|
|
10
|
-
isProtocolV2ResourceFileValid,
|
|
7
|
+
parseProtocolV2ResourceManifest,
|
|
11
8
|
parseProtocolV2Resources,
|
|
12
|
-
readProtocolV2ResourceInventory,
|
|
13
9
|
} from '../src/protocols/protocol-v2/resources';
|
|
14
10
|
|
|
15
|
-
import type {
|
|
16
|
-
ConnectSettings,
|
|
17
|
-
IProtocolV2BootResources,
|
|
18
|
-
IProtocolV2Resource,
|
|
19
|
-
RemoteConfigResponse,
|
|
20
|
-
} from '../src/types';
|
|
11
|
+
import type { ConnectSettings, RemoteConfigResponse } from '../src/types';
|
|
21
12
|
|
|
22
13
|
jest.mock('axios');
|
|
23
14
|
jest.mock('../src/data/config', () => ({
|
|
@@ -28,45 +19,77 @@ jest.mock('../src/data/config', () => ({
|
|
|
28
19
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
29
20
|
Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
30
21
|
|
|
31
|
-
const
|
|
22
|
+
const resourceSource = {
|
|
23
|
+
archiveUrl: 'https://example.com/resource/pro2-resource.zip',
|
|
24
|
+
archiveSha256: 'a'.repeat(64),
|
|
25
|
+
archiveSize: 16_815_479,
|
|
26
|
+
};
|
|
32
27
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
header[index] = char.charCodeAt(0);
|
|
38
|
-
});
|
|
39
|
-
'RESC'.split('').forEach((char, index) => {
|
|
40
|
-
header[0x08 + index] = char.charCodeAt(0);
|
|
41
|
-
});
|
|
42
|
-
view.setUint32(0x0c, header.byteLength, true);
|
|
43
|
-
for (let index = 0; index < headerHash.length / 2; index++) {
|
|
44
|
-
header[0x240 + index] = Number.parseInt(headerHash.slice(index * 2, index * 2 + 2), 16);
|
|
45
|
-
}
|
|
46
|
-
return header;
|
|
28
|
+
const neoResourceSource = {
|
|
29
|
+
archiveUrl: 'https://example.com/resource/neo-resource.zip',
|
|
30
|
+
archiveSha256: 'b'.repeat(64),
|
|
31
|
+
archiveSize: 12_345_678,
|
|
47
32
|
};
|
|
48
33
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
34
|
+
const manifestFiles = [
|
|
35
|
+
['bundles/firmware_logo-build.okpkg', 'firmware_logo.okpkg', 'vol0:/bundles/firmware_logo.okpkg'],
|
|
36
|
+
['bundles/font/noto-build.okpkg', 'noto.okpkg', 'vol0:/bundles/font/noto.okpkg'],
|
|
37
|
+
['bundles/font/roobert-build.okpkg', 'roobert.okpkg', 'vol0:/bundles/font/roobert.okpkg'],
|
|
38
|
+
[
|
|
39
|
+
'bundles/images/animation-build.okpkg',
|
|
40
|
+
'animation.okpkg',
|
|
41
|
+
'vol0:/bundles/images/animation.okpkg',
|
|
42
|
+
],
|
|
43
|
+
['bundles/images/images-build.okpkg', 'images.okpkg', 'vol0:/bundles/images/images.okpkg'],
|
|
44
|
+
[
|
|
45
|
+
'bundles/images/wallpaper-build.okpkg',
|
|
46
|
+
'wallpaper.okpkg',
|
|
47
|
+
'vol0:/bundles/images/wallpaper.okpkg',
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
'bundles/translations/translations-build.okpkg',
|
|
51
|
+
'translations.okpkg',
|
|
52
|
+
'vol0:/bundles/translations/translations.okpkg',
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
'loaders/bootloader/boot_resource-build.okpkg',
|
|
56
|
+
'boot_resource.okpkg',
|
|
57
|
+
'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
58
|
+
],
|
|
59
|
+
['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
|
|
60
|
+
].map(([archive_path, original_name, device_path], index) => {
|
|
61
|
+
const binary = new Uint8Array([index + 1]).buffer;
|
|
62
|
+
return {
|
|
63
|
+
archive_path,
|
|
64
|
+
original_name,
|
|
65
|
+
device_path,
|
|
66
|
+
size: 1,
|
|
67
|
+
sha256: bytesToHex(sha256(new Uint8Array(binary))),
|
|
68
|
+
signed: true,
|
|
69
|
+
sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
|
|
70
|
+
payload_version: '1.0.0',
|
|
71
|
+
binary,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
56
74
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
const resourceManifest = {
|
|
76
|
+
schema: 1,
|
|
77
|
+
artifact_name: 'pro2-resource-build',
|
|
78
|
+
release_name: 'resource-build',
|
|
79
|
+
variant: 'resource',
|
|
80
|
+
commit: 'a'.repeat(40),
|
|
81
|
+
short_sha: 'aaaaaaa',
|
|
82
|
+
timestamp_utc: '20260807_091424',
|
|
83
|
+
core_version: '1.0.0',
|
|
84
|
+
key_set: 'dev',
|
|
85
|
+
device_root: 'vol0:',
|
|
86
|
+
restore_mode: 'bootloader_update',
|
|
87
|
+
trees: [
|
|
88
|
+
{ path: 'bundles', device: 'vol0:/bundles' },
|
|
89
|
+
{ path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
|
|
90
|
+
{ path: 'loaders/rom', device: 'vol0:/loaders/rom' },
|
|
69
91
|
],
|
|
92
|
+
files: manifestFiles.map(({ binary: _binary, ...file }) => file),
|
|
70
93
|
};
|
|
71
94
|
|
|
72
95
|
const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
|
|
@@ -85,7 +108,8 @@ const createRemoteConfig = (): RemoteConfigResponse =>
|
|
|
85
108
|
mini: { firmware: [], ble: [] },
|
|
86
109
|
touch: { firmware: [], ble: [] },
|
|
87
110
|
pro: { firmware: [], ble: [] },
|
|
88
|
-
pro2: { firmware: [], ble: [], resources: {
|
|
111
|
+
pro2: { firmware: [], ble: [], resources: { source: resourceSource } },
|
|
112
|
+
neo: { firmware: [], ble: [], resources: { source: neoResourceSource } },
|
|
89
113
|
bridge: {},
|
|
90
114
|
} as unknown as RemoteConfigResponse);
|
|
91
115
|
|
|
@@ -95,201 +119,44 @@ describe('Pro2 resource configuration', () => {
|
|
|
95
119
|
DataManager.lastCheckTimestamp = 0;
|
|
96
120
|
});
|
|
97
121
|
|
|
98
|
-
test('accepts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
boot: bootResources,
|
|
122
|
+
test('accepts the manifest source and the CI schema 1 file set', () => {
|
|
123
|
+
expect(parseProtocolV2Resources({ source: resourceSource })).toEqual({
|
|
124
|
+
source: resourceSource,
|
|
102
125
|
});
|
|
103
|
-
|
|
104
|
-
expect(parsed?.stable.map(item => item.type)).toEqual(PROTOCOL_V2_RESOURCE_TYPES);
|
|
105
|
-
expect(parsed?.boot).toEqual(bootResources);
|
|
106
|
-
expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.translations).toBe(
|
|
107
|
-
'vol0:/bundles/translations/translations.okpkg'
|
|
108
|
-
);
|
|
109
|
-
expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.firmware_logo).toBe(
|
|
110
|
-
'vol0:/bundles/firmware_logo.okpkg'
|
|
111
|
-
);
|
|
126
|
+
expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
|
|
112
127
|
});
|
|
113
128
|
|
|
114
|
-
test('rejects
|
|
115
|
-
expect(() => parseProtocolV2Resources({
|
|
116
|
-
'7 unique resource types'
|
|
117
|
-
);
|
|
118
|
-
expect(() =>
|
|
119
|
-
parseProtocolV2Resources({ stable: [...resources.slice(0, 6), resources[0]] })
|
|
120
|
-
).toThrow('7 unique resource types');
|
|
129
|
+
test('rejects missing source and malformed manifest paths', () => {
|
|
130
|
+
expect(() => parseProtocolV2Resources({})).toThrow('source is required');
|
|
121
131
|
expect(() =>
|
|
122
132
|
parseProtocolV2Resources({
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
source: {
|
|
134
|
+
...resourceSource,
|
|
135
|
+
archiveUrl: 'http://example.com/pro2-resource.zip',
|
|
136
|
+
},
|
|
126
137
|
})
|
|
127
|
-
).toThrow('
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('requires boot resources to remain optional and use RES file entries', () => {
|
|
138
|
+
).toThrow('must use HTTPS');
|
|
131
139
|
expect(() =>
|
|
132
140
|
parseProtocolV2Resources({
|
|
133
|
-
|
|
134
|
-
boot: { ...bootResources, required: true },
|
|
141
|
+
source: { ...resourceSource, archiveSha256: 'invalid' },
|
|
135
142
|
})
|
|
136
|
-
).toThrow('
|
|
143
|
+
).toThrow('SHA-256');
|
|
137
144
|
expect(() =>
|
|
138
145
|
parseProtocolV2Resources({
|
|
139
|
-
|
|
140
|
-
boot: { ...bootResources, target: 'INVALID' },
|
|
146
|
+
source: { ...resourceSource, archiveSize: 0 },
|
|
141
147
|
})
|
|
142
|
-
).toThrow('
|
|
148
|
+
).toThrow('positive integer');
|
|
143
149
|
expect(() =>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
...
|
|
148
|
-
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
).toThrow('devicePath');
|
|
152
|
-
|
|
153
|
-
for (const devicePath of [
|
|
154
|
-
'vol0:/assets/loaders\\bootloader_crest.bin',
|
|
155
|
-
'vol0:/assets/loaders\\\\bootloader_crest.bin',
|
|
156
|
-
]) {
|
|
157
|
-
expect(() =>
|
|
158
|
-
parseProtocolV2Resources({
|
|
159
|
-
stable: resources,
|
|
160
|
-
boot: {
|
|
161
|
-
...bootResources,
|
|
162
|
-
files: [{ ...bootResources.files[0], devicePath }],
|
|
163
|
-
},
|
|
164
|
-
})
|
|
165
|
-
).toThrow('devicePath');
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('downloads nothing when all resource identities match', () => {
|
|
170
|
-
const inventory = resources.map(({ type, size, headerHash }) => ({ type, size, headerHash }));
|
|
171
|
-
|
|
172
|
-
expect(
|
|
173
|
-
buildProtocolV2ResourceUpdatePlan({ resources, inventory, mode: 'application' })
|
|
174
|
-
).toEqual({ status: 'valid', resources: [] });
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('builds inventory from existing filesystem calls without ResourceInventoryGet', async () => {
|
|
178
|
-
const installedResources = resources.map((resource, index) => ({
|
|
179
|
-
...resource,
|
|
180
|
-
size: PROTOCOL_V2_OKPP_HEADER_SIZE + index + 1,
|
|
181
|
-
}));
|
|
182
|
-
const resourceByPath = new Map(
|
|
183
|
-
installedResources.map(resource => [
|
|
184
|
-
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
185
|
-
resource,
|
|
186
|
-
])
|
|
187
|
-
);
|
|
188
|
-
const typedCall = jest.fn(
|
|
189
|
-
(requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
190
|
-
if (requestType === 'ResourceInventoryGet') {
|
|
191
|
-
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
192
|
-
}
|
|
193
|
-
if (requestType === 'FilesystemPathInfoQuery') {
|
|
194
|
-
const resource = resourceByPath.get(payload.path);
|
|
195
|
-
return {
|
|
196
|
-
message: {
|
|
197
|
-
exist: Boolean(resource),
|
|
198
|
-
directory: false,
|
|
199
|
-
size: resource?.size ?? 0,
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
if (requestType === 'FilesystemFileRead') {
|
|
204
|
-
const resource = resourceByPath.get(payload.file.path);
|
|
205
|
-
if (!resource) throw new Error('missing resource');
|
|
206
|
-
const header = createResourceHeader(resource.headerHash);
|
|
207
|
-
const offset = Number(payload.file.offset);
|
|
208
|
-
const chunkLength = Number(payload.chunk_len);
|
|
209
|
-
return {
|
|
210
|
-
message: {
|
|
211
|
-
data: header.slice(offset, offset + chunkLength),
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
216
|
-
}
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
await expect(
|
|
220
|
-
readProtocolV2ResourceInventory({
|
|
221
|
-
commands: { typedCall },
|
|
222
|
-
resources: installedResources,
|
|
223
|
-
chunkSize: 4000,
|
|
150
|
+
parseProtocolV2ResourceManifest({
|
|
151
|
+
...resourceManifest,
|
|
152
|
+
files: resourceManifest.files.map((file, index) =>
|
|
153
|
+
index === 0 ? { ...file, archive_path: '../outside.okpkg' } : file
|
|
154
|
+
),
|
|
224
155
|
})
|
|
225
|
-
).
|
|
226
|
-
installedResources.map(({ type, size, headerHash }) => ({ type, size, headerHash }))
|
|
227
|
-
);
|
|
228
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
229
|
-
expect(typedCall.mock.calls.filter(call => call[0] === 'FilesystemPathInfoQuery')).toHaveLength(
|
|
230
|
-
7
|
|
231
|
-
);
|
|
232
|
-
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('selects only the changed or missing resource in application mode', () => {
|
|
236
|
-
const inventory = resources
|
|
237
|
-
.filter(resource => resource.type !== 'noto')
|
|
238
|
-
.map(({ type, size, headerHash }) => ({
|
|
239
|
-
type,
|
|
240
|
-
size: type === 'images' ? size + 1 : size,
|
|
241
|
-
headerHash,
|
|
242
|
-
}));
|
|
243
|
-
|
|
244
|
-
const result = buildProtocolV2ResourceUpdatePlan({
|
|
245
|
-
resources,
|
|
246
|
-
inventory,
|
|
247
|
-
mode: 'application',
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
expect(result.status).toBe('outdated');
|
|
251
|
-
expect(result.resources.map(resource => resource.type)).toEqual(['images', 'noto']);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('reports unknown without an application inventory and selects all in recovery mode', () => {
|
|
255
|
-
expect(buildProtocolV2ResourceUpdatePlan({ resources, mode: 'application' })).toEqual({
|
|
256
|
-
status: 'unknown',
|
|
257
|
-
resources: [],
|
|
258
|
-
});
|
|
259
|
-
expect(
|
|
260
|
-
buildProtocolV2ResourceUpdatePlan({ resources, mode: 'bootloader-recovery' }).resources
|
|
261
|
-
).toHaveLength(7);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
test('uses a filesystem inventory for incremental recovery mode updates', () => {
|
|
265
|
-
const inventory = resources.slice(1).map(({ type, size, headerHash }) => ({
|
|
266
|
-
type,
|
|
267
|
-
size,
|
|
268
|
-
headerHash,
|
|
269
|
-
}));
|
|
270
|
-
|
|
271
|
-
expect(
|
|
272
|
-
buildProtocolV2ResourceUpdatePlan({
|
|
273
|
-
resources,
|
|
274
|
-
inventory,
|
|
275
|
-
mode: 'bootloader-recovery',
|
|
276
|
-
}).resources.map(resource => resource.type)
|
|
277
|
-
).toEqual(['images']);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('verifies both full file size and SHA-256 before transfer', () => {
|
|
281
|
-
const bytes = new Uint8Array([1, 2, 3]);
|
|
282
|
-
const binary = bytes.buffer;
|
|
283
|
-
const identity = { size: bytes.byteLength, fileHash: bytesToHex(sha256(bytes)) };
|
|
284
|
-
|
|
285
|
-
expect(isProtocolV2ResourceFileValid(binary, identity)).toBe(true);
|
|
286
|
-
expect(isProtocolV2ResourceFileValid(binary, { ...identity, size: 4 })).toBe(false);
|
|
287
|
-
expect(isProtocolV2ResourceFileValid(binary, { ...identity, fileHash: '0'.repeat(64) })).toBe(
|
|
288
|
-
false
|
|
289
|
-
);
|
|
156
|
+
).toThrow('archive_path');
|
|
290
157
|
});
|
|
291
158
|
|
|
292
|
-
test('applies a validated pre-release config and exposes
|
|
159
|
+
test('applies a validated pre-release config and exposes its archive source', async () => {
|
|
293
160
|
const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
|
|
294
161
|
|
|
295
162
|
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
@@ -297,8 +164,7 @@ describe('Pro2 resource configuration', () => {
|
|
|
297
164
|
expect(configFetcher).toHaveBeenCalledWith(
|
|
298
165
|
expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
|
|
299
166
|
);
|
|
300
|
-
expect(DataManager.
|
|
301
|
-
expect(DataManager.getProtocolV2BootResources()).toEqual(bootResources);
|
|
167
|
+
expect(DataManager.getProtocolV2ResourceSource()).toEqual(resourceSource);
|
|
302
168
|
expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
|
|
303
169
|
});
|
|
304
170
|
|
|
@@ -314,18 +180,45 @@ describe('Pro2 resource configuration', () => {
|
|
|
314
180
|
|
|
315
181
|
test('keeps base SDK initialization available when remote Pro2 resources are invalid', async () => {
|
|
316
182
|
const remoteConfig = createRemoteConfig();
|
|
317
|
-
(remoteConfig.pro2 as { resources?: unknown }).resources = {
|
|
183
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
|
|
318
184
|
const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
|
|
319
185
|
|
|
320
186
|
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
321
187
|
|
|
322
|
-
expect(DataManager.
|
|
323
|
-
expect(DataManager.
|
|
188
|
+
expect(DataManager.getProtocolV2ResourceSource()).toBeUndefined();
|
|
189
|
+
expect(DataManager.protocolV2ResourcesConfigError).toBeInstanceOf(Error);
|
|
190
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toEqual(neoResourceSource);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('keeps Pro2 resources available when remote Neo resources are invalid', async () => {
|
|
194
|
+
const remoteConfig = createRemoteConfig();
|
|
195
|
+
(remoteConfig.neo as { resources?: unknown }).resources = { source: {} };
|
|
196
|
+
const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
|
|
197
|
+
|
|
198
|
+
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
199
|
+
|
|
200
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Pro2)).toEqual(resourceSource);
|
|
201
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toBeUndefined();
|
|
202
|
+
await expect(
|
|
203
|
+
DataManager.forceReloadData({
|
|
204
|
+
requireResources: true,
|
|
205
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
206
|
+
})
|
|
207
|
+
).resolves.toBeUndefined();
|
|
208
|
+
await expect(
|
|
209
|
+
DataManager.forceReloadData({
|
|
210
|
+
requireResources: true,
|
|
211
|
+
resourceDeviceType: EDeviceType.Neo,
|
|
212
|
+
})
|
|
213
|
+
).rejects.toMatchObject({
|
|
214
|
+
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
215
|
+
message: expect.stringContaining('Invalid Neo resources config'),
|
|
216
|
+
});
|
|
324
217
|
});
|
|
325
218
|
|
|
326
219
|
test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
|
|
327
220
|
const remoteConfig = createRemoteConfig();
|
|
328
|
-
(remoteConfig.pro2 as { resources?: unknown }).resources = {
|
|
221
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
|
|
329
222
|
const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
|
|
330
223
|
DataManager.settings = settings;
|
|
331
224
|
|
|
@@ -337,6 +230,29 @@ describe('Pro2 resource configuration', () => {
|
|
|
337
230
|
expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
|
|
338
231
|
});
|
|
339
232
|
|
|
233
|
+
test('checks resource configuration errors for the requested device only', async () => {
|
|
234
|
+
const remoteConfig = createRemoteConfig();
|
|
235
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
|
|
236
|
+
const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
|
|
237
|
+
DataManager.settings = settings;
|
|
238
|
+
|
|
239
|
+
await expect(
|
|
240
|
+
DataManager.forceReloadData({
|
|
241
|
+
requireResources: true,
|
|
242
|
+
resourceDeviceType: EDeviceType.Neo,
|
|
243
|
+
})
|
|
244
|
+
).resolves.toBeUndefined();
|
|
245
|
+
await expect(
|
|
246
|
+
DataManager.forceReloadData({
|
|
247
|
+
requireResources: true,
|
|
248
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
249
|
+
})
|
|
250
|
+
).rejects.toMatchObject({
|
|
251
|
+
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
252
|
+
message: expect.stringContaining('Invalid Pro2 resources config'),
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
340
256
|
test('does not advance the cache timestamp when refresh fails', async () => {
|
|
341
257
|
jest.spyOn(axios, 'get').mockRejectedValue(new Error('offline'));
|
|
342
258
|
const settings = createSettings(jest.fn().mockResolvedValue(null));
|